summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--COPYRIGHT.txt6
-rw-r--r--SConstruct11
-rw-r--r--core/SCsub1
-rw-r--r--core/core_bind.cpp10
-rw-r--r--core/core_bind.h2
-rw-r--r--core/extension/gdextension.cpp56
-rw-r--r--core/extension/gdextension.h4
-rw-r--r--core/io/file_access.cpp1
-rw-r--r--core/io/file_access.h1
-rw-r--r--core/io/file_access_compressed.h1
-rw-r--r--core/io/file_access_encrypted.h1
-rw-r--r--core/io/file_access_memory.h1
-rw-r--r--core/io/file_access_pack.h1
-rw-r--r--core/io/file_access_zip.h1
-rw-r--r--core/io/json.cpp4
-rw-r--r--core/io/resource_saver.cpp2
-rw-r--r--core/math/basis.cpp2
-rw-r--r--core/math/basis.h2
-rw-r--r--core/math/delaunay_3d.h77
-rw-r--r--core/math/geometry_2d.cpp111
-rw-r--r--core/os/os.h9
-rw-r--r--core/os/shared_object.h (renamed from editor/export/editor_export_shared_object.h)8
-rw-r--r--core/string/char_utils.h2
-rw-r--r--core/string/translation.cpp2
-rw-r--r--core/variant/variant_parser.cpp4
-rw-r--r--doc/classes/ClassDB.xml9
-rw-r--r--doc/classes/FileAccess.xml7
-rw-r--r--doc/classes/MeshInstance3D.xml8
-rw-r--r--doc/classes/NavigationMeshSourceGeometryData2D.xml14
-rw-r--r--doc/classes/NavigationMeshSourceGeometryData3D.xml8
-rw-r--r--doc/classes/SurfaceTool.xml10
-rw-r--r--doc/classes/TileMapLayer.xml2
-rw-r--r--doc/classes/XRBodyModifier3D.xml9
-rw-r--r--doc/classes/XRBodyTracker.xml3
-rw-r--r--doc/classes/XRControllerTracker.xml17
-rw-r--r--doc/classes/XRFaceModifier3D.xml2
-rw-r--r--doc/classes/XRFaceTracker.xml3
-rw-r--r--doc/classes/XRHandModifier3D.xml6
-rw-r--r--doc/classes/XRHandTracker.xml19
-rw-r--r--doc/classes/XRNode3D.xml3
-rw-r--r--doc/classes/XRPositionalTracker.xml22
-rw-r--r--doc/classes/XRServer.xml163
-rw-r--r--doc/classes/XRTracker.xml30
-rw-r--r--drivers/unix/file_access_unix.cpp17
-rw-r--r--drivers/unix/file_access_unix.h1
-rw-r--r--drivers/unix/file_access_unix_pipe.h1
-rw-r--r--drivers/unix/os_unix.cpp6
-rw-r--r--drivers/unix/os_unix.h2
-rw-r--r--drivers/windows/file_access_windows.cpp19
-rw-r--r--drivers/windows/file_access_windows.h1
-rw-r--r--drivers/windows/file_access_windows_pipe.h1
-rw-r--r--editor/editor_dock_manager.cpp1
-rw-r--r--editor/editor_inspector.cpp7
-rw-r--r--editor/editor_paths.cpp4
-rw-r--r--editor/editor_paths.h1
-rw-r--r--editor/export/editor_export.cpp7
-rw-r--r--editor/export/editor_export_platform.h2
-rw-r--r--editor/export/editor_export_plugin.cpp4
-rw-r--r--editor/export/editor_export_plugin.h3
-rw-r--r--editor/icons/XRFaceModifier3D.svg1
-rw-r--r--editor/icons/XRNode3D.svg1
-rw-r--r--editor/import/3d/post_import_plugin_skeleton_rest_fixer.cpp6
-rw-r--r--editor/import/3d/resource_importer_obj.cpp2
-rw-r--r--editor/plugins/gdextension_export_plugin.h31
-rw-r--r--editor/plugins/sprite_2d_editor_plugin.cpp63
-rw-r--r--methods.py6
-rw-r--r--misc/extension_api_validation/4.2-stable.expected21
-rw-r--r--modules/cvtt/image_compress_cvtt.cpp4
-rw-r--r--modules/mono/editor/bindings_generator.cpp26
-rw-r--r--modules/mono/editor/bindings_generator.h16
-rw-r--r--modules/openxr/doc_classes/OpenXRInterface.xml14
-rw-r--r--modules/openxr/extensions/openxr_hand_tracking_extension.cpp33
-rw-r--r--modules/openxr/extensions/platform/openxr_android_extension.cpp22
-rw-r--r--modules/openxr/extensions/platform/openxr_android_extension.h4
-rw-r--r--modules/openxr/openxr_interface.cpp69
-rw-r--r--modules/openxr/openxr_interface.h4
-rw-r--r--modules/webxr/doc_classes/WebXRInterface.xml4
-rw-r--r--modules/webxr/webxr_interface.compat.inc41
-rw-r--r--modules/webxr/webxr_interface.cpp1
-rw-r--r--modules/webxr/webxr_interface.h9
-rw-r--r--modules/webxr/webxr_interface_js.cpp14
-rw-r--r--modules/webxr/webxr_interface_js.h6
-rw-r--r--platform/android/api/java_class_wrapper.h3
-rw-r--r--platform/android/api/jni_singleton.h11
-rw-r--r--platform/android/dir_access_jandroid.cpp8
-rw-r--r--platform/android/dir_access_jandroid.h1
-rw-r--r--platform/android/export/export.cpp7
-rw-r--r--platform/android/export/export_plugin.cpp86
-rw-r--r--platform/android/export/export_plugin.h7
-rw-r--r--platform/android/file_access_android.cpp17
-rw-r--r--platform/android/file_access_android.h11
-rw-r--r--platform/android/file_access_filesystem_jandroid.cpp31
-rw-r--r--platform/android/file_access_filesystem_jandroid.h3
-rw-r--r--platform/android/java/app/build.gradle30
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/io/file/DataAccess.kt22
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/io/file/FileAccessHandler.kt9
-rw-r--r--platform/android/java_class_wrapper.cpp18
-rw-r--r--platform/android/java_godot_io_wrapper.cpp11
-rw-r--r--platform/android/java_godot_lib_jni.cpp12
-rw-r--r--platform/android/java_godot_view_wrapper.cpp1
-rw-r--r--platform/android/java_godot_wrapper.cpp20
-rw-r--r--platform/android/net_socket_android.cpp8
-rw-r--r--platform/android/net_socket_android.h1
-rw-r--r--platform/android/os_android.cpp76
-rw-r--r--platform/android/os_android.h4
-rw-r--r--platform/android/tts_android.cpp10
-rw-r--r--platform/android/tts_android.h1
-rw-r--r--platform/ios/os_ios.h2
-rw-r--r--platform/ios/os_ios.mm10
-rw-r--r--platform/macos/os_macos.h2
-rw-r--r--platform/macos/os_macos.mm6
-rw-r--r--platform/web/os_web.cpp6
-rw-r--r--platform/web/os_web.h2
-rw-r--r--platform/windows/os_windows.cpp16
-rw-r--r--platform/windows/os_windows.h2
-rw-r--r--scene/2d/tile_map_layer.cpp9
-rw-r--r--scene/3d/mesh_instance_3d.cpp159
-rw-r--r--scene/3d/mesh_instance_3d.h2
-rw-r--r--scene/3d/xr_body_modifier_3d.cpp45
-rw-r--r--scene/3d/xr_body_modifier_3d.h6
-rw-r--r--scene/3d/xr_face_modifier_3d.cpp8
-rw-r--r--scene/3d/xr_face_modifier_3d.h2
-rw-r--r--scene/3d/xr_hand_modifier_3d.cpp109
-rw-r--r--scene/3d/xr_hand_modifier_3d.h4
-rw-r--r--scene/3d/xr_nodes.cpp37
-rw-r--r--scene/3d/xr_nodes.h4
-rw-r--r--scene/gui/color_picker.cpp3
-rw-r--r--scene/gui/rich_text_label.cpp136
-rw-r--r--scene/gui/rich_text_label.h2
-rw-r--r--scene/gui/tab_container.cpp4
-rw-r--r--scene/gui/text_edit.cpp2
-rw-r--r--scene/gui/tree.cpp181
-rw-r--r--scene/gui/tree.h2
-rw-r--r--scene/register_scene_types.cpp2
-rw-r--r--scene/resources/2d/navigation_mesh_source_geometry_data_2d.cpp21
-rw-r--r--scene/resources/2d/navigation_mesh_source_geometry_data_2d.h3
-rw-r--r--scene/resources/3d/importer_mesh.cpp2
-rw-r--r--scene/resources/3d/navigation_mesh_source_geometry_data_3d.cpp26
-rw-r--r--scene/resources/3d/navigation_mesh_source_geometry_data_3d.h2
-rw-r--r--scene/resources/mesh.cpp2
-rw-r--r--scene/resources/surface_tool.cpp13
-rw-r--r--scene/resources/surface_tool.h3
-rw-r--r--scene/resources/syntax_highlighter.cpp2
-rw-r--r--scene/resources/visual_shader.cpp4
-rw-r--r--servers/register_server_types.cpp3
-rw-r--r--servers/xr/xr_body_tracker.cpp12
-rw-r--r--servers/xr/xr_body_tracker.h11
-rw-r--r--servers/xr/xr_controller_tracker.cpp39
-rw-r--r--servers/xr/xr_controller_tracker.h52
-rw-r--r--servers/xr/xr_face_tracker.cpp8
-rw-r--r--servers/xr/xr_face_tracker.h10
-rw-r--r--servers/xr/xr_hand_tracker.cpp50
-rw-r--r--servers/xr/xr_hand_tracker.h11
-rw-r--r--servers/xr/xr_positional_tracker.cpp73
-rw-r--r--servers/xr/xr_positional_tracker.h28
-rw-r--r--servers/xr/xr_tracker.cpp70
-rw-r--r--servers/xr/xr_tracker.h61
-rw-r--r--servers/xr_server.compat.inc51
-rw-r--r--servers/xr_server.cpp163
-rw-r--r--servers/xr_server.h49
-rw-r--r--tests/core/math/test_geometry_2d.h8
-rw-r--r--thirdparty/misc/clipper.cpp4661
-rw-r--r--thirdparty/misc/clipper.hpp406
-rw-r--r--thirdparty/misc/patches/clipper-exceptions.patch154
164 files changed, 1973 insertions, 6326 deletions
diff --git a/COPYRIGHT.txt b/COPYRIGHT.txt
index 7f4a3b4b9d..c1711a9e81 100644
--- a/COPYRIGHT.txt
+++ b/COPYRIGHT.txt
@@ -360,12 +360,6 @@ Copyright: 1998-2010, Gilles Vollant
2009-2010, Mathias Svensson
License: Zlib
-Files: ./thirdparty/misc/clipper.cpp
- ./thirdparty/misc/clipper.hpp
-Comment: Clipper
-Copyright: 2010-2017, Angus Johnson
-License: BSL-1.0
-
Files: ./thirdparty/misc/cubemap_coeffs.h
Comment: Fast Filtering of Reflection Probes
Copyright: 2016, Activision Publishing, Inc.
diff --git a/SConstruct b/SConstruct
index dbcd9dfd68..260e6bb48a 100644
--- a/SConstruct
+++ b/SConstruct
@@ -168,8 +168,7 @@ if profile:
opts = Variables(customs, ARGUMENTS)
# Target build options
-opts.Add("platform", "Target platform (%s)" % ("|".join(platform_list),), "")
-opts.Add("p", "Platform (alias for 'platform')", "")
+opts.Add(["platform", "p"], "Target platform (%s)" % "|".join(platform_list), "")
opts.Add(EnumVariable("target", "Compilation target", "editor", ("editor", "template_release", "template_debug")))
opts.Add(EnumVariable("arch", "CPU architecture", "auto", ["auto"] + architectures, architecture_aliases))
opts.Add(BoolVariable("dev_build", "Developer build with dev-only debugging code (DEV_ENABLED)", False))
@@ -285,13 +284,9 @@ if env["import_env_vars"]:
# Platform selection: validate input, and add options.
-selected_platform = ""
+selected_platform = env["platform"]
-if env["platform"] != "":
- selected_platform = env["platform"]
-elif env["p"] != "":
- selected_platform = env["p"]
-else:
+if selected_platform == "":
# Missing `platform` argument, try to detect platform automatically
if (
sys.platform.startswith("linux")
diff --git a/core/SCsub b/core/SCsub
index f7b733a221..ec4658e8ca 100644
--- a/core/SCsub
+++ b/core/SCsub
@@ -61,7 +61,6 @@ thirdparty_misc_sources = [
# C++ sources
"pcg.cpp",
"polypartition.cpp",
- "clipper.cpp",
"smolv.cpp",
]
thirdparty_misc_sources = [thirdparty_misc_dir + file for file in thirdparty_misc_sources]
diff --git a/core/core_bind.cpp b/core/core_bind.cpp
index 03c31bee28..364b016ddd 100644
--- a/core/core_bind.cpp
+++ b/core/core_bind.cpp
@@ -152,12 +152,10 @@ void ResourceLoader::_bind_methods() {
////// ResourceSaver //////
Error ResourceSaver::save(const Ref<Resource> &p_resource, const String &p_path, BitField<SaverFlags> p_flags) {
- ERR_FAIL_COND_V_MSG(p_resource.is_null(), ERR_INVALID_PARAMETER, "Can't save empty resource to path '" + p_path + "'.");
return ::ResourceSaver::save(p_resource, p_path, p_flags);
}
Vector<String> ResourceSaver::get_recognized_extensions(const Ref<Resource> &p_resource) {
- ERR_FAIL_COND_V_MSG(p_resource.is_null(), Vector<String>(), "It's not a reference to a valid Resource object.");
List<String> exts;
::ResourceSaver::get_recognized_extensions(p_resource, &exts);
Vector<String> ret;
@@ -1533,6 +1531,10 @@ StringName ClassDB::class_get_integer_constant_enum(const StringName &p_class, c
return ::ClassDB::get_integer_constant_enum(p_class, p_name, p_no_inheritance);
}
+bool ClassDB::is_class_enum_bitfield(const StringName &p_class, const StringName &p_enum, bool p_no_inheritance) const {
+ return ::ClassDB::is_enum_bitfield(p_class, p_enum, p_no_inheritance);
+}
+
bool ClassDB::is_class_enabled(const StringName &p_class) const {
return ::ClassDB::is_class_enabled(p_class);
}
@@ -1549,7 +1551,7 @@ void ClassDB::get_argument_options(const StringName &p_function, int p_idx, List
pf == "class_has_method" || pf == "class_get_method_list" ||
pf == "class_get_integer_constant_list" || pf == "class_has_integer_constant" || pf == "class_get_integer_constant" ||
pf == "class_has_enum" || pf == "class_get_enum_list" || pf == "class_get_enum_constants" || pf == "class_get_integer_constant_enum" ||
- pf == "is_class_enabled");
+ pf == "is_class_enabled" || pf == "is_class_enum_bitfield");
}
if (first_argument_is_class || pf == "is_parent_class") {
for (const String &E : get_class_list()) {
@@ -1594,6 +1596,8 @@ void ClassDB::_bind_methods() {
::ClassDB::bind_method(D_METHOD("class_get_enum_constants", "class", "enum", "no_inheritance"), &ClassDB::class_get_enum_constants, DEFVAL(false));
::ClassDB::bind_method(D_METHOD("class_get_integer_constant_enum", "class", "name", "no_inheritance"), &ClassDB::class_get_integer_constant_enum, DEFVAL(false));
+ ::ClassDB::bind_method(D_METHOD("is_class_enum_bitfield", "class", "enum", "no_inheritance"), &ClassDB::is_class_enum_bitfield, DEFVAL(false));
+
::ClassDB::bind_method(D_METHOD("is_class_enabled", "class"), &ClassDB::is_class_enabled);
}
diff --git a/core/core_bind.h b/core/core_bind.h
index 3c0cdc25ce..73c817f43c 100644
--- a/core/core_bind.h
+++ b/core/core_bind.h
@@ -462,6 +462,8 @@ public:
PackedStringArray class_get_enum_constants(const StringName &p_class, const StringName &p_enum, bool p_no_inheritance = false) const;
StringName class_get_integer_constant_enum(const StringName &p_class, const StringName &p_name, bool p_no_inheritance = false) const;
+ bool is_class_enum_bitfield(const StringName &p_class, const StringName &p_enum, bool p_no_inheritance = false) const;
+
bool is_class_enabled(const StringName &p_class) const;
#ifdef TOOLS_ENABLED
diff --git a/core/extension/gdextension.cpp b/core/extension/gdextension.cpp
index 546c40e9cb..abf4adbc0d 100644
--- a/core/extension/gdextension.cpp
+++ b/core/extension/gdextension.cpp
@@ -46,6 +46,41 @@ String GDExtension::get_extension_list_config_file() {
return ProjectSettings::get_singleton()->get_project_data_path().path_join("extension_list.cfg");
}
+Vector<SharedObject> GDExtension::find_extension_dependencies(const String &p_path, Ref<ConfigFile> p_config, std::function<bool(String)> p_has_feature) {
+ Vector<SharedObject> dependencies_shared_objects;
+ if (p_config->has_section("dependencies")) {
+ List<String> config_dependencies;
+ p_config->get_section_keys("dependencies", &config_dependencies);
+
+ for (const String &dependency : config_dependencies) {
+ Vector<String> dependency_tags = dependency.split(".");
+ bool all_tags_met = true;
+ for (int i = 0; i < dependency_tags.size(); i++) {
+ String tag = dependency_tags[i].strip_edges();
+ if (!p_has_feature(tag)) {
+ all_tags_met = false;
+ break;
+ }
+ }
+
+ if (all_tags_met) {
+ Dictionary dependency_value = p_config->get_value("dependencies", dependency);
+ for (const Variant *key = dependency_value.next(nullptr); key; key = dependency_value.next(key)) {
+ String dependency_path = *key;
+ String target_path = dependency_value[*key];
+ if (dependency_path.is_relative_path()) {
+ dependency_path = p_path.get_base_dir().path_join(dependency_path);
+ }
+ dependencies_shared_objects.push_back(SharedObject(dependency_path, dependency_tags, target_path));
+ }
+ break;
+ }
+ }
+ }
+
+ return dependencies_shared_objects;
+}
+
String GDExtension::find_extension_library(const String &p_path, Ref<ConfigFile> p_config, std::function<bool(String)> p_has_feature, PackedStringArray *r_tags) {
// First, check the explicit libraries.
if (p_config->has_section("libraries")) {
@@ -727,10 +762,24 @@ GDExtensionInterfaceFunctionPtr GDExtension::get_interface_function(const String
return *function;
}
-Error GDExtension::open_library(const String &p_path, const String &p_entry_symbol) {
+Error GDExtension::open_library(const String &p_path, const String &p_entry_symbol, Vector<SharedObject> *p_dependencies) {
String abs_path = ProjectSettings::get_singleton()->globalize_path(p_path);
+
+ Vector<String> abs_dependencies_paths;
+ if (p_dependencies != nullptr && !p_dependencies->is_empty()) {
+ for (const SharedObject &dependency : *p_dependencies) {
+ abs_dependencies_paths.push_back(ProjectSettings::get_singleton()->globalize_path(dependency.path));
+ }
+ }
+
String actual_lib_path;
- Error err = OS::get_singleton()->open_dynamic_library(abs_path, library, true, &actual_lib_path, Engine::get_singleton()->is_editor_hint());
+ OS::GDExtensionData data = {
+ true, // also_set_library_path
+ &actual_lib_path, // r_resolved_path
+ Engine::get_singleton()->is_editor_hint(), // generate_temp_files
+ &abs_dependencies_paths, // library_dependencies
+ };
+ Error err = OS::get_singleton()->open_dynamic_library(abs_path, library, &data);
if (actual_lib_path.get_file() != abs_path.get_file()) {
// If temporary files are generated, let's change the library path to point at the original,
@@ -970,7 +1019,8 @@ Error GDExtensionResourceLoader::load_gdextension_resource(const String &p_path,
FileAccess::get_modified_time(library_path));
#endif
- err = p_extension->open_library(is_static_library ? String() : library_path, entry_symbol);
+ Vector<SharedObject> library_dependencies = GDExtension::find_extension_dependencies(p_path, config, [](const String &p_feature) { return OS::get_singleton()->has_feature(p_feature); });
+ err = p_extension->open_library(is_static_library ? String() : library_path, entry_symbol, &library_dependencies);
if (err != OK) {
// Unreference the extension so that this loading can be considered a failure.
p_extension.unref();
diff --git a/core/extension/gdextension.h b/core/extension/gdextension.h
index 1f48edecf7..23b1f51208 100644
--- a/core/extension/gdextension.h
+++ b/core/extension/gdextension.h
@@ -37,6 +37,7 @@
#include "core/io/config_file.h"
#include "core/io/resource_loader.h"
#include "core/object/ref_counted.h"
+#include "core/os/shared_object.h"
class GDExtensionMethodBind;
@@ -123,8 +124,9 @@ public:
static String get_extension_list_config_file();
static String find_extension_library(const String &p_path, Ref<ConfigFile> p_config, std::function<bool(String)> p_has_feature, PackedStringArray *r_tags = nullptr);
+ static Vector<SharedObject> find_extension_dependencies(const String &p_path, Ref<ConfigFile> p_config, std::function<bool(String)> p_has_feature);
- Error open_library(const String &p_path, const String &p_entry_symbol);
+ Error open_library(const String &p_path, const String &p_entry_symbol, Vector<SharedObject> *p_dependencies = nullptr);
void close_library();
enum InitializationLevel {
diff --git a/core/io/file_access.cpp b/core/io/file_access.cpp
index d2a5103953..1cf388b33a 100644
--- a/core/io/file_access.cpp
+++ b/core/io/file_access.cpp
@@ -867,6 +867,7 @@ void FileAccess::_bind_methods() {
ClassDB::bind_static_method("FileAccess", D_METHOD("get_file_as_bytes", "path"), &FileAccess::_get_file_as_bytes);
ClassDB::bind_static_method("FileAccess", D_METHOD("get_file_as_string", "path"), &FileAccess::_get_file_as_string);
+ ClassDB::bind_method(D_METHOD("resize", "length"), &FileAccess::resize);
ClassDB::bind_method(D_METHOD("flush"), &FileAccess::flush);
ClassDB::bind_method(D_METHOD("get_path"), &FileAccess::get_path);
ClassDB::bind_method(D_METHOD("get_path_absolute"), &FileAccess::get_path_absolute);
diff --git a/core/io/file_access.h b/core/io/file_access.h
index 0b631f68b1..2ab84db4b6 100644
--- a/core/io/file_access.h
+++ b/core/io/file_access.h
@@ -166,6 +166,7 @@ public:
virtual Error get_error() const = 0; ///< get last error
+ virtual Error resize(int64_t p_length) = 0;
virtual void flush() = 0;
virtual void store_8(uint8_t p_dest) = 0; ///< store a byte
virtual void store_16(uint16_t p_dest); ///< store 16 bits uint
diff --git a/core/io/file_access_compressed.h b/core/io/file_access_compressed.h
index bf57eaa07c..f706c82f8e 100644
--- a/core/io/file_access_compressed.h
+++ b/core/io/file_access_compressed.h
@@ -88,6 +88,7 @@ public:
virtual Error get_error() const override; ///< get last error
+ virtual Error resize(int64_t p_length) override { return ERR_UNAVAILABLE; }
virtual void flush() override;
virtual void store_8(uint8_t p_dest) override; ///< store a byte
diff --git a/core/io/file_access_encrypted.h b/core/io/file_access_encrypted.h
index 489d213b8f..42afe49a5e 100644
--- a/core/io/file_access_encrypted.h
+++ b/core/io/file_access_encrypted.h
@@ -78,6 +78,7 @@ public:
virtual Error get_error() const override; ///< get last error
+ virtual Error resize(int64_t p_length) override { return ERR_UNAVAILABLE; }
virtual void flush() override;
virtual void store_8(uint8_t p_dest) override; ///< store a byte
virtual void store_buffer(const uint8_t *p_src, uint64_t p_length) override; ///< store an array of bytes
diff --git a/core/io/file_access_memory.h b/core/io/file_access_memory.h
index d9efb354c3..e9fbc26d75 100644
--- a/core/io/file_access_memory.h
+++ b/core/io/file_access_memory.h
@@ -61,6 +61,7 @@ public:
virtual Error get_error() const override; ///< get last error
+ virtual Error resize(int64_t p_length) override { return ERR_UNAVAILABLE; }
virtual void flush() override;
virtual void store_8(uint8_t p_byte) override; ///< store a byte
virtual void store_buffer(const uint8_t *p_src, uint64_t p_length) override; ///< store an array of bytes
diff --git a/core/io/file_access_pack.h b/core/io/file_access_pack.h
index 0deacfebab..594ac8f089 100644
--- a/core/io/file_access_pack.h
+++ b/core/io/file_access_pack.h
@@ -177,6 +177,7 @@ public:
virtual Error get_error() const override;
+ virtual Error resize(int64_t p_length) override { return ERR_UNAVAILABLE; }
virtual void flush() override;
virtual void store_8(uint8_t p_dest) override;
diff --git a/core/io/file_access_zip.h b/core/io/file_access_zip.h
index e9ea74e01d..88b63e93e2 100644
--- a/core/io/file_access_zip.h
+++ b/core/io/file_access_zip.h
@@ -100,6 +100,7 @@ public:
virtual Error get_error() const override; ///< get last error
+ virtual Error resize(int64_t p_length) override { return ERR_UNAVAILABLE; }
virtual void flush() override;
virtual void store_8(uint8_t p_dest) override; ///< store a byte
diff --git a/core/io/json.cpp b/core/io/json.cpp
index 5a1fe45f70..61051727c1 100644
--- a/core/io/json.cpp
+++ b/core/io/json.cpp
@@ -344,10 +344,10 @@ Error JSON::_get_token(const char32_t *p_str, int &index, int p_len, Token &r_to
r_token.value = number;
return OK;
- } else if (is_ascii_char(p_str[index])) {
+ } else if (is_ascii_alphabet_char(p_str[index])) {
String id;
- while (is_ascii_char(p_str[index])) {
+ while (is_ascii_alphabet_char(p_str[index])) {
id += p_str[index];
index++;
}
diff --git a/core/io/resource_saver.cpp b/core/io/resource_saver.cpp
index e4022b2073..1dc1245355 100644
--- a/core/io/resource_saver.cpp
+++ b/core/io/resource_saver.cpp
@@ -98,6 +98,7 @@ void ResourceFormatSaver::_bind_methods() {
}
Error ResourceSaver::save(const Ref<Resource> &p_resource, const String &p_path, uint32_t p_flags) {
+ ERR_FAIL_COND_V_MSG(p_resource.is_null(), ERR_INVALID_PARAMETER, "Can't save empty resource to path '" + p_path + "'.");
String path = p_path;
if (path.is_empty()) {
path = p_resource->get_path();
@@ -174,6 +175,7 @@ void ResourceSaver::set_save_callback(ResourceSavedCallback p_callback) {
}
void ResourceSaver::get_recognized_extensions(const Ref<Resource> &p_resource, List<String> *p_extensions) {
+ ERR_FAIL_COND_MSG(p_resource.is_null(), "It's not a reference to a valid Resource object.");
for (int i = 0; i < saver_count; i++) {
saver[i]->get_recognized_extensions(p_resource, p_extensions);
}
diff --git a/core/math/basis.cpp b/core/math/basis.cpp
index 84ac878172..34ed1c2d85 100644
--- a/core/math/basis.cpp
+++ b/core/math/basis.cpp
@@ -293,7 +293,7 @@ Vector3 Basis::get_scale_abs() const {
Vector3(rows[0][2], rows[1][2], rows[2][2]).length());
}
-Vector3 Basis::get_scale_local() const {
+Vector3 Basis::get_scale_global() const {
real_t det_sign = SIGN(determinant());
return det_sign * Vector3(rows[0].length(), rows[1].length(), rows[2].length());
}
diff --git a/core/math/basis.h b/core/math/basis.h
index 79f3bda8f8..918cbc18d4 100644
--- a/core/math/basis.h
+++ b/core/math/basis.h
@@ -103,7 +103,7 @@ struct _NO_DISCARD_ Basis {
Vector3 get_scale() const;
Vector3 get_scale_abs() const;
- Vector3 get_scale_local() const;
+ Vector3 get_scale_global() const;
void set_axis_angle_scale(const Vector3 &p_axis, real_t p_angle, const Vector3 &p_scale);
void set_euler_scale(const Vector3 &p_euler, const Vector3 &p_scale, EulerOrder p_order = EulerOrder::YXZ);
diff --git a/core/math/delaunay_3d.h b/core/math/delaunay_3d.h
index 45571fb570..25bd4e8d89 100644
--- a/core/math/delaunay_3d.h
+++ b/core/math/delaunay_3d.h
@@ -46,7 +46,8 @@ class Delaunay3D {
struct Simplex;
enum {
- ACCEL_GRID_SIZE = 16
+ ACCEL_GRID_SIZE = 16,
+ QUANTIZATION_MAX = 1 << 16 // A power of two smaller than the 23 bit significand of a float.
};
struct GridPos {
Vector3i pos;
@@ -173,38 +174,25 @@ class Delaunay3D {
R128 radius2 = rel2_x * rel2_x + rel2_y * rel2_y + rel2_z * rel2_z;
- return radius2 < (p_simplex.circum_r2 - R128(0.00001));
+ return radius2 < (p_simplex.circum_r2 - R128(0.0000000001));
+ // When this tolerance is too big, it can result in overlapping simplices.
+ // When it's too small, large amounts of planar simplices are created.
}
static bool simplex_is_coplanar(const Vector3 *p_points, const Simplex &p_simplex) {
- Plane p(p_points[p_simplex.points[0]], p_points[p_simplex.points[1]], p_points[p_simplex.points[2]]);
- if (ABS(p.distance_to(p_points[p_simplex.points[3]])) < CMP_EPSILON) {
- return true;
+ // Checking every possible distance like this is overkill, but only checking
+ // one is not enough. If the simplex is almost planar then the vectors p1-p2
+ // and p1-p3 can be practically collinear, which makes Plane unreliable.
+ for (uint32_t i = 0; i < 4; i++) {
+ Plane p(p_points[p_simplex.points[i]], p_points[p_simplex.points[(i + 1) % 4]], p_points[p_simplex.points[(i + 2) % 4]]);
+ // This tolerance should not be smaller than the one used with
+ // Plane::has_point() when creating the LightmapGI probe BSP tree.
+ if (ABS(p.distance_to(p_points[p_simplex.points[(i + 3) % 4]])) < 0.001) {
+ return true;
+ }
}
- Projection cm;
-
- cm.columns[0][0] = p_points[p_simplex.points[0]].x;
- cm.columns[0][1] = p_points[p_simplex.points[1]].x;
- cm.columns[0][2] = p_points[p_simplex.points[2]].x;
- cm.columns[0][3] = p_points[p_simplex.points[3]].x;
-
- cm.columns[1][0] = p_points[p_simplex.points[0]].y;
- cm.columns[1][1] = p_points[p_simplex.points[1]].y;
- cm.columns[1][2] = p_points[p_simplex.points[2]].y;
- cm.columns[1][3] = p_points[p_simplex.points[3]].y;
-
- cm.columns[2][0] = p_points[p_simplex.points[0]].z;
- cm.columns[2][1] = p_points[p_simplex.points[1]].z;
- cm.columns[2][2] = p_points[p_simplex.points[2]].z;
- cm.columns[2][3] = p_points[p_simplex.points[3]].z;
-
- cm.columns[3][0] = 1.0;
- cm.columns[3][1] = 1.0;
- cm.columns[3][2] = 1.0;
- cm.columns[3][3] = 1.0;
-
- return ABS(cm.determinant()) <= CMP_EPSILON;
+ return false;
}
public:
@@ -215,9 +203,10 @@ public:
static Vector<OutputSimplex> tetrahedralize(const Vector<Vector3> &p_points) {
uint32_t point_count = p_points.size();
Vector3 *points = (Vector3 *)memalloc(sizeof(Vector3) * (point_count + 4));
+ const Vector3 *src_points = p_points.ptr();
+ Vector3 proportions;
{
- const Vector3 *src_points = p_points.ptr();
AABB rect;
for (uint32_t i = 0; i < point_count; i++) {
Vector3 point = src_points[i];
@@ -226,17 +215,25 @@ public:
} else {
rect.expand_to(point);
}
- points[i] = point;
}
+ real_t longest_axis = rect.size[rect.get_longest_axis_index()];
+ proportions = Vector3(longest_axis, longest_axis, longest_axis) / rect.size;
+
for (uint32_t i = 0; i < point_count; i++) {
- points[i] = (points[i] - rect.position) / rect.size;
+ // Scale points to the unit cube to better utilize R128 precision
+ // and quantize to stabilize triangulation over a wide range of
+ // distances.
+ points[i] = Vector3(Vector3i((src_points[i] - rect.position) / longest_axis * QUANTIZATION_MAX)) / QUANTIZATION_MAX;
}
- const real_t delta_max = Math::sqrt(2.0) * 20.0;
+ const real_t delta_max = Math::sqrt(2.0) * 100.0;
Vector3 center = Vector3(0.5, 0.5, 0.5);
- // any simplex that contains everything is good
+ // The larger the root simplex is, the more likely it is that the
+ // triangulation is convex. If it's not absolutely huge, there can
+ // be missing simplices that are not created for the outermost faces
+ // of the point cloud if the point density is very low there.
points[point_count + 0] = center + Vector3(0, 1, 0) * delta_max;
points[point_count + 1] = center + Vector3(0, -1, 1) * delta_max;
points[point_count + 2] = center + Vector3(1, -1, -1) * delta_max;
@@ -271,7 +268,7 @@ public:
for (uint32_t i = 0; i < point_count; i++) {
bool unique = true;
for (uint32_t j = i + 1; j < point_count; j++) {
- if (points[i].is_equal_approx(points[j])) {
+ if (points[i] == points[j]) {
unique = false;
break;
}
@@ -280,7 +277,7 @@ public:
continue;
}
- Vector3i grid_pos = Vector3i(points[i] * ACCEL_GRID_SIZE);
+ Vector3i grid_pos = Vector3i(points[i] * proportions * ACCEL_GRID_SIZE);
grid_pos = grid_pos.clamp(Vector3i(), Vector3i(ACCEL_GRID_SIZE - 1, ACCEL_GRID_SIZE - 1, ACCEL_GRID_SIZE - 1));
for (List<Simplex *>::Element *E = acceleration_grid[grid_pos.x][grid_pos.y][grid_pos.z].front(); E;) {
@@ -300,6 +297,9 @@ public:
Triangle t = Triangle(simplex->points[triangle_order[k][0]], simplex->points[triangle_order[k][1]], simplex->points[triangle_order[k][2]]);
uint32_t *p = triangles_inserted.lookup_ptr(t);
if (p) {
+ // This Delaunay implementation uses the Bowyer-Watson algorithm.
+ // The rule is that you don't reuse any triangles that were
+ // shared by any of the retriangulated simplices.
triangles[*p].bad = true;
} else {
triangles_inserted.insert(t, triangles.size());
@@ -307,7 +307,6 @@ public:
}
}
- //remove simplex and continue
simplex_list.erase(simplex->SE);
for (const GridPos &gp : simplex->grid_positions) {
@@ -334,8 +333,8 @@ public:
const real_t radius2 = Math::sqrt(double(new_simplex->circum_r2)) + 0.0001;
Vector3 extents = Vector3(radius2, radius2, radius2);
- Vector3i from = Vector3i((center - extents) * ACCEL_GRID_SIZE);
- Vector3i to = Vector3i((center + extents) * ACCEL_GRID_SIZE);
+ Vector3i from = Vector3i((center - extents) * proportions * ACCEL_GRID_SIZE);
+ Vector3i to = Vector3i((center + extents) * proportions * ACCEL_GRID_SIZE);
from = from.clamp(Vector3i(), Vector3i(ACCEL_GRID_SIZE - 1, ACCEL_GRID_SIZE - 1, ACCEL_GRID_SIZE - 1));
to = to.clamp(Vector3i(), Vector3i(ACCEL_GRID_SIZE - 1, ACCEL_GRID_SIZE - 1, ACCEL_GRID_SIZE - 1));
@@ -370,7 +369,7 @@ public:
break;
}
}
- if (invalid || simplex_is_coplanar(points, *simplex)) {
+ if (invalid || simplex_is_coplanar(src_points, *simplex)) {
memdelete(simplex);
continue;
}
diff --git a/core/math/geometry_2d.cpp b/core/math/geometry_2d.cpp
index 602e95bc13..d60619b27f 100644
--- a/core/math/geometry_2d.cpp
+++ b/core/math/geometry_2d.cpp
@@ -30,12 +30,12 @@
#include "geometry_2d.h"
-#include "thirdparty/misc/clipper.hpp"
+#include "thirdparty/clipper2/include/clipper2/clipper.h"
#include "thirdparty/misc/polypartition.h"
#define STB_RECT_PACK_IMPLEMENTATION
#include "thirdparty/misc/stb_rect_pack.h"
-#define SCALE_FACTOR 100000.0 // Based on CMP_EPSILON.
+#define PRECISION 5 // Based on CMP_EPSILON.
Vector<Vector<Vector2>> Geometry2D::decompose_polygon_in_convex(const Vector<Point2> &polygon) {
Vector<Vector<Vector2>> decomp;
@@ -196,58 +196,59 @@ void Geometry2D::make_atlas(const Vector<Size2i> &p_rects, Vector<Point2i> &r_re
}
Vector<Vector<Point2>> Geometry2D::_polypaths_do_operation(PolyBooleanOperation p_op, const Vector<Point2> &p_polypath_a, const Vector<Point2> &p_polypath_b, bool is_a_open) {
- using namespace ClipperLib;
+ using namespace Clipper2Lib;
- ClipType op = ctUnion;
+ ClipType op = ClipType::Union;
switch (p_op) {
case OPERATION_UNION:
- op = ctUnion;
+ op = ClipType::Union;
break;
case OPERATION_DIFFERENCE:
- op = ctDifference;
+ op = ClipType::Difference;
break;
case OPERATION_INTERSECTION:
- op = ctIntersection;
+ op = ClipType::Intersection;
break;
case OPERATION_XOR:
- op = ctXor;
+ op = ClipType::Xor;
break;
}
- Path path_a, path_b;
- // Need to scale points (Clipper's requirement for robust computation).
+ PathD path_a(p_polypath_a.size());
for (int i = 0; i != p_polypath_a.size(); ++i) {
- path_a << IntPoint(p_polypath_a[i].x * (real_t)SCALE_FACTOR, p_polypath_a[i].y * (real_t)SCALE_FACTOR);
+ path_a[i] = PointD(p_polypath_a[i].x, p_polypath_a[i].y);
}
+ PathD path_b(p_polypath_b.size());
for (int i = 0; i != p_polypath_b.size(); ++i) {
- path_b << IntPoint(p_polypath_b[i].x * (real_t)SCALE_FACTOR, p_polypath_b[i].y * (real_t)SCALE_FACTOR);
+ path_b[i] = PointD(p_polypath_b[i].x, p_polypath_b[i].y);
}
- Clipper clp;
- clp.AddPath(path_a, ptSubject, !is_a_open); // Forward compatible with Clipper 10.0.0.
- clp.AddPath(path_b, ptClip, true); // Polylines cannot be set as clip.
- Paths paths;
+ ClipperD clp(PRECISION); // Scale points up internally to attain the desired precision.
+ clp.PreserveCollinear(false); // Remove redundant vertices.
+ if (is_a_open) {
+ clp.AddOpenSubject({ path_a });
+ } else {
+ clp.AddSubject({ path_a });
+ }
+ clp.AddClip({ path_b });
+
+ PathsD paths;
if (is_a_open) {
- PolyTree tree; // Needed to populate polylines.
- clp.Execute(op, tree);
- OpenPathsFromPolyTree(tree, paths);
+ PolyTreeD tree; // Needed to populate polylines.
+ clp.Execute(op, FillRule::EvenOdd, tree, paths);
} else {
- clp.Execute(op, paths); // Works on closed polygons only.
+ clp.Execute(op, FillRule::EvenOdd, paths); // Works on closed polygons only.
}
- // Have to scale points down now.
+
Vector<Vector<Point2>> polypaths;
+ for (PathsD::size_type i = 0; i < paths.size(); ++i) {
+ const PathD &path = paths[i];
- for (Paths::size_type i = 0; i < paths.size(); ++i) {
Vector<Vector2> polypath;
-
- const Path &scaled_path = paths[i];
-
- for (Paths::size_type j = 0; j < scaled_path.size(); ++j) {
- polypath.push_back(Point2(
- static_cast<real_t>(scaled_path[j].X) / (real_t)SCALE_FACTOR,
- static_cast<real_t>(scaled_path[j].Y) / (real_t)SCALE_FACTOR));
+ for (PathsD::size_type j = 0; j < path.size(); ++j) {
+ polypath.push_back(Point2(static_cast<real_t>(path[j].x), static_cast<real_t>(path[j].y)));
}
polypaths.push_back(polypath);
}
@@ -255,67 +256,61 @@ Vector<Vector<Point2>> Geometry2D::_polypaths_do_operation(PolyBooleanOperation
}
Vector<Vector<Point2>> Geometry2D::_polypath_offset(const Vector<Point2> &p_polypath, real_t p_delta, PolyJoinType p_join_type, PolyEndType p_end_type) {
- using namespace ClipperLib;
+ using namespace Clipper2Lib;
- JoinType jt = jtSquare;
+ JoinType jt = JoinType::Square;
switch (p_join_type) {
case JOIN_SQUARE:
- jt = jtSquare;
+ jt = JoinType::Square;
break;
case JOIN_ROUND:
- jt = jtRound;
+ jt = JoinType::Round;
break;
case JOIN_MITER:
- jt = jtMiter;
+ jt = JoinType::Miter;
break;
}
- EndType et = etClosedPolygon;
+ EndType et = EndType::Polygon;
switch (p_end_type) {
case END_POLYGON:
- et = etClosedPolygon;
+ et = EndType::Polygon;
break;
case END_JOINED:
- et = etClosedLine;
+ et = EndType::Joined;
break;
case END_BUTT:
- et = etOpenButt;
+ et = EndType::Butt;
break;
case END_SQUARE:
- et = etOpenSquare;
+ et = EndType::Square;
break;
case END_ROUND:
- et = etOpenRound;
+ et = EndType::Round;
break;
}
- ClipperOffset co(2.0, 0.25f * (real_t)SCALE_FACTOR); // Defaults from ClipperOffset.
- Path path;
- // Need to scale points (Clipper's requirement for robust computation).
+ PathD polypath(p_polypath.size());
for (int i = 0; i != p_polypath.size(); ++i) {
- path << IntPoint(p_polypath[i].x * (real_t)SCALE_FACTOR, p_polypath[i].y * (real_t)SCALE_FACTOR);
+ polypath[i] = PointD(p_polypath[i].x, p_polypath[i].y);
}
- co.AddPath(path, jt, et);
- Paths paths;
- co.Execute(paths, p_delta * (real_t)SCALE_FACTOR); // Inflate/deflate.
+ // Inflate/deflate.
+ PathsD paths = InflatePaths({ polypath }, p_delta, jt, et, 2.0, PRECISION, 0.0);
+ // Here the miter_limit = 2.0 and arc_tolerance = 0.0 are Clipper2 defaults,
+ // and the PRECISION is used to scale points up internally, to attain the desired precision.
- // Have to scale points down now.
Vector<Vector<Point2>> polypaths;
+ for (PathsD::size_type i = 0; i < paths.size(); ++i) {
+ const PathD &path = paths[i];
- for (Paths::size_type i = 0; i < paths.size(); ++i) {
- Vector<Vector2> polypath;
-
- const Path &scaled_path = paths[i];
-
- for (Paths::size_type j = 0; j < scaled_path.size(); ++j) {
- polypath.push_back(Point2(
- static_cast<real_t>(scaled_path[j].X) / (real_t)SCALE_FACTOR,
- static_cast<real_t>(scaled_path[j].Y) / (real_t)SCALE_FACTOR));
+ Vector<Vector2> polypath2;
+ for (PathsD::size_type j = 0; j < path.size(); ++j) {
+ polypath2.push_back(Point2(static_cast<real_t>(path[j].x), static_cast<real_t>(path[j].y)));
}
- polypaths.push_back(polypath);
+ polypaths.push_back(polypath2);
}
return polypaths;
}
diff --git a/core/os/os.h b/core/os/os.h
index 06be0e2b41..069a3876af 100644
--- a/core/os/os.h
+++ b/core/os/os.h
@@ -153,7 +153,14 @@ public:
virtual void alert(const String &p_alert, const String &p_title = "ALERT!");
- virtual Error open_dynamic_library(const String &p_path, void *&p_library_handle, bool p_also_set_library_path = false, String *r_resolved_path = nullptr, bool p_generate_temp_files = false) { return ERR_UNAVAILABLE; }
+ struct GDExtensionData {
+ bool also_set_library_path = false;
+ String *r_resolved_path = nullptr;
+ bool generate_temp_files = false;
+ PackedStringArray *library_dependencies = nullptr;
+ };
+
+ virtual Error open_dynamic_library(const String &p_path, void *&p_library_handle, GDExtensionData *p_data = nullptr) { return ERR_UNAVAILABLE; }
virtual Error close_dynamic_library(void *p_library_handle) { return ERR_UNAVAILABLE; }
virtual Error get_dynamic_library_symbol_handle(void *p_library_handle, const String &p_name, void *&p_symbol_handle, bool p_optional = false) { return ERR_UNAVAILABLE; }
diff --git a/editor/export/editor_export_shared_object.h b/core/os/shared_object.h
index 54e1314a42..88423bed13 100644
--- a/editor/export/editor_export_shared_object.h
+++ b/core/os/shared_object.h
@@ -1,5 +1,5 @@
/**************************************************************************/
-/* editor_export_shared_object.h */
+/* shared_object.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -28,8 +28,8 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
-#ifndef EDITOR_EXPORT_SHARED_OBJECT_H
-#define EDITOR_EXPORT_SHARED_OBJECT_H
+#ifndef SHARED_OBJECT_H
+#define SHARED_OBJECT_H
#include "core/string/ustring.h"
#include "core/templates/vector.h"
@@ -48,4 +48,4 @@ struct SharedObject {
SharedObject() {}
};
-#endif // EDITOR_EXPORT_SHARED_OBJECT_H
+#endif // SHARED_OBJECT_H
diff --git a/core/string/char_utils.h b/core/string/char_utils.h
index aa9bc198ca..fc2fbb95a1 100644
--- a/core/string/char_utils.h
+++ b/core/string/char_utils.h
@@ -92,7 +92,7 @@ static _FORCE_INLINE_ bool is_binary_digit(char32_t c) {
return (c == '0' || c == '1');
}
-static _FORCE_INLINE_ bool is_ascii_char(char32_t c) {
+static _FORCE_INLINE_ bool is_ascii_alphabet_char(char32_t c) {
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
}
diff --git a/core/string/translation.cpp b/core/string/translation.cpp
index 613edd11cd..344fe42fa0 100644
--- a/core/string/translation.cpp
+++ b/core/string/translation.cpp
@@ -993,7 +993,7 @@ String TranslationServer::add_padding(const String &p_message, int p_length) con
}
const char32_t *TranslationServer::get_accented_version(char32_t p_character) const {
- if (!is_ascii_char(p_character)) {
+ if (!is_ascii_alphabet_char(p_character)) {
return nullptr;
}
diff --git a/core/variant/variant_parser.cpp b/core/variant/variant_parser.cpp
index dcb94b16b1..06daaab6a2 100644
--- a/core/variant/variant_parser.cpp
+++ b/core/variant/variant_parser.cpp
@@ -489,11 +489,11 @@ Error VariantParser::get_token(Stream *p_stream, Token &r_token, int &line, Stri
r_token.value = num.as_int();
}
return OK;
- } else if (is_ascii_char(cchar) || is_underscore(cchar)) {
+ } else if (is_ascii_alphabet_char(cchar) || is_underscore(cchar)) {
StringBuffer<> id;
bool first = true;
- while (is_ascii_char(cchar) || is_underscore(cchar) || (!first && is_digit(cchar))) {
+ while (is_ascii_alphabet_char(cchar) || is_underscore(cchar) || (!first && is_digit(cchar))) {
id += cchar;
cchar = p_stream->get_char();
first = false;
diff --git a/doc/classes/ClassDB.xml b/doc/classes/ClassDB.xml
index 3f71406091..bdb0ec9bc6 100644
--- a/doc/classes/ClassDB.xml
+++ b/doc/classes/ClassDB.xml
@@ -192,6 +192,15 @@
Returns whether this [param class] is enabled or not.
</description>
</method>
+ <method name="is_class_enum_bitfield" qualifiers="const">
+ <return type="bool" />
+ <param index="0" name="class" type="StringName" />
+ <param index="1" name="enum" type="StringName" />
+ <param index="2" name="no_inheritance" type="bool" default="false" />
+ <description>
+ Returns whether [param class] (or its ancestor classes if [param no_inheritance] is [code]false[/code]) has an enum called [param enum] that is a bitfield.
+ </description>
+ </method>
<method name="is_parent_class" qualifiers="const">
<return type="bool" />
<param index="0" name="class" type="StringName" />
diff --git a/doc/classes/FileAccess.xml b/doc/classes/FileAccess.xml
index 4052e48400..fafc02734a 100644
--- a/doc/classes/FileAccess.xml
+++ b/doc/classes/FileAccess.xml
@@ -324,6 +324,13 @@
Returns [code]null[/code] if opening the file failed. You can use [method get_open_error] to check the error that occurred.
</description>
</method>
+ <method name="resize">
+ <return type="int" enum="Error" />
+ <param index="0" name="length" type="int" />
+ <description>
+ Resizes the file to a specified length. The file must be open in a mode that permits writing. If the file is extended, NUL characters are appended. If the file is truncated, all data from the end file to the original length of the file is lost.
+ </description>
+ </method>
<method name="seek">
<return type="void" />
<param index="0" name="position" type="int" />
diff --git a/doc/classes/MeshInstance3D.xml b/doc/classes/MeshInstance3D.xml
index e5187cf7a1..abbb4c4eeb 100644
--- a/doc/classes/MeshInstance3D.xml
+++ b/doc/classes/MeshInstance3D.xml
@@ -13,6 +13,14 @@
<link title="Third Person Shooter (TPS) Demo">https://godotengine.org/asset-library/asset/2710</link>
</tutorials>
<methods>
+ <method name="bake_mesh_from_current_blend_shape_mix">
+ <return type="ArrayMesh" />
+ <param index="0" name="existing" type="ArrayMesh" default="null" />
+ <description>
+ Takes a snapshot from the current [ArrayMesh] with all blend shapes applied according to their current weights and bakes it to the provided [param existing] mesh. If no [param existing] mesh is provided a new [ArrayMesh] is created, baked and returned. Mesh surface materials are not copied.
+ [b]Performance:[/b] [Mesh] data needs to be received from the GPU, stalling the [RenderingServer] in the process.
+ </description>
+ </method>
<method name="create_convex_collision">
<return type="void" />
<param index="0" name="clean" type="bool" default="true" />
diff --git a/doc/classes/NavigationMeshSourceGeometryData2D.xml b/doc/classes/NavigationMeshSourceGeometryData2D.xml
index 609877fadc..1d8689420b 100644
--- a/doc/classes/NavigationMeshSourceGeometryData2D.xml
+++ b/doc/classes/NavigationMeshSourceGeometryData2D.xml
@@ -31,6 +31,20 @@
Adds the outline points of a shape as traversable area.
</description>
</method>
+ <method name="append_obstruction_outlines">
+ <return type="void" />
+ <param index="0" name="obstruction_outlines" type="PackedVector2Array[]" />
+ <description>
+ Appends another array of [param obstruction_outlines] at the end of the existing obstruction outlines array.
+ </description>
+ </method>
+ <method name="append_traversable_outlines">
+ <return type="void" />
+ <param index="0" name="traversable_outlines" type="PackedVector2Array[]" />
+ <description>
+ Appends another array of [param traversable_outlines] at the end of the existing traversable outlines array.
+ </description>
+ </method>
<method name="clear">
<return type="void" />
<description>
diff --git a/doc/classes/NavigationMeshSourceGeometryData3D.xml b/doc/classes/NavigationMeshSourceGeometryData3D.xml
index 322e2e7c66..0b3126a63b 100644
--- a/doc/classes/NavigationMeshSourceGeometryData3D.xml
+++ b/doc/classes/NavigationMeshSourceGeometryData3D.xml
@@ -43,6 +43,14 @@
Adds a projected obstruction shape to the source geometry. The [param vertices] are considered projected on a xz-axes plane, placed at the global y-axis [param elevation] and extruded by [param height]. If [param carve] is [code]true[/code] the carved shape will not be affected by additional offsets (e.g. agent radius) of the navigation mesh baking process.
</description>
</method>
+ <method name="append_arrays">
+ <return type="void" />
+ <param index="0" name="vertices" type="PackedFloat32Array" />
+ <param index="1" name="indices" type="PackedInt32Array" />
+ <description>
+ Appends arrays of [param vertices] and [param indices] at the end of the existing arrays. Adds the existing index as an offset to the appended indices.
+ </description>
+ </method>
<method name="clear">
<return type="void" />
<description>
diff --git a/doc/classes/SurfaceTool.xml b/doc/classes/SurfaceTool.xml
index 576587a5df..a8bd068b1c 100644
--- a/doc/classes/SurfaceTool.xml
+++ b/doc/classes/SurfaceTool.xml
@@ -93,7 +93,7 @@
<method name="commit_to_arrays">
<return type="Array" />
<description>
- Commits the data to the same format used by [method ArrayMesh.add_surface_from_arrays]. This way you can further process the mesh data using the [ArrayMesh] API.
+ Commits the data to the same format used by [method ArrayMesh.add_surface_from_arrays], [method ImporterMesh.add_surface], and [method create_from_arrays]. This way you can further process the mesh data using the [ArrayMesh] or [ImporterMesh] APIs.
</description>
</method>
<method name="create_from">
@@ -104,6 +104,14 @@
Creates a vertex array from an existing [Mesh].
</description>
</method>
+ <method name="create_from_arrays">
+ <return type="void" />
+ <param index="0" name="arrays" type="Array" />
+ <param index="1" name="primitive_type" type="int" enum="Mesh.PrimitiveType" default="3" />
+ <description>
+ Creates this SurfaceTool from existing vertex arrays such as returned by [method commit_to_arrays], [method Mesh.surface_get_arrays], [method Mesh.surface_get_blend_shape_arrays], [method ImporterMesh.get_surface_arrays], and [method ImporterMesh.get_surface_blend_shape_arrays]. [param primitive_type] controls the type of mesh data, defaulting to [constant Mesh.PRIMITIVE_TRIANGLES].
+ </description>
+ </method>
<method name="create_from_blend_shape">
<return type="void" />
<param index="0" name="existing" type="Mesh" />
diff --git a/doc/classes/TileMapLayer.xml b/doc/classes/TileMapLayer.xml
index da716a8fe3..0513a7934c 100644
--- a/doc/classes/TileMapLayer.xml
+++ b/doc/classes/TileMapLayer.xml
@@ -268,7 +268,7 @@
The quadrant size does not apply on a Y-sorted [TileMapLayer], as tiles are be grouped by Y position instead in that case.
[b]Note:[/b] As quadrants are created according to the map's coordinate system, the quadrant's "square shape" might not look like square in the [TileMapLayer]'s local coordinate system.
</member>
- <member name="tile_map_data" type="PackedByteArray" setter="set_tile_map_data_from_array" getter="get_tile_map_data_as_array" default="PackedByteArray(&quot;AAA=&quot;)">
+ <member name="tile_map_data" type="PackedByteArray" setter="set_tile_map_data_from_array" getter="get_tile_map_data_as_array" default="PackedByteArray()">
The raw tile map data as a byte array.
</member>
<member name="tile_set" type="TileSet" setter="set_tile_set" getter="get_tile_set">
diff --git a/doc/classes/XRBodyModifier3D.xml b/doc/classes/XRBodyModifier3D.xml
index 49a226c106..d08b92a56c 100644
--- a/doc/classes/XRBodyModifier3D.xml
+++ b/doc/classes/XRBodyModifier3D.xml
@@ -4,15 +4,15 @@
A node for driving body meshes from [XRBodyTracker] data.
</brief_description>
<description>
- This node uses body tracking data from a [XRBodyTracker] to animate the skeleton of a body mesh.
- This node positions itself at the [constant XRBodyTracker.JOINT_ROOT] position and scales itself to [member XRServer.world_scale]. Adding the body model as a child of this node will result in the model being positioned and scaled correctly for XR experiences.
+ This node uses body tracking data from an [XRBodyTracker] to pose the skeleton of a body mesh.
+ Positioning of the body is performed by creating an [XRNode3D] ancestor of the body mesh driven by the same [XRBodyTracker].
The body tracking position-data is scaled by [member Skeleton3D.motion_scale] when applied to the skeleton, which can be used to adjust the tracked body to match the scale of the body model.
</description>
<tutorials>
<link title="XR documentation index">$DOCS_URL/tutorials/xr/index.html</link>
</tutorials>
<members>
- <member name="body_tracker" type="StringName" setter="set_body_tracker" getter="get_body_tracker" default="&amp;&quot;/user/body&quot;">
+ <member name="body_tracker" type="StringName" setter="set_body_tracker" getter="get_body_tracker" default="&amp;&quot;/user/body_tracker&quot;">
The name of the [XRBodyTracker] registered with [XRServer] to obtain the body tracking data from.
</member>
<member name="body_update" type="int" setter="set_body_update" getter="get_body_update" enum="XRBodyModifier3D.BodyUpdate" is_bitfield="true" default="7">
@@ -21,9 +21,6 @@
<member name="bone_update" type="int" setter="set_bone_update" getter="get_bone_update" enum="XRBodyModifier3D.BoneUpdate" default="0">
Specifies the type of updates to perform on the bones.
</member>
- <member name="show_when_tracked" type="bool" setter="set_show_when_tracked" getter="get_show_when_tracked" default="true">
- If true then the nodes visibility is determined by whether tracking data is available.
- </member>
</members>
<constants>
<constant name="BODY_UPDATE_UPPER_BODY" value="1" enum="BodyUpdate" is_bitfield="true">
diff --git a/doc/classes/XRBodyTracker.xml b/doc/classes/XRBodyTracker.xml
index 9c869b4f5f..06a4879953 100644
--- a/doc/classes/XRBodyTracker.xml
+++ b/doc/classes/XRBodyTracker.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" ?>
-<class name="XRBodyTracker" inherits="RefCounted" experimental="" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
+<class name="XRBodyTracker" inherits="XRPositionalTracker" experimental="" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
<brief_description>
A tracked body in XR.
</brief_description>
@@ -49,6 +49,7 @@
<member name="has_tracking_data" type="bool" setter="set_has_tracking_data" getter="get_has_tracking_data" default="false">
If [code]true[/code], the body tracking data is valid.
</member>
+ <member name="type" type="int" setter="set_tracker_type" getter="get_tracker_type" overrides="XRTracker" enum="XRServer.TrackerType" default="32" />
</members>
<constants>
<constant name="BODY_FLAG_UPPER_BODY_SUPPORTED" value="1" enum="BodyFlags" is_bitfield="true">
diff --git a/doc/classes/XRControllerTracker.xml b/doc/classes/XRControllerTracker.xml
new file mode 100644
index 0000000000..50727a7633
--- /dev/null
+++ b/doc/classes/XRControllerTracker.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<class name="XRControllerTracker" inherits="XRPositionalTracker" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
+ <brief_description>
+ A tracked controller.
+ </brief_description>
+ <description>
+ An instance of this object represents a controller that is tracked.
+ As controllers are turned on and the [XRInterface] detects them, instances of this object are automatically added to this list of active tracking objects accessible through the [XRServer].
+ The [XRController3D] consumes objects of this type and should be used in your project.
+ </description>
+ <tutorials>
+ <link title="XR documentation index">$DOCS_URL/tutorials/xr/index.html</link>
+ </tutorials>
+ <members>
+ <member name="type" type="int" setter="set_tracker_type" getter="get_tracker_type" overrides="XRTracker" enum="XRServer.TrackerType" default="2" />
+ </members>
+</class>
diff --git a/doc/classes/XRFaceModifier3D.xml b/doc/classes/XRFaceModifier3D.xml
index 8caa74cff7..9599051b1b 100644
--- a/doc/classes/XRFaceModifier3D.xml
+++ b/doc/classes/XRFaceModifier3D.xml
@@ -12,7 +12,7 @@
<link title="XR documentation index">$DOCS_URL/tutorials/xr/index.html</link>
</tutorials>
<members>
- <member name="face_tracker" type="StringName" setter="set_face_tracker" getter="get_face_tracker" default="&amp;&quot;/user/head&quot;">
+ <member name="face_tracker" type="StringName" setter="set_face_tracker" getter="get_face_tracker" default="&amp;&quot;/user/face_tracker&quot;">
The [XRFaceTracker] path.
</member>
<member name="target" type="NodePath" setter="set_target" getter="get_target" default="NodePath(&quot;&quot;)">
diff --git a/doc/classes/XRFaceTracker.xml b/doc/classes/XRFaceTracker.xml
index 96ed137324..23a9a9cb17 100644
--- a/doc/classes/XRFaceTracker.xml
+++ b/doc/classes/XRFaceTracker.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" ?>
-<class name="XRFaceTracker" inherits="RefCounted" experimental="" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
+<class name="XRFaceTracker" inherits="XRTracker" experimental="" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
<brief_description>
A tracked face.
</brief_description>
@@ -31,6 +31,7 @@
<member name="blend_shapes" type="PackedFloat32Array" setter="set_blend_shapes" getter="get_blend_shapes" default="PackedFloat32Array()">
The array of face blend shape weights with indices corresponding to the [enum BlendShapeEntry] enum.
</member>
+ <member name="type" type="int" setter="set_tracker_type" getter="get_tracker_type" overrides="XRTracker" enum="XRServer.TrackerType" default="64" />
</members>
<constants>
<constant name="FT_EYE_LOOK_OUT_RIGHT" value="0" enum="BlendShapeEntry">
diff --git a/doc/classes/XRHandModifier3D.xml b/doc/classes/XRHandModifier3D.xml
index 9ff27bb982..09ff321070 100644
--- a/doc/classes/XRHandModifier3D.xml
+++ b/doc/classes/XRHandModifier3D.xml
@@ -4,8 +4,8 @@
A node for driving hand meshes from [XRHandTracker] data.
</brief_description>
<description>
- This node uses hand tracking data from a [XRHandTracker] to animate the skeleton of a hand mesh.
- This node positions itself at the [constant XRHandTracker.HAND_JOINT_PALM] position and scales itself to [member XRServer.world_scale]. Adding the hand model as a child of this node will result in the model being positioned and scaled correctly for XR experiences.
+ This node uses hand tracking data from an [XRHandTracker] to pose the skeleton of a hand mesh.
+ Positioning of hands is performed by creating an [XRNode3D] ancestor of the hand mesh driven by the same [XRHandTracker].
The hand tracking position-data is scaled by [member Skeleton3D.motion_scale] when applied to the skeleton, which can be used to adjust the tracked hand to match the scale of the hand model.
</description>
<tutorials>
@@ -15,7 +15,7 @@
<member name="bone_update" type="int" setter="set_bone_update" getter="get_bone_update" enum="XRHandModifier3D.BoneUpdate" default="0">
Specifies the type of updates to perform on the bones.
</member>
- <member name="hand_tracker" type="StringName" setter="set_hand_tracker" getter="get_hand_tracker" default="&amp;&quot;/user/left&quot;">
+ <member name="hand_tracker" type="StringName" setter="set_hand_tracker" getter="get_hand_tracker" default="&amp;&quot;/user/hand_tracker/left&quot;">
The name of the [XRHandTracker] registered with [XRServer] to obtain the hand tracking data from.
</member>
</members>
diff --git a/doc/classes/XRHandTracker.xml b/doc/classes/XRHandTracker.xml
index 932fec083a..69390df696 100644
--- a/doc/classes/XRHandTracker.xml
+++ b/doc/classes/XRHandTracker.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" ?>
-<class name="XRHandTracker" inherits="RefCounted" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
+<class name="XRHandTracker" inherits="XRPositionalTracker" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
<brief_description>
A tracked hand in XR.
</brief_description>
@@ -11,6 +11,12 @@
<link title="XR documentation index">$DOCS_URL/tutorials/xr/index.html</link>
</tutorials>
<methods>
+ <method name="get_hand" qualifiers="const">
+ <return type="int" enum="XRHandTracker.Hand" />
+ <description>
+ Returns the type of hand.
+ </description>
+ </method>
<method name="get_hand_joint_angular_velocity" qualifiers="const">
<return type="Vector3" />
<param index="0" name="joint" type="int" enum="XRHandTracker.HandJoint" />
@@ -46,6 +52,13 @@
Returns the transform for the given hand joint.
</description>
</method>
+ <method name="set_hand">
+ <return type="void" />
+ <param index="0" name="hand" type="int" enum="XRHandTracker.Hand" />
+ <description>
+ Sets the type of hand.
+ </description>
+ </method>
<method name="set_hand_joint_angular_velocity">
<return type="void" />
<param index="0" name="joint" type="int" enum="XRHandTracker.HandJoint" />
@@ -88,15 +101,13 @@
</method>
</methods>
<members>
- <member name="hand" type="int" setter="set_hand" getter="get_hand" enum="XRHandTracker.Hand" default="0">
- The type of hand.
- </member>
<member name="hand_tracking_source" type="int" setter="set_hand_tracking_source" getter="get_hand_tracking_source" enum="XRHandTracker.HandTrackingSource" default="0">
The source of the hand tracking data.
</member>
<member name="has_tracking_data" type="bool" setter="set_has_tracking_data" getter="get_has_tracking_data" default="false">
If [code]true[/code], the hand tracking data is valid.
</member>
+ <member name="type" type="int" setter="set_tracker_type" getter="get_tracker_type" overrides="XRTracker" enum="XRServer.TrackerType" default="16" />
</members>
<constants>
<constant name="HAND_LEFT" value="0" enum="Hand">
diff --git a/doc/classes/XRNode3D.xml b/doc/classes/XRNode3D.xml
index 3da1873ed8..dfe5600fcc 100644
--- a/doc/classes/XRNode3D.xml
+++ b/doc/classes/XRNode3D.xml
@@ -46,6 +46,9 @@
The name of the pose we're bound to. Which poses a tracker supports is not known during design time.
Godot defines number of standard pose names such as [code]aim[/code] and [code]grip[/code] but other may be configured within a given [XRInterface].
</member>
+ <member name="show_when_tracked" type="bool" setter="set_show_when_tracked" getter="get_show_when_tracked" default="false">
+ Enables showing the node when tracking starts, and hiding the node when tracking is lost.
+ </member>
<member name="tracker" type="StringName" setter="set_tracker" getter="get_tracker" default="&amp;&quot;&quot;">
The name of the tracker we're bound to. Which trackers are available is not known during design time.
Godot defines a number of standard trackers such as [code]left_hand[/code] and [code]right_hand[/code] but others may be configured within a given [XRInterface].
diff --git a/doc/classes/XRPositionalTracker.xml b/doc/classes/XRPositionalTracker.xml
index bd2432af50..2b2eae0c5e 100644
--- a/doc/classes/XRPositionalTracker.xml
+++ b/doc/classes/XRPositionalTracker.xml
@@ -1,18 +1,18 @@
<?xml version="1.0" encoding="UTF-8" ?>
-<class name="XRPositionalTracker" inherits="RefCounted" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
+<class name="XRPositionalTracker" inherits="XRTracker" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
<brief_description>
A tracked object.
</brief_description>
<description>
An instance of this object represents a device that is tracked, such as a controller or anchor point. HMDs aren't represented here as they are handled internally.
As controllers are turned on and the [XRInterface] detects them, instances of this object are automatically added to this list of active tracking objects accessible through the [XRServer].
- The [XRController3D] and [XRAnchor3D] both consume objects of this type and should be used in your project. The positional trackers are just under-the-hood objects that make this all work. These are mostly exposed so that GDExtension-based interfaces can interact with them.
+ The [XRNode3D] and [XRAnchor3D] both consume objects of this type and should be used in your project. The positional trackers are just under-the-hood objects that make this all work. These are mostly exposed so that GDExtension-based interfaces can interact with them.
</description>
<tutorials>
<link title="XR documentation index">$DOCS_URL/tutorials/xr/index.html</link>
</tutorials>
<methods>
- <method name="get_input" qualifiers="const">
+ <method name="get_input" qualifiers="const" deprecated="Use through [XRControllerTracker].">
<return type="Variant" />
<param index="0" name="name" type="StringName" />
<description>
@@ -40,7 +40,7 @@
Marks this pose as invalid, we don't clear the last reported state but it allows users to decide if trackers need to be hidden if we lose tracking or just remain at their last known position.
</description>
</method>
- <method name="set_input">
+ <method name="set_input" deprecated="Use through [XRControllerTracker].">
<return type="void" />
<param index="0" name="name" type="StringName" />
<param index="1" name="value" type="Variant" />
@@ -61,23 +61,12 @@
</method>
</methods>
<members>
- <member name="description" type="String" setter="set_tracker_desc" getter="get_tracker_desc" default="&quot;&quot;">
- The description of this tracker.
- </member>
<member name="hand" type="int" setter="set_tracker_hand" getter="get_tracker_hand" enum="XRPositionalTracker.TrackerHand" default="0">
Defines which hand this tracker relates to.
</member>
- <member name="name" type="StringName" setter="set_tracker_name" getter="get_tracker_name" default="&amp;&quot;Unknown&quot;">
- The unique name of this tracker. The trackers that are available differ between various XR runtimes and can often be configured by the user. Godot maintains a number of reserved names that it expects the [XRInterface] to implement if applicable:
- - [code]left_hand[/code] identifies the controller held in the players left hand
- - [code]right_hand[/code] identifies the controller held in the players right hand
- </member>
<member name="profile" type="String" setter="set_tracker_profile" getter="get_tracker_profile" default="&quot;&quot;">
The profile associated with this tracker, interface dependent but will indicate the type of controller being tracked.
</member>
- <member name="type" type="int" setter="set_tracker_type" getter="get_tracker_type" enum="XRServer.TrackerType" default="128">
- The type of tracker.
- </member>
</members>
<signals>
<signal name="button_pressed">
@@ -135,5 +124,8 @@
<constant name="TRACKER_HAND_RIGHT" value="2" enum="TrackerHand">
This tracker is the right hand controller.
</constant>
+ <constant name="TRACKER_HAND_MAX" value="3" enum="TrackerHand">
+ Represents the size of the [enum TrackerHand] enum.
+ </constant>
</constants>
</class>
diff --git a/doc/classes/XRServer.xml b/doc/classes/XRServer.xml
index 671cc8f15c..d5714980c3 100644
--- a/doc/classes/XRServer.xml
+++ b/doc/classes/XRServer.xml
@@ -10,30 +10,6 @@
<link title="XR documentation index">$DOCS_URL/tutorials/xr/index.html</link>
</tutorials>
<methods>
- <method name="add_body_tracker">
- <return type="void" />
- <param index="0" name="tracker_name" type="StringName" />
- <param index="1" name="body_tracker" type="XRBodyTracker" />
- <description>
- Registers a new [XRBodyTracker] that tracks the joints of a body.
- </description>
- </method>
- <method name="add_face_tracker">
- <return type="void" />
- <param index="0" name="tracker_name" type="StringName" />
- <param index="1" name="face_tracker" type="XRFaceTracker" />
- <description>
- Registers a new [XRFaceTracker] that tracks the blend shapes of a face.
- </description>
- </method>
- <method name="add_hand_tracker">
- <return type="void" />
- <param index="0" name="tracker_name" type="StringName" />
- <param index="1" name="hand_tracker" type="XRHandTracker" />
- <description>
- Registers a new [XRHandTracker] that tracks the joints of a hand.
- </description>
- </method>
<method name="add_interface">
<return type="void" />
<param index="0" name="interface" type="XRInterface" />
@@ -43,9 +19,9 @@
</method>
<method name="add_tracker">
<return type="void" />
- <param index="0" name="tracker" type="XRPositionalTracker" />
+ <param index="0" name="tracker" type="XRTracker" />
<description>
- Registers a new [XRPositionalTracker] that tracks a spatial location in real space.
+ Registers a new [XRTracker] that tracks a physical object.
</description>
</method>
<method name="center_on_hmd">
@@ -74,45 +50,6 @@
Finds an interface by its [param name]. For example, if your project uses capabilities of an AR/VR platform, you can find the interface for that platform by name and initialize it.
</description>
</method>
- <method name="get_body_tracker" qualifiers="const">
- <return type="XRBodyTracker" />
- <param index="0" name="tracker_name" type="StringName" />
- <description>
- Returns the [XRBodyTracker] with the given tracker name.
- </description>
- </method>
- <method name="get_body_trackers" qualifiers="const">
- <return type="Dictionary" />
- <description>
- Returns a dictionary of the registered body trackers. Each element of the dictionary is a tracker name mapping to the [XRBodyTracker] instance.
- </description>
- </method>
- <method name="get_face_tracker" qualifiers="const">
- <return type="XRFaceTracker" />
- <param index="0" name="tracker_name" type="StringName" />
- <description>
- Returns the [XRFaceTracker] with the given tracker name.
- </description>
- </method>
- <method name="get_face_trackers" qualifiers="const">
- <return type="Dictionary" />
- <description>
- Returns a dictionary of the registered face trackers. Each element of the dictionary is a tracker name mapping to the [XRFaceTracker] instance.
- </description>
- </method>
- <method name="get_hand_tracker" qualifiers="const">
- <return type="XRHandTracker" />
- <param index="0" name="tracker_name" type="StringName" />
- <description>
- Returns the [XRHandTracker] with the given tracker name.
- </description>
- </method>
- <method name="get_hand_trackers" qualifiers="const">
- <return type="Dictionary" />
- <description>
- Returns a dictionary of the registered hand trackers. Each element of the dictionary is a tracker name mapping to the [XRHandTracker] instance.
- </description>
- </method>
<method name="get_hmd_transform">
<return type="Transform3D" />
<description>
@@ -145,7 +82,7 @@
</description>
</method>
<method name="get_tracker" qualifiers="const">
- <return type="XRPositionalTracker" />
+ <return type="XRTracker" />
<param index="0" name="tracker_name" type="StringName" />
<description>
Returns the positional tracker with the given [param tracker_name].
@@ -158,27 +95,6 @@
Returns a dictionary of trackers for [param tracker_types].
</description>
</method>
- <method name="remove_body_tracker">
- <return type="void" />
- <param index="0" name="tracker_name" type="StringName" />
- <description>
- Removes a registered [XRBodyTracker].
- </description>
- </method>
- <method name="remove_face_tracker">
- <return type="void" />
- <param index="0" name="tracker_name" type="StringName" />
- <description>
- Removes a registered [XRFaceTracker].
- </description>
- </method>
- <method name="remove_hand_tracker">
- <return type="void" />
- <param index="0" name="tracker_name" type="StringName" />
- <description>
- Removes a registered [XRHandTracker].
- </description>
- </method>
<method name="remove_interface">
<return type="void" />
<param index="0" name="interface" type="XRInterface" />
@@ -188,9 +104,9 @@
</method>
<method name="remove_tracker">
<return type="void" />
- <param index="0" name="tracker" type="XRPositionalTracker" />
+ <param index="0" name="tracker" type="XRTracker" />
<description>
- Removes this positional [param tracker].
+ Removes this [param tracker].
</description>
</method>
</methods>
@@ -207,66 +123,6 @@
</member>
</members>
<signals>
- <signal name="body_tracker_added">
- <param index="0" name="tracker_name" type="StringName" />
- <param index="1" name="body_tracker" type="XRBodyTracker" />
- <description>
- Emitted when a new body tracker is added.
- </description>
- </signal>
- <signal name="body_tracker_removed">
- <param index="0" name="tracker_name" type="StringName" />
- <description>
- Emitted when a body tracker is removed.
- </description>
- </signal>
- <signal name="body_tracker_updated">
- <param index="0" name="tracker_name" type="StringName" />
- <param index="1" name="body_tracker" type="XRBodyTracker" />
- <description>
- Emitted when an existing body tracker is updated.
- </description>
- </signal>
- <signal name="face_tracker_added">
- <param index="0" name="tracker_name" type="StringName" />
- <param index="1" name="face_tracker" type="XRFaceTracker" />
- <description>
- Emitted when a new face tracker is added.
- </description>
- </signal>
- <signal name="face_tracker_removed">
- <param index="0" name="tracker_name" type="StringName" />
- <description>
- Emitted when a face tracker is removed.
- </description>
- </signal>
- <signal name="face_tracker_updated">
- <param index="0" name="tracker_name" type="StringName" />
- <param index="1" name="face_tracker" type="XRFaceTracker" />
- <description>
- Emitted when an existing face tracker is updated.
- </description>
- </signal>
- <signal name="hand_tracker_added">
- <param index="0" name="tracker_name" type="StringName" />
- <param index="1" name="hand_tracker" type="XRHandTracker" />
- <description>
- Emitted when a new hand tracker is added.
- </description>
- </signal>
- <signal name="hand_tracker_removed">
- <param index="0" name="tracker_name" type="StringName" />
- <description>
- Emitted when a hand tracker is removed.
- </description>
- </signal>
- <signal name="hand_tracker_updated">
- <param index="0" name="tracker_name" type="StringName" />
- <param index="1" name="hand_tracker" type="XRHandTracker" />
- <description>
- Emitted when an existing hand tracker is updated.
- </description>
- </signal>
<signal name="interface_added">
<param index="0" name="interface_name" type="StringName" />
<description>
@@ -314,6 +170,15 @@
<constant name="TRACKER_ANCHOR" value="8" enum="TrackerType">
The tracker tracks the location and size of an AR anchor.
</constant>
+ <constant name="TRACKER_HAND" value="16" enum="TrackerType">
+ The tracker tracks the location and joints of a hand.
+ </constant>
+ <constant name="TRACKER_BODY" value="32" enum="TrackerType">
+ The tracker tracks the location and joints of a body.
+ </constant>
+ <constant name="TRACKER_FACE" value="64" enum="TrackerType">
+ The tracker tracks the expressions of a face.
+ </constant>
<constant name="TRACKER_ANY_KNOWN" value="127" enum="TrackerType">
Used internally to filter trackers of any known type.
</constant>
diff --git a/doc/classes/XRTracker.xml b/doc/classes/XRTracker.xml
new file mode 100644
index 0000000000..00a44bd03e
--- /dev/null
+++ b/doc/classes/XRTracker.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<class name="XRTracker" inherits="RefCounted" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
+ <brief_description>
+ A tracked object.
+ </brief_description>
+ <description>
+ This object is the base of all XR trackers.
+ </description>
+ <tutorials>
+ <link title="XR documentation index">$DOCS_URL/tutorials/xr/index.html</link>
+ </tutorials>
+ <members>
+ <member name="description" type="String" setter="set_tracker_desc" getter="get_tracker_desc" default="&quot;&quot;">
+ The description of this tracker.
+ </member>
+ <member name="name" type="StringName" setter="set_tracker_name" getter="get_tracker_name" default="&amp;&quot;Unknown&quot;">
+ The unique name of this tracker. The trackers that are available differ between various XR runtimes and can often be configured by the user. Godot maintains a number of reserved names that it expects the [XRInterface] to implement if applicable:
+ - [code]head[/code] identifies the [XRPositionalTracker] of the players head
+ - [code]left_hand[/code] identifies the [XRControllerTracker] in the players left hand
+ - [code]right_hand[/code] identifies the [XRControllerTracker] in the players right hand
+ - [code]/user/hand_tracker/left[/code] identifies the [XRHandTracker] for the players left hand
+ - [code]/user/hand_tracker/right[/code] identifies the [XRHandTracker] for the players right hand
+ - [code]/user/body_tracker[/code] identifies the [XRBodyTracker] for the players body
+ - [code]/user/face_tracker[/code] identifies the [XRFaceTracker] for the players face
+ </member>
+ <member name="type" type="int" setter="set_tracker_type" getter="get_tracker_type" enum="XRServer.TrackerType" default="128">
+ The type of tracker.
+ </member>
+ </members>
+</class>
diff --git a/drivers/unix/file_access_unix.cpp b/drivers/unix/file_access_unix.cpp
index a35d8bfdde..210507c2c6 100644
--- a/drivers/unix/file_access_unix.cpp
+++ b/drivers/unix/file_access_unix.cpp
@@ -286,6 +286,23 @@ Error FileAccessUnix::get_error() const {
return last_error;
}
+Error FileAccessUnix::resize(int64_t p_length) {
+ ERR_FAIL_NULL_V_MSG(f, FAILED, "File must be opened before use.");
+ int res = ::ftruncate(fileno(f), p_length);
+ switch (res) {
+ case 0:
+ return OK;
+ case EBADF:
+ return ERR_FILE_CANT_OPEN;
+ case EFBIG:
+ return ERR_OUT_OF_MEMORY;
+ case EINVAL:
+ return ERR_INVALID_PARAMETER;
+ default:
+ return FAILED;
+ }
+}
+
void FileAccessUnix::flush() {
ERR_FAIL_NULL_MSG(f, "File must be opened before use.");
fflush(f);
diff --git a/drivers/unix/file_access_unix.h b/drivers/unix/file_access_unix.h
index 553fbcf355..c0286dbff3 100644
--- a/drivers/unix/file_access_unix.h
+++ b/drivers/unix/file_access_unix.h
@@ -75,6 +75,7 @@ public:
virtual Error get_error() const override; ///< get last error
+ virtual Error resize(int64_t p_length) override;
virtual void flush() override;
virtual void store_8(uint8_t p_dest) override; ///< store a byte
virtual void store_16(uint16_t p_dest) override;
diff --git a/drivers/unix/file_access_unix_pipe.h b/drivers/unix/file_access_unix_pipe.h
index d14f897d8f..8e7988791b 100644
--- a/drivers/unix/file_access_unix_pipe.h
+++ b/drivers/unix/file_access_unix_pipe.h
@@ -70,6 +70,7 @@ public:
virtual Error get_error() const override; ///< get last error
+ virtual Error resize(int64_t p_length) override { return ERR_UNAVAILABLE; }
virtual void flush() override {}
virtual void store_8(uint8_t p_src) override; ///< store a byte
virtual void store_buffer(const uint8_t *p_src, uint64_t p_length) override; ///< store an array of bytes
diff --git a/drivers/unix/os_unix.cpp b/drivers/unix/os_unix.cpp
index 1a8cd53486..0a79c8014b 100644
--- a/drivers/unix/os_unix.cpp
+++ b/drivers/unix/os_unix.cpp
@@ -784,7 +784,7 @@ String OS_Unix::get_locale() const {
return locale;
}
-Error OS_Unix::open_dynamic_library(const String &p_path, void *&p_library_handle, bool p_also_set_library_path, String *r_resolved_path, bool p_generate_temp_files) {
+Error OS_Unix::open_dynamic_library(const String &p_path, void *&p_library_handle, GDExtensionData *p_data) {
String path = p_path;
if (FileAccess::exists(path) && path.is_relative_path()) {
@@ -808,8 +808,8 @@ Error OS_Unix::open_dynamic_library(const String &p_path, void *&p_library_handl
p_library_handle = dlopen(path.utf8().get_data(), GODOT_DLOPEN_MODE);
ERR_FAIL_NULL_V_MSG(p_library_handle, ERR_CANT_OPEN, vformat("Can't open dynamic library: %s. Error: %s.", p_path, dlerror()));
- if (r_resolved_path != nullptr) {
- *r_resolved_path = path;
+ if (p_data != nullptr && p_data->r_resolved_path != nullptr) {
+ *p_data->r_resolved_path = path;
}
return OK;
diff --git a/drivers/unix/os_unix.h b/drivers/unix/os_unix.h
index a107e7a0e3..df269a59d3 100644
--- a/drivers/unix/os_unix.h
+++ b/drivers/unix/os_unix.h
@@ -62,7 +62,7 @@ public:
virtual Error get_entropy(uint8_t *r_buffer, int p_bytes) override;
- virtual Error open_dynamic_library(const String &p_path, void *&p_library_handle, bool p_also_set_library_path = false, String *r_resolved_path = nullptr, bool p_generate_temp_files = false) override;
+ virtual Error open_dynamic_library(const String &p_path, void *&p_library_handle, GDExtensionData *p_data = nullptr) override;
virtual Error close_dynamic_library(void *p_library_handle) override;
virtual Error get_dynamic_library_symbol_handle(void *p_library_handle, const String &p_name, void *&p_symbol_handle, bool p_optional = false) override;
diff --git a/drivers/windows/file_access_windows.cpp b/drivers/windows/file_access_windows.cpp
index dd8bceb573..726e0fdc5a 100644
--- a/drivers/windows/file_access_windows.cpp
+++ b/drivers/windows/file_access_windows.cpp
@@ -41,6 +41,7 @@
#include <windows.h>
#include <errno.h>
+#include <io.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <tchar.h>
@@ -369,6 +370,24 @@ Error FileAccessWindows::get_error() const {
return last_error;
}
+Error FileAccessWindows::resize(int64_t p_length) {
+ ERR_FAIL_NULL_V_MSG(f, FAILED, "File must be opened before use.");
+ errno_t res = _chsize_s(_fileno(f), p_length);
+ switch (res) {
+ case 0:
+ return OK;
+ case EACCES:
+ case EBADF:
+ return ERR_FILE_CANT_OPEN;
+ case ENOSPC:
+ return ERR_OUT_OF_MEMORY;
+ case EINVAL:
+ return ERR_INVALID_PARAMETER;
+ default:
+ return FAILED;
+ }
+}
+
void FileAccessWindows::flush() {
ERR_FAIL_NULL(f);
diff --git a/drivers/windows/file_access_windows.h b/drivers/windows/file_access_windows.h
index 173423fb06..a25bbcfb3a 100644
--- a/drivers/windows/file_access_windows.h
+++ b/drivers/windows/file_access_windows.h
@@ -77,6 +77,7 @@ public:
virtual Error get_error() const override; ///< get last error
+ virtual Error resize(int64_t p_length) override;
virtual void flush() override;
virtual void store_8(uint8_t p_dest) override; ///< store a byte
virtual void store_16(uint16_t p_dest) override;
diff --git a/drivers/windows/file_access_windows_pipe.h b/drivers/windows/file_access_windows_pipe.h
index e6abe61fa3..b885ef78e6 100644
--- a/drivers/windows/file_access_windows_pipe.h
+++ b/drivers/windows/file_access_windows_pipe.h
@@ -69,6 +69,7 @@ public:
virtual Error get_error() const override; ///< get last error
+ virtual Error resize(int64_t p_length) override { return ERR_UNAVAILABLE; }
virtual void flush() override {}
virtual void store_8(uint8_t p_src) override; ///< store a byte
virtual void store_buffer(const uint8_t *p_src, uint64_t p_length) override; ///< store an array of bytes
diff --git a/editor/editor_dock_manager.cpp b/editor/editor_dock_manager.cpp
index b6250671ee..06dd33d8ab 100644
--- a/editor/editor_dock_manager.cpp
+++ b/editor/editor_dock_manager.cpp
@@ -147,7 +147,6 @@ void EditorDockManager::_update_layout() {
if (!dock_context_popup->is_inside_tree() || EditorNode::get_singleton()->is_exiting()) {
return;
}
- EditorNode::get_singleton()->edit_current();
dock_context_popup->docks_updated();
_update_docks_menu();
EditorNode::get_singleton()->save_editor_layout_delayed();
diff --git a/editor/editor_inspector.cpp b/editor/editor_inspector.cpp
index 50cc89c618..98feba38fd 100644
--- a/editor/editor_inspector.cpp
+++ b/editor/editor_inspector.cpp
@@ -3477,7 +3477,9 @@ void EditorInspector::edit(Object *p_object) {
next_object = p_object; // Some plugins need to know the next edited object when clearing the inspector.
if (object) {
_clear();
- object->disconnect("property_list_changed", callable_mp(this, &EditorInspector::_changed_callback));
+ if (object->is_connected("property_list_changed", callable_mp(this, &EditorInspector::_changed_callback))) {
+ object->disconnect("property_list_changed", callable_mp(this, &EditorInspector::_changed_callback));
+ }
}
per_array_page.clear();
@@ -4019,14 +4021,13 @@ void EditorInspector::_notification(int p_what) {
} break;
case NOTIFICATION_PREDELETE: {
- edit(nullptr); //just in case
+ edit(nullptr);
} break;
case NOTIFICATION_EXIT_TREE: {
if (!sub_inspector) {
get_tree()->disconnect("node_removed", callable_mp(this, &EditorInspector::_node_removed));
}
- edit(nullptr);
} break;
case NOTIFICATION_VISIBILITY_CHANGED: {
diff --git a/editor/editor_paths.cpp b/editor/editor_paths.cpp
index 801c81efa8..be511452a6 100644
--- a/editor/editor_paths.cpp
+++ b/editor/editor_paths.cpp
@@ -70,6 +70,10 @@ String EditorPaths::get_export_templates_dir() const {
return get_data_dir().path_join(export_templates_folder);
}
+String EditorPaths::get_debug_keystore_path() const {
+ return get_data_dir().path_join("keystores/debug.keystore");
+}
+
String EditorPaths::get_project_settings_dir() const {
return get_project_data_dir().path_join("editor");
}
diff --git a/editor/editor_paths.h b/editor/editor_paths.h
index 547b93ad7e..a396c43301 100644
--- a/editor/editor_paths.h
+++ b/editor/editor_paths.h
@@ -63,6 +63,7 @@ public:
String get_cache_dir() const;
String get_project_data_dir() const;
String get_export_templates_dir() const;
+ String get_debug_keystore_path() const;
String get_project_settings_dir() const;
String get_text_editor_themes_dir() const;
String get_script_templates_dir() const;
diff --git a/editor/export/editor_export.cpp b/editor/export/editor_export.cpp
index 0f02ab5a7a..9a0f2f18fa 100644
--- a/editor/export/editor_export.cpp
+++ b/editor/export/editor_export.cpp
@@ -32,6 +32,7 @@
#include "core/config/project_settings.h"
#include "core/io/config_file.h"
+#include "editor/editor_settings.h"
EditorExport *EditorExport::singleton = nullptr;
@@ -191,6 +192,12 @@ void EditorExport::_notification(int p_what) {
export_platforms.write[i]->cleanup();
}
} break;
+
+ case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: {
+ for (int i = 0; i < export_platforms.size(); i++) {
+ export_platforms.write[i]->notification(p_what);
+ }
+ } break;
}
}
diff --git a/editor/export/editor_export_platform.h b/editor/export/editor_export_platform.h
index 26e1f86c8b..3fd75ff67f 100644
--- a/editor/export/editor_export_platform.h
+++ b/editor/export/editor_export_platform.h
@@ -36,8 +36,8 @@ struct EditorProgress;
#include "core/io/dir_access.h"
#include "core/io/zip_io.h"
+#include "core/os/shared_object.h"
#include "editor_export_preset.h"
-#include "editor_export_shared_object.h"
#include "scene/gui/rich_text_label.h"
#include "scene/main/node.h"
#include "scene/resources/image_texture.h"
diff --git a/editor/export/editor_export_plugin.cpp b/editor/export/editor_export_plugin.cpp
index 5353eae654..28d0750d5a 100644
--- a/editor/export/editor_export_plugin.cpp
+++ b/editor/export/editor_export_plugin.cpp
@@ -60,6 +60,10 @@ void EditorExportPlugin::add_shared_object(const String &p_path, const Vector<St
shared_objects.push_back(SharedObject(p_path, p_tags, p_target));
}
+void EditorExportPlugin::_add_shared_object(const SharedObject &p_shared_object) {
+ shared_objects.push_back(p_shared_object);
+}
+
void EditorExportPlugin::add_ios_framework(const String &p_path) {
ios_frameworks.push_back(p_path);
}
diff --git a/editor/export/editor_export_plugin.h b/editor/export/editor_export_plugin.h
index a4e9917a81..56eea85010 100644
--- a/editor/export/editor_export_plugin.h
+++ b/editor/export/editor_export_plugin.h
@@ -32,9 +32,9 @@
#define EDITOR_EXPORT_PLUGIN_H
#include "core/extension/gdextension.h"
+#include "core/os/shared_object.h"
#include "editor_export_platform.h"
#include "editor_export_preset.h"
-#include "editor_export_shared_object.h"
#include "scene/main/node.h"
class EditorExportPlugin : public RefCounted {
@@ -94,6 +94,7 @@ protected:
void add_file(const String &p_path, const Vector<uint8_t> &p_file, bool p_remap);
void add_shared_object(const String &p_path, const Vector<String> &tags, const String &p_target = String());
+ void _add_shared_object(const SharedObject &p_shared_object);
void add_ios_framework(const String &p_path);
void add_ios_embedded_framework(const String &p_path);
diff --git a/editor/icons/XRFaceModifier3D.svg b/editor/icons/XRFaceModifier3D.svg
new file mode 100644
index 0000000000..6ab48ca29c
--- /dev/null
+++ b/editor/icons/XRFaceModifier3D.svg
@@ -0,0 +1 @@
+<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><g fill="#fc7f7f"><path d="m4 12v1c0 .552.448 1 1 1-.552 0-1 .448-1 1v1h1v-1h1v1h1v-1c0-.552-.448-1-1-1 .552 0 1-.448 1-1v-1h-1v1h-1v-1zm5 0v4h1v-1h1v1h1v-1c-.001-.176-.048-.348-.137-.5.089-.152.136-.324.137-.5v-1c0-.552-.448-1-1-1zm1 1h1v1h-1z"/><path d="m11.384 9.462v-2.155c1.613-.944 2.156-3.016 1.213-4.631-.603-1.033-1.709-1.67-2.905-1.676h-3.385c-1.869.008-3.377 1.532-3.368 3.401.005 1.197.643 2.301 1.676 2.906v2.155c0 .934.758 1.692 1.692 1.692h3.385c.935 0 1.692-.758 1.692-1.692zm-4.23-4.231h1.692v.846h-1.692zm-2.539-.846c0-.468.378-.846.847-.846.468 0 .846.378.846.846 0 .467-.378.846-.846.846-.469 0-.847-.379-.847-.846zm5.923 5.077h-.846v-.847h-.846v.846h-1.692v-.846h-.847v.846h-.846v-2.538h.846v.846h.847v-.846h1.692v.846h.846v-.846h.846zm-.846-5.077c0-.468.378-.846.846-.846s.846.378.846.846c0 .467-.378.846-.846.846s-.846-.379-.846-.846z"/></g></svg> \ No newline at end of file
diff --git a/editor/icons/XRNode3D.svg b/editor/icons/XRNode3D.svg
new file mode 100644
index 0000000000..50dd3ad951
--- /dev/null
+++ b/editor/icons/XRNode3D.svg
@@ -0,0 +1 @@
+<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><circle cx="8" cy="6" fill="none" r="4" stroke="#fc7f7f" stroke-width="2"/><path d="m4 12v1c0 .552.448 1 1 1-.552 0-1 .448-1 1v1h1v-1h1v1h1v-1c0-.552-.448-1-1-1 .552 0 1-.448 1-1v-1h-1v1h-1v-1zm5 0v4h1v-1h1v1h1v-1c-.001-.176-.048-.348-.137-.5.089-.152.136-.324.137-.5v-1c0-.552-.448-1-1-1zm1 1h1v1h-1z" fill="#fc7f7f"/></svg> \ No newline at end of file
diff --git a/editor/import/3d/post_import_plugin_skeleton_rest_fixer.cpp b/editor/import/3d/post_import_plugin_skeleton_rest_fixer.cpp
index 251834f42f..44016292b1 100644
--- a/editor/import/3d/post_import_plugin_skeleton_rest_fixer.cpp
+++ b/editor/import/3d/post_import_plugin_skeleton_rest_fixer.cpp
@@ -109,7 +109,7 @@ void PostImportPluginSkeletonRestFixer::internal_process(InternalImportCategory
// Apply node transforms.
if (bool(p_options["retarget/rest_fixer/apply_node_transforms"])) {
- Vector3 scl = global_transform.basis.get_scale_local();
+ Vector3 scl = global_transform.basis.get_scale_global();
Vector<int> bones_to_process = src_skeleton->get_parentless_bones();
for (int i = 0; i < bones_to_process.size(); i++) {
@@ -674,7 +674,7 @@ void PostImportPluginSkeletonRestFixer::internal_process(InternalImportCategory
int bone_idx = src_skeleton->find_bone(bn);
if (bone_idx >= 0) {
Transform3D adjust_transform = src_skeleton->get_bone_global_rest(bone_idx).affine_inverse() * silhouette_diff[bone_idx].affine_inverse() * pre_silhouette_skeleton_global_rest[bone_idx];
- adjust_transform.scale(global_transform.basis.get_scale_local());
+ adjust_transform.scale(global_transform.basis.get_scale_global());
skin->set_bind_pose(i, adjust_transform * skin->get_bind_pose(i));
}
}
@@ -691,7 +691,7 @@ void PostImportPluginSkeletonRestFixer::internal_process(InternalImportCategory
}
ERR_CONTINUE(bone_idx < 0 || bone_idx >= src_skeleton->get_bone_count());
Transform3D adjust_transform = src_skeleton->get_bone_global_rest(bone_idx).affine_inverse() * silhouette_diff[bone_idx].affine_inverse() * pre_silhouette_skeleton_global_rest[bone_idx];
- adjust_transform.scale(global_transform.basis.get_scale_local());
+ adjust_transform.scale(global_transform.basis.get_scale_global());
TypedArray<Node> child_nodes = attachment->get_children();
while (child_nodes.size()) {
diff --git a/editor/import/3d/resource_importer_obj.cpp b/editor/import/3d/resource_importer_obj.cpp
index 242b483b51..f5bf60175a 100644
--- a/editor/import/3d/resource_importer_obj.cpp
+++ b/editor/import/3d/resource_importer_obj.cpp
@@ -497,7 +497,7 @@ static Error _parse_obj(const String &p_path, List<Ref<ImporterMesh>> &r_meshes,
}
}
- if (p_single_mesh) {
+ if (p_single_mesh && mesh->get_surface_count() > 0) {
r_meshes.push_back(mesh);
}
diff --git a/editor/plugins/gdextension_export_plugin.h b/editor/plugins/gdextension_export_plugin.h
index 28080ed559..da136b70ae 100644
--- a/editor/plugins/gdextension_export_plugin.h
+++ b/editor/plugins/gdextension_export_plugin.h
@@ -129,34 +129,9 @@ void GDExtensionExportPlugin::_export_file(const String &p_path, const String &p
ERR_FAIL_MSG(vformat("No suitable library found for GDExtension: %s. Possible feature flags for your platform: %s", p_path, String(", ").join(features_vector)));
}
- List<String> dependencies;
- if (config->has_section("dependencies")) {
- config->get_section_keys("dependencies", &dependencies);
- }
-
- for (const String &E : dependencies) {
- Vector<String> dependency_tags = E.split(".");
- bool all_tags_met = true;
- for (int i = 0; i < dependency_tags.size(); i++) {
- String tag = dependency_tags[i].strip_edges();
- if (!p_features.has(tag)) {
- all_tags_met = false;
- break;
- }
- }
-
- if (all_tags_met) {
- Dictionary dependency = config->get_value("dependencies", E);
- for (const Variant *key = dependency.next(nullptr); key; key = dependency.next(key)) {
- String dependency_path = *key;
- String target_path = dependency[*key];
- if (dependency_path.is_relative_path()) {
- dependency_path = p_path.get_base_dir().path_join(dependency_path);
- }
- add_shared_object(dependency_path, dependency_tags, target_path);
- }
- break;
- }
+ Vector<SharedObject> dependencies_shared_objects = GDExtension::find_extension_dependencies(p_path, config, [p_features](String p_feature) { return p_features.has(p_feature); });
+ for (const SharedObject &shared_object : dependencies_shared_objects) {
+ _add_shared_object(shared_object);
}
}
}
diff --git a/editor/plugins/sprite_2d_editor_plugin.cpp b/editor/plugins/sprite_2d_editor_plugin.cpp
index 07d6ed4185..34a0df5906 100644
--- a/editor/plugins/sprite_2d_editor_plugin.cpp
+++ b/editor/plugins/sprite_2d_editor_plugin.cpp
@@ -46,7 +46,9 @@
#include "scene/gui/menu_button.h"
#include "scene/gui/panel.h"
#include "scene/gui/view_panner.h"
-#include "thirdparty/misc/clipper.hpp"
+#include "thirdparty/clipper2/include/clipper2/clipper.h"
+
+#define PRECISION 1
void Sprite2DEditor::_node_removed(Node *p_node) {
if (p_node == node) {
@@ -59,58 +61,39 @@ void Sprite2DEditor::edit(Sprite2D *p_sprite) {
node = p_sprite;
}
-#define PRECISION 10.0
-
Vector<Vector2> expand(const Vector<Vector2> &points, const Rect2i &rect, float epsilon = 2.0) {
int size = points.size();
ERR_FAIL_COND_V(size < 2, Vector<Vector2>());
- ClipperLib::Path subj;
- ClipperLib::PolyTree solution;
- ClipperLib::PolyTree out;
-
+ Clipper2Lib::PathD subj(points.size());
for (int i = 0; i < points.size(); i++) {
- subj << ClipperLib::IntPoint(points[i].x * PRECISION, points[i].y * PRECISION);
+ subj[i] = Clipper2Lib::PointD(points[i].x, points[i].y);
}
- ClipperLib::ClipperOffset co;
- co.AddPath(subj, ClipperLib::jtMiter, ClipperLib::etClosedPolygon);
- co.Execute(solution, epsilon * PRECISION);
- ClipperLib::PolyNode *p = solution.GetFirst();
+ Clipper2Lib::PathsD solution = Clipper2Lib::InflatePaths({ subj }, epsilon, Clipper2Lib::JoinType::Miter, Clipper2Lib::EndType::Polygon, 2.0, PRECISION, 0.0);
+ // Here the miter_limit = 2.0 and arc_tolerance = 0.0 are Clipper2 defaults,
+ // and PRECISION is used to scale points up internally, to attain the desired precision.
- ERR_FAIL_NULL_V(p, points);
+ ERR_FAIL_COND_V(solution.size() == 0, points);
- while (p->IsHole()) {
- p = p->GetNext();
- }
+ // Clamp into the specified rect.
+ Clipper2Lib::RectD clamp(rect.position.x,
+ rect.position.y,
+ rect.position.x + rect.size.width,
+ rect.position.y + rect.size.height);
+ Clipper2Lib::PathsD out = Clipper2Lib::RectClip(clamp, solution[0], PRECISION);
+ // Here PRECISION is used to scale points up internally, to attain the desired precision.
- //turn the result into simply polygon (AKA, fix overlap)
-
- //clamp into the specified rect
- ClipperLib::Clipper cl;
- cl.StrictlySimple(true);
- cl.AddPath(p->Contour, ClipperLib::ptSubject, true);
- //create the clipping rect
- ClipperLib::Path clamp;
- clamp.push_back(ClipperLib::IntPoint(0, 0));
- clamp.push_back(ClipperLib::IntPoint(rect.size.width * PRECISION, 0));
- clamp.push_back(ClipperLib::IntPoint(rect.size.width * PRECISION, rect.size.height * PRECISION));
- clamp.push_back(ClipperLib::IntPoint(0, rect.size.height * PRECISION));
- cl.AddPath(clamp, ClipperLib::ptClip, true);
- cl.Execute(ClipperLib::ctIntersection, out);
+ ERR_FAIL_COND_V(out.size() == 0, points);
- Vector<Vector2> outPoints;
- ClipperLib::PolyNode *p2 = out.GetFirst();
- ERR_FAIL_NULL_V(p2, points);
+ const Clipper2Lib::PathD &p2 = out[0];
- while (p2->IsHole()) {
- p2 = p2->GetNext();
- }
+ Vector<Vector2> outPoints;
- int lasti = p2->Contour.size() - 1;
- Vector2 prev = Vector2(p2->Contour[lasti].X / PRECISION, p2->Contour[lasti].Y / PRECISION);
- for (uint64_t i = 0; i < p2->Contour.size(); i++) {
- Vector2 cur = Vector2(p2->Contour[i].X / PRECISION, p2->Contour[i].Y / PRECISION);
+ int lasti = p2.size() - 1;
+ Vector2 prev = Vector2(p2[lasti].x, p2[lasti].y);
+ for (uint64_t i = 0; i < p2.size(); i++) {
+ Vector2 cur = Vector2(p2[i].x, p2[i].y);
if (cur.distance_to(prev) > 0.5) {
outPoints.push_back(cur);
prev = cur;
diff --git a/methods.py b/methods.py
index 8498310bf5..f0e51c7d6f 100644
--- a/methods.py
+++ b/methods.py
@@ -926,7 +926,11 @@ def get_compiler_version(env):
# Not using -dumpversion as some GCC distros only return major, and
# Clang used to return hardcoded 4.2.1: # https://reviews.llvm.org/D56803
try:
- version = subprocess.check_output([env.subst(env["CXX"]), "--version"]).strip().decode("utf-8")
+ version = (
+ subprocess.check_output([env.subst(env["CXX"]), "--version"], shell=(os.name == "nt"))
+ .strip()
+ .decode("utf-8")
+ )
except (subprocess.CalledProcessError, OSError):
print("Couldn't parse CXX environment variable to infer compiler version.")
return ret
diff --git a/misc/extension_api_validation/4.2-stable.expected b/misc/extension_api_validation/4.2-stable.expected
index 43349ae6ef..de60e5114c 100644
--- a/misc/extension_api_validation/4.2-stable.expected
+++ b/misc/extension_api_validation/4.2-stable.expected
@@ -275,6 +275,7 @@ Validate extension JSON: API was removed: classes/Skeleton3D/signals/bone_pose_c
They have been replaced by a safer API due to performance concerns. Compatibility method registered.
+
GH-90747
--------
Validate extension JSON: API was removed: classes/NavigationRegion2D/methods/get_avoidance_layers
@@ -287,3 +288,23 @@ Validate extension JSON: API was removed: classes/NavigationRegion2D/methods/get
Validate extension JSON: API was removed: classes/NavigationRegion2D/properties/constrain_avoidance
Experimental NavigationRegion2D feature "constrain_avoidance" was discontinued with no replacement.
+
+
+GH-90645
+--------
+Validate extension JSON: API was removed: classes/XRPositionalTracker/methods/get_tracker_desc
+Validate extension JSON: API was removed: classes/XRPositionalTracker/methods/get_tracker_name
+Validate extension JSON: API was removed: classes/XRPositionalTracker/methods/get_tracker_type
+Validate extension JSON: API was removed: classes/XRPositionalTracker/methods/set_tracker_desc
+Validate extension JSON: API was removed: classes/XRPositionalTracker/methods/set_tracker_name
+Validate extension JSON: API was removed: classes/XRPositionalTracker/methods/set_tracker_type
+Validate extension JSON: API was removed: classes/XRPositionalTracker/properties/description
+Validate extension JSON: API was removed: classes/XRPositionalTracker/properties/name
+Validate extension JSON: API was removed: classes/XRPositionalTracker/properties/type
+Validate extension JSON: Error: Field 'classes/WebXRInterface/methods/get_input_source_tracker/return_value': type changed value in new API, from "XRPositionalTracker" to "XRControllerTracker".
+Validate extension JSON: Error: Field 'classes/XRServer/methods/add_tracker/arguments/0': type changed value in new API, from "XRPositionalTracker" to "XRTracker".
+Validate extension JSON: Error: Field 'classes/XRServer/methods/get_tracker/return_value': type changed value in new API, from "XRPositionalTracker" to "XRTracker".
+Validate extension JSON: Error: Field 'classes/XRServer/methods/remove_tracker/arguments/0': type changed value in new API, from "XRPositionalTracker" to "XRTracker".
+
+All trackers now have an XRTracker base, and the XRServer uses the XRTracker type.
+
diff --git a/modules/cvtt/image_compress_cvtt.cpp b/modules/cvtt/image_compress_cvtt.cpp
index ad70772270..e9a7009d7c 100644
--- a/modules/cvtt/image_compress_cvtt.cpp
+++ b/modules/cvtt/image_compress_cvtt.cpp
@@ -302,8 +302,6 @@ void image_decompress_cvtt(Image *p_image) {
int y_end = y_start + 4;
for (int x_start = 0; x_start < w; x_start += 4 * cvtt::NumParallelBlocks) {
- int x_end = x_start + 4 * cvtt::NumParallelBlocks;
-
uint8_t input_blocks[16 * cvtt::NumParallelBlocks];
memset(input_blocks, 0, sizeof(input_blocks));
@@ -315,6 +313,8 @@ void image_decompress_cvtt(Image *p_image) {
memcpy(input_blocks, in_bytes, 16 * num_real_blocks);
in_bytes += 16 * num_real_blocks;
+ int x_end = x_start + 4 * num_real_blocks;
+
if (is_hdr) {
if (is_signed) {
cvtt::Kernels::DecodeBC6HS(output_blocks_hdr, input_blocks);
diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp
index eb45ade285..e1ce41edd5 100644
--- a/modules/mono/editor/bindings_generator.cpp
+++ b/modules/mono/editor/bindings_generator.cpp
@@ -2581,6 +2581,10 @@ Error BindingsGenerator::_generate_cs_property(const BindingsGenerator::TypeInte
p_output.append("\")]");
}
+ if (p_iprop.is_hidden) {
+ p_output.append(MEMBER_BEGIN "[EditorBrowsable(EditorBrowsableState.Never)]");
+ }
+
p_output.append(MEMBER_BEGIN "public ");
if (prop_allowed_inherited_member_hiding.has(p_itype.proxy_name + "." + p_iprop.proxy_name)) {
@@ -2840,7 +2844,7 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf
p_output.append("\")]");
}
- if (p_imethod.is_compat) {
+ if (p_imethod.is_hidden) {
p_output.append(MEMBER_BEGIN "[EditorBrowsable(EditorBrowsableState.Never)]");
}
@@ -3654,11 +3658,16 @@ bool BindingsGenerator::_populate_object_type_interfaces() {
iprop.setter = ClassDB::get_property_setter(type_cname, iprop.cname);
iprop.getter = ClassDB::get_property_getter(type_cname, iprop.cname);
- if (iprop.setter != StringName()) {
- accessor_methods[iprop.setter] = iprop.cname;
- }
- if (iprop.getter != StringName()) {
- accessor_methods[iprop.getter] = iprop.cname;
+ // If the property is internal hide it; otherwise, hide the getter and setter.
+ if (property.usage & PROPERTY_USAGE_INTERNAL) {
+ iprop.is_hidden = true;
+ } else {
+ if (iprop.setter != StringName()) {
+ accessor_methods[iprop.setter] = iprop.cname;
+ }
+ if (iprop.getter != StringName()) {
+ accessor_methods[iprop.getter] = iprop.cname;
+ }
}
bool valid = false;
@@ -3860,10 +3869,10 @@ bool BindingsGenerator::_populate_object_type_interfaces() {
HashMap<StringName, StringName>::Iterator accessor = accessor_methods.find(imethod.cname);
if (accessor) {
- // We only make internal an accessor method if it's in the same class as the property.
+ // We only hide an accessor method if it's in the same class as the property.
// It's easier this way, but also we don't know if an accessor method in a different class
// could have other purposes, so better leave those untouched.
- imethod.is_internal = true;
+ imethod.is_hidden = true;
}
if (itype.class_doc) {
@@ -3892,6 +3901,7 @@ bool BindingsGenerator::_populate_object_type_interfaces() {
// after all the non-compat methods have been added. The compat methods are added in
// reverse so the most recently added ones take precedence over older compat methods.
if (imethod.is_compat) {
+ imethod.is_hidden = true;
compat_methods.push_front(imethod);
continue;
}
diff --git a/modules/mono/editor/bindings_generator.h b/modules/mono/editor/bindings_generator.h
index bb0ba0cb00..a397dcb026 100644
--- a/modules/mono/editor/bindings_generator.h
+++ b/modules/mono/editor/bindings_generator.h
@@ -88,6 +88,14 @@ class BindingsGenerator {
StringName setter;
StringName getter;
+ /**
+ * Determines if the property will be hidden with the [EditorBrowsable(EditorBrowsableState.Never)]
+ * attribute.
+ * We do this for propertyies that have the PROPERTY_USAGE_INTERNAL flag, because they are not meant
+ * to be exposed to scripting but we can't remove them to prevent breaking compatibility.
+ */
+ bool is_hidden = false;
+
const DocData::PropertyDoc *prop_doc;
bool is_deprecated = false;
@@ -180,6 +188,14 @@ class BindingsGenerator {
bool is_internal = false;
/**
+ * Determines if the method will be hidden with the [EditorBrowsable(EditorBrowsableState.Never)]
+ * attribute.
+ * We do this for methods that we don't want to expose but need to be public to prevent breaking
+ * compat (i.e: methods with 'is_compat' set to true.)
+ */
+ bool is_hidden = false;
+
+ /**
* Determines if the method is a compatibility method added to avoid breaking binary compatibility.
* These methods will be generated but hidden and are considered deprecated.
*/
diff --git a/modules/openxr/doc_classes/OpenXRInterface.xml b/modules/openxr/doc_classes/OpenXRInterface.xml
index 1136ac1b69..05dff7d6ae 100644
--- a/modules/openxr/doc_classes/OpenXRInterface.xml
+++ b/modules/openxr/doc_classes/OpenXRInterface.xml
@@ -23,7 +23,7 @@
Returns display refresh rates supported by the current HMD. Only returned if this feature is supported by the OpenXR runtime and after the interface has been initialized.
</description>
</method>
- <method name="get_hand_joint_angular_velocity" qualifiers="const" deprecated="Use [method XRHandTracker.get_hand_joint_angular_velocity] obtained from [method XRServer.get_hand_tracker] instead.">
+ <method name="get_hand_joint_angular_velocity" qualifiers="const" deprecated="Use [method XRHandTracker.get_hand_joint_angular_velocity] obtained from [method XRServer.get_tracker] instead.">
<return type="Vector3" />
<param index="0" name="hand" type="int" enum="OpenXRInterface.Hand" />
<param index="1" name="joint" type="int" enum="OpenXRInterface.HandJoints" />
@@ -31,7 +31,7 @@
If handtracking is enabled, returns the angular velocity of a joint ([param joint]) of a hand ([param hand]) as provided by OpenXR. This is relative to [XROrigin3D]!
</description>
</method>
- <method name="get_hand_joint_flags" qualifiers="const" deprecated="Use [method XRHandTracker.get_hand_joint_flags] obtained from [method XRServer.get_hand_tracker] instead.">
+ <method name="get_hand_joint_flags" qualifiers="const" deprecated="Use [method XRHandTracker.get_hand_joint_flags] obtained from [method XRServer.get_tracker] instead.">
<return type="int" enum="OpenXRInterface.HandJointFlags" is_bitfield="true" />
<param index="0" name="hand" type="int" enum="OpenXRInterface.Hand" />
<param index="1" name="joint" type="int" enum="OpenXRInterface.HandJoints" />
@@ -39,7 +39,7 @@
If handtracking is enabled, returns flags that inform us of the validity of the tracking data.
</description>
</method>
- <method name="get_hand_joint_linear_velocity" qualifiers="const" deprecated="Use [method XRHandTracker.get_hand_joint_linear_velocity] obtained from [method XRServer.get_hand_tracker] instead.">
+ <method name="get_hand_joint_linear_velocity" qualifiers="const" deprecated="Use [method XRHandTracker.get_hand_joint_linear_velocity] obtained from [method XRServer.get_tracker] instead.">
<return type="Vector3" />
<param index="0" name="hand" type="int" enum="OpenXRInterface.Hand" />
<param index="1" name="joint" type="int" enum="OpenXRInterface.HandJoints" />
@@ -47,7 +47,7 @@
If handtracking is enabled, returns the linear velocity of a joint ([param joint]) of a hand ([param hand]) as provided by OpenXR. This is relative to [XROrigin3D] without worldscale applied!
</description>
</method>
- <method name="get_hand_joint_position" qualifiers="const" deprecated="Use [method XRHandTracker.get_hand_joint_transform] obtained from [method XRServer.get_hand_tracker] instead.">
+ <method name="get_hand_joint_position" qualifiers="const" deprecated="Use [method XRHandTracker.get_hand_joint_transform] obtained from [method XRServer.get_tracker] instead.">
<return type="Vector3" />
<param index="0" name="hand" type="int" enum="OpenXRInterface.Hand" />
<param index="1" name="joint" type="int" enum="OpenXRInterface.HandJoints" />
@@ -55,7 +55,7 @@
If handtracking is enabled, returns the position of a joint ([param joint]) of a hand ([param hand]) as provided by OpenXR. This is relative to [XROrigin3D] without worldscale applied!
</description>
</method>
- <method name="get_hand_joint_radius" qualifiers="const" deprecated="Use [method XRHandTracker.get_hand_joint_radius] obtained from [method XRServer.get_hand_tracker] instead.">
+ <method name="get_hand_joint_radius" qualifiers="const" deprecated="Use [method XRHandTracker.get_hand_joint_radius] obtained from [method XRServer.get_tracker] instead.">
<return type="float" />
<param index="0" name="hand" type="int" enum="OpenXRInterface.Hand" />
<param index="1" name="joint" type="int" enum="OpenXRInterface.HandJoints" />
@@ -63,7 +63,7 @@
If handtracking is enabled, returns the radius of a joint ([param joint]) of a hand ([param hand]) as provided by OpenXR. This is without worldscale applied!
</description>
</method>
- <method name="get_hand_joint_rotation" qualifiers="const" deprecated="Use [method XRHandTracker.get_hand_joint_transform] obtained from [method XRServer.get_hand_tracker] instead.">
+ <method name="get_hand_joint_rotation" qualifiers="const" deprecated="Use [method XRHandTracker.get_hand_joint_transform] obtained from [method XRServer.get_tracker] instead.">
<return type="Quaternion" />
<param index="0" name="hand" type="int" enum="OpenXRInterface.Hand" />
<param index="1" name="joint" type="int" enum="OpenXRInterface.HandJoints" />
@@ -71,7 +71,7 @@
If handtracking is enabled, returns the rotation of a joint ([param joint]) of a hand ([param hand]) as provided by OpenXR.
</description>
</method>
- <method name="get_hand_tracking_source" qualifiers="const" deprecated="Use [member XRHandTracker.hand_tracking_source] obtained from [method XRServer.get_hand_tracker] instead.">
+ <method name="get_hand_tracking_source" qualifiers="const" deprecated="Use [member XRHandTracker.hand_tracking_source] obtained from [method XRServer.get_tracker] instead.">
<return type="int" enum="OpenXRInterface.HandTrackedSource" />
<param index="0" name="hand" type="int" enum="OpenXRInterface.Hand" />
<description>
diff --git a/modules/openxr/extensions/openxr_hand_tracking_extension.cpp b/modules/openxr/extensions/openxr_hand_tracking_extension.cpp
index b3c20ef8b9..f8cc3d1d8c 100644
--- a/modules/openxr/extensions/openxr_hand_tracking_extension.cpp
+++ b/modules/openxr/extensions/openxr_hand_tracking_extension.cpp
@@ -196,7 +196,8 @@ void OpenXRHandTrackingExtension::on_process() {
Ref<XRHandTracker> godot_tracker;
godot_tracker.instantiate();
godot_tracker->set_hand(i == 0 ? XRHandTracker::HAND_LEFT : XRHandTracker::HAND_RIGHT);
- XRServer::get_singleton()->add_hand_tracker(i == 0 ? "/user/left" : "/user/right", godot_tracker);
+ godot_tracker->set_tracker_name(i == 0 ? "/user/hand_tracker/left" : "/user/hand_tracker/right");
+ XRServer::get_singleton()->add_tracker(godot_tracker);
hand_trackers[i].godot_tracker = godot_tracker;
hand_trackers[i].is_initialized = true;
@@ -229,8 +230,7 @@ void OpenXRHandTrackingExtension::on_process() {
// For some reason an inactive controller isn't coming back as inactive but has coordinates either as NAN or very large
const XrPosef &palm = hand_trackers[i].joint_locations[XR_HAND_JOINT_PALM_EXT].pose;
- if (
- !hand_trackers[i].locations.isActive || isnan(palm.position.x) || palm.position.x < -1000000.00 || palm.position.x > 1000000.00) {
+ if (!hand_trackers[i].locations.isActive || isnan(palm.position.x) || palm.position.x < -1000000.00 || palm.position.x > 1000000.00) {
hand_trackers[i].locations.isActive = false; // workaround, make sure its inactive
}
@@ -249,6 +249,8 @@ void OpenXRHandTrackingExtension::on_process() {
const XrPosef &pose = location.pose;
Transform3D transform;
+ Vector3 linear_velocity;
+ Vector3 angular_velocity;
BitField<XRHandTracker::HandJointFlags> flags;
if (location.locationFlags & XR_SPACE_LOCATION_ORIENTATION_VALID_BIT) {
@@ -269,27 +271,34 @@ void OpenXRHandTrackingExtension::on_process() {
}
if (location.locationFlags & XR_SPACE_VELOCITY_LINEAR_VALID_BIT) {
flags.set_flag(XRHandTracker::HAND_JOINT_FLAG_LINEAR_VELOCITY_VALID);
- godot_tracker->set_hand_joint_linear_velocity((XRHandTracker::HandJoint)joint, Vector3(velocity.linearVelocity.x, velocity.linearVelocity.y, velocity.linearVelocity.z));
+ linear_velocity = Vector3(velocity.linearVelocity.x, velocity.linearVelocity.y, velocity.linearVelocity.z);
+ godot_tracker->set_hand_joint_linear_velocity((XRHandTracker::HandJoint)joint, linear_velocity);
}
if (location.locationFlags & XR_SPACE_VELOCITY_ANGULAR_VALID_BIT) {
flags.set_flag(XRHandTracker::HAND_JOINT_FLAG_ANGULAR_VELOCITY_VALID);
- godot_tracker->set_hand_joint_angular_velocity((XRHandTracker::HandJoint)joint, Vector3(velocity.angularVelocity.x, velocity.angularVelocity.y, velocity.angularVelocity.z));
+ angular_velocity = Vector3(velocity.angularVelocity.x, velocity.angularVelocity.y, velocity.angularVelocity.z);
+ godot_tracker->set_hand_joint_angular_velocity((XRHandTracker::HandJoint)joint, angular_velocity);
}
godot_tracker->set_hand_joint_flags((XRHandTracker::HandJoint)joint, flags);
godot_tracker->set_hand_joint_transform((XRHandTracker::HandJoint)joint, transform);
godot_tracker->set_hand_joint_radius((XRHandTracker::HandJoint)joint, location.radius);
- XRHandTracker::HandTrackingSource source = XRHandTracker::HAND_TRACKING_SOURCE_UNKNOWN;
- if (data_source.dataSource == XR_HAND_TRACKING_DATA_SOURCE_UNOBSTRUCTED_EXT) {
- source = XRHandTracker::HAND_TRACKING_SOURCE_UNOBSTRUCTED;
- } else if (data_source.dataSource == XR_HAND_TRACKING_DATA_SOURCE_CONTROLLER_EXT) {
- source = XRHandTracker::HAND_TRACKING_SOURCE_CONTROLLER;
+ if (joint == XR_HAND_JOINT_PALM_EXT) {
+ XRHandTracker::HandTrackingSource source = XRHandTracker::HAND_TRACKING_SOURCE_UNKNOWN;
+ if (data_source.dataSource == XR_HAND_TRACKING_DATA_SOURCE_UNOBSTRUCTED_EXT) {
+ source = XRHandTracker::HAND_TRACKING_SOURCE_UNOBSTRUCTED;
+ } else if (data_source.dataSource == XR_HAND_TRACKING_DATA_SOURCE_CONTROLLER_EXT) {
+ source = XRHandTracker::HAND_TRACKING_SOURCE_CONTROLLER;
+ }
+
+ godot_tracker->set_hand_tracking_source(source);
+ godot_tracker->set_pose("default", transform, linear_velocity, angular_velocity);
}
- godot_tracker->set_hand_tracking_source(source);
}
} else {
godot_tracker->set_has_tracking_data(false);
+ godot_tracker->invalidate_pose("default");
}
}
}
@@ -311,7 +320,7 @@ void OpenXRHandTrackingExtension::cleanup_hand_tracking() {
hand_trackers[i].is_initialized = false;
hand_trackers[i].hand_tracker = XR_NULL_HANDLE;
- XRServer::get_singleton()->remove_hand_tracker(i == 0 ? "/user/left" : "/user/right");
+ XRServer::get_singleton()->remove_tracker(hand_trackers[i].godot_tracker);
}
}
}
diff --git a/modules/openxr/extensions/platform/openxr_android_extension.cpp b/modules/openxr/extensions/platform/openxr_android_extension.cpp
index de542828c3..04404923ef 100644
--- a/modules/openxr/extensions/platform/openxr_android_extension.cpp
+++ b/modules/openxr/extensions/platform/openxr_android_extension.cpp
@@ -36,7 +36,6 @@
#include "os_android.h"
#include "thread_jandroid.h"
-#include <jni.h>
#include <openxr/openxr.h>
#include <openxr/openxr_platform.h>
@@ -48,6 +47,12 @@ OpenXRAndroidExtension *OpenXRAndroidExtension::get_singleton() {
OpenXRAndroidExtension::OpenXRAndroidExtension() {
singleton = this;
+
+ JNIEnv *env = get_jni_env();
+ ERR_FAIL_NULL(env);
+
+ env->GetJavaVM(&vm);
+ activity_object = env->NewGlobalRef(static_cast<OS_Android *>(OS::get_singleton())->get_godot_java()->get_activity());
}
HashMap<String, bool *> OpenXRAndroidExtension::get_requested_extensions() {
@@ -66,11 +71,6 @@ void OpenXRAndroidExtension::on_before_instance_created() {
}
loader_init_extension_available = true;
- JNIEnv *env = get_jni_env();
- JavaVM *vm;
- env->GetJavaVM(&vm);
- jobject activity_object = env->NewGlobalRef(static_cast<OS_Android *>(OS::get_singleton())->get_godot_java()->get_activity());
-
XrLoaderInitInfoAndroidKHR loader_init_info_android = {
.type = XR_TYPE_LOADER_INIT_INFO_ANDROID_KHR,
.next = nullptr,
@@ -93,11 +93,6 @@ void *OpenXRAndroidExtension::set_instance_create_info_and_get_next_pointer(void
return nullptr;
}
- JNIEnv *env = get_jni_env();
- JavaVM *vm;
- env->GetJavaVM(&vm);
- jobject activity_object = env->NewGlobalRef(static_cast<OS_Android *>(OS::get_singleton())->get_godot_java()->get_activity());
-
instance_create_info = {
.type = XR_TYPE_INSTANCE_CREATE_INFO_ANDROID_KHR,
.next = p_next_pointer,
@@ -109,4 +104,9 @@ void *OpenXRAndroidExtension::set_instance_create_info_and_get_next_pointer(void
OpenXRAndroidExtension::~OpenXRAndroidExtension() {
singleton = nullptr;
+
+ JNIEnv *env = get_jni_env();
+ ERR_FAIL_NULL(env);
+
+ env->DeleteGlobalRef(activity_object);
}
diff --git a/modules/openxr/extensions/platform/openxr_android_extension.h b/modules/openxr/extensions/platform/openxr_android_extension.h
index e51b5824e8..61f4b02ab6 100644
--- a/modules/openxr/extensions/platform/openxr_android_extension.h
+++ b/modules/openxr/extensions/platform/openxr_android_extension.h
@@ -34,6 +34,8 @@
#include "../../util.h"
#include "../openxr_extension_wrapper.h"
+#include <jni.h>
+
class OpenXRAndroidExtension : public OpenXRExtensionWrapper {
public:
static OpenXRAndroidExtension *get_singleton();
@@ -49,6 +51,8 @@ public:
private:
static OpenXRAndroidExtension *singleton;
+ JavaVM *vm;
+ jobject activity_object;
bool loader_init_extension_available = false;
bool create_instance_extension_available = false;
diff --git a/modules/openxr/openxr_interface.cpp b/modules/openxr/openxr_interface.cpp
index 7eb9a6ebe1..aa68441f03 100644
--- a/modules/openxr/openxr_interface.cpp
+++ b/modules/openxr/openxr_interface.cpp
@@ -35,6 +35,7 @@
#include "servers/rendering/rendering_server_globals.h"
#include "extensions/openxr_eye_gaze_interaction.h"
+#include "thirdparty/openxr/include/openxr/openxr.h"
void OpenXRInterface::_bind_methods() {
// lifecycle signals
@@ -154,9 +155,14 @@ PackedStringArray OpenXRInterface::get_suggested_tracker_names() const {
// These are hardcoded in OpenXR, note that they will only be available if added to our action map
PackedStringArray arr = {
- "left_hand", // /user/hand/left is mapped to our defaults
- "right_hand", // /user/hand/right is mapped to our defaults
- "/user/treadmill",
+ "head", // XRPositionalTracker for the users head (Mapped from OpenXR /user/head)
+ "left_hand", // XRControllerTracker for the users left hand (Mapped from OpenXR /user/hand/left)
+ "right_hand", // XRControllerTracker for the users right hand (Mapped from OpenXR /user/hand/right)
+ "/user/hand_tracker/left", // XRHandTracker for the users left hand
+ "/user/hand_tracker/right", // XRHandTracker for the users right hand
+ "/user/body_tracker", // XRBodyTracker for the users body
+ "/user/face_tracker", // XRFaceTracker for the users face
+ "/user/treadmill"
};
for (OpenXRExtensionWrapper *wrapper : OpenXRAPI::get_singleton()->get_registered_extension_wrappers()) {
@@ -430,34 +436,31 @@ OpenXRInterface::Tracker *OpenXRInterface::find_tracker(const String &p_tracker_
RID tracker_rid = openxr_api->tracker_create(p_tracker_name);
ERR_FAIL_COND_V(tracker_rid.is_null(), nullptr);
- // create our positional tracker
- Ref<XRPositionalTracker> positional_tracker;
- positional_tracker.instantiate();
+ // Create our controller tracker.
+ Ref<XRControllerTracker> controller_tracker;
+ controller_tracker.instantiate();
// We have standardized some names to make things nicer to the user so lets recognize the toplevel paths related to these.
if (p_tracker_name == "/user/hand/left") {
- positional_tracker->set_tracker_type(XRServer::TRACKER_CONTROLLER);
- positional_tracker->set_tracker_name("left_hand");
- positional_tracker->set_tracker_desc("Left hand controller");
- positional_tracker->set_tracker_hand(XRPositionalTracker::TRACKER_HAND_LEFT);
+ controller_tracker->set_tracker_name("left_hand");
+ controller_tracker->set_tracker_desc("Left hand controller");
+ controller_tracker->set_tracker_hand(XRPositionalTracker::TRACKER_HAND_LEFT);
} else if (p_tracker_name == "/user/hand/right") {
- positional_tracker->set_tracker_type(XRServer::TRACKER_CONTROLLER);
- positional_tracker->set_tracker_name("right_hand");
- positional_tracker->set_tracker_desc("Right hand controller");
- positional_tracker->set_tracker_hand(XRPositionalTracker::TRACKER_HAND_RIGHT);
+ controller_tracker->set_tracker_name("right_hand");
+ controller_tracker->set_tracker_desc("Right hand controller");
+ controller_tracker->set_tracker_hand(XRPositionalTracker::TRACKER_HAND_RIGHT);
} else {
- positional_tracker->set_tracker_type(XRServer::TRACKER_CONTROLLER);
- positional_tracker->set_tracker_name(p_tracker_name);
- positional_tracker->set_tracker_desc(p_tracker_name);
+ controller_tracker->set_tracker_name(p_tracker_name);
+ controller_tracker->set_tracker_desc(p_tracker_name);
}
- positional_tracker->set_tracker_profile(INTERACTION_PROFILE_NONE);
- xr_server->add_tracker(positional_tracker);
+ controller_tracker->set_tracker_profile(INTERACTION_PROFILE_NONE);
+ xr_server->add_tracker(controller_tracker);
// create a new entry
tracker = memnew(Tracker);
tracker->tracker_name = p_tracker_name;
tracker->tracker_rid = tracker_rid;
- tracker->positional_tracker = positional_tracker;
+ tracker->controller_tracker = controller_tracker;
tracker->interaction_profile = RID();
trackers.push_back(tracker);
@@ -477,17 +480,17 @@ void OpenXRInterface::tracker_profile_changed(RID p_tracker, RID p_interaction_p
if (p_interaction_profile.is_null()) {
print_verbose("OpenXR: Interaction profile for " + tracker->tracker_name + " changed to " + INTERACTION_PROFILE_NONE);
- tracker->positional_tracker->set_tracker_profile(INTERACTION_PROFILE_NONE);
+ tracker->controller_tracker->set_tracker_profile(INTERACTION_PROFILE_NONE);
} else {
String name = openxr_api->interaction_profile_get_name(p_interaction_profile);
print_verbose("OpenXR: Interaction profile for " + tracker->tracker_name + " changed to " + name);
- tracker->positional_tracker->set_tracker_profile(name);
+ tracker->controller_tracker->set_tracker_profile(name);
}
}
void OpenXRInterface::handle_tracker(Tracker *p_tracker) {
ERR_FAIL_NULL(openxr_api);
- ERR_FAIL_COND(p_tracker->positional_tracker.is_null());
+ ERR_FAIL_COND(p_tracker->controller_tracker.is_null());
// Note, which actions are actually bound to inputs are handled by our interaction profiles however interaction
// profiles are suggested bindings for controller types we know about. OpenXR runtimes can stray away from these
@@ -506,15 +509,15 @@ void OpenXRInterface::handle_tracker(Tracker *p_tracker) {
switch (action->action_type) {
case OpenXRAction::OPENXR_ACTION_BOOL: {
bool pressed = openxr_api->get_action_bool(action->action_rid, p_tracker->tracker_rid);
- p_tracker->positional_tracker->set_input(action->action_name, Variant(pressed));
+ p_tracker->controller_tracker->set_input(action->action_name, Variant(pressed));
} break;
case OpenXRAction::OPENXR_ACTION_FLOAT: {
real_t value = openxr_api->get_action_float(action->action_rid, p_tracker->tracker_rid);
- p_tracker->positional_tracker->set_input(action->action_name, Variant(value));
+ p_tracker->controller_tracker->set_input(action->action_name, Variant(value));
} break;
case OpenXRAction::OPENXR_ACTION_VECTOR2: {
Vector2 value = openxr_api->get_action_vector2(action->action_rid, p_tracker->tracker_rid);
- p_tracker->positional_tracker->set_input(action->action_name, Variant(value));
+ p_tracker->controller_tracker->set_input(action->action_name, Variant(value));
} break;
case OpenXRAction::OPENXR_ACTION_POSE: {
Transform3D transform;
@@ -523,9 +526,9 @@ void OpenXRInterface::handle_tracker(Tracker *p_tracker) {
XRPose::TrackingConfidence confidence = openxr_api->get_action_pose(action->action_rid, p_tracker->tracker_rid, transform, linear, angular);
if (confidence != XRPose::XR_TRACKING_CONFIDENCE_NONE) {
- p_tracker->positional_tracker->set_pose(action->action_name, transform, linear, angular, confidence);
+ p_tracker->controller_tracker->set_pose(action->action_name, transform, linear, angular, confidence);
} else {
- p_tracker->positional_tracker->invalidate_pose(action->action_name);
+ p_tracker->controller_tracker->invalidate_pose(action->action_name);
}
} break;
default: {
@@ -567,8 +570,8 @@ void OpenXRInterface::free_trackers() {
Tracker *tracker = trackers[i];
openxr_api->tracker_free(tracker->tracker_rid);
- xr_server->remove_tracker(tracker->positional_tracker);
- tracker->positional_tracker.unref();
+ xr_server->remove_tracker(tracker->controller_tracker);
+ tracker->controller_tracker.unref();
memdelete(tracker);
}
@@ -1005,7 +1008,7 @@ void OpenXRInterface::handle_hand_tracking(const String &p_path, OpenXRHandTrack
OpenXRHandTrackingExtension *hand_tracking_ext = OpenXRHandTrackingExtension::get_singleton();
if (hand_tracking_ext && hand_tracking_ext->get_active()) {
OpenXRInterface::Tracker *tracker = find_tracker(p_path);
- if (tracker && tracker->positional_tracker.is_valid()) {
+ if (tracker && tracker->controller_tracker.is_valid()) {
XrSpaceLocationFlags location_flags = hand_tracking_ext->get_hand_joint_location_flags(p_hand, XR_HAND_JOINT_PALM_EXT);
if (location_flags & (XR_SPACE_LOCATION_ORIENTATION_VALID_BIT + XR_SPACE_LOCATION_POSITION_VALID_BIT)) {
@@ -1035,9 +1038,9 @@ void OpenXRInterface::handle_hand_tracking(const String &p_path, OpenXRHandTrack
angular_velocity = hand_tracking_ext->get_hand_joint_angular_velocity(p_hand, XR_HAND_JOINT_PALM_EXT);
}
- tracker->positional_tracker->set_pose("skeleton", transform, linear_velocity, angular_velocity, confidence);
+ tracker->controller_tracker->set_pose("skeleton", transform, linear_velocity, angular_velocity, confidence);
} else {
- tracker->positional_tracker->invalidate_pose("skeleton");
+ tracker->controller_tracker->invalidate_pose("skeleton");
}
}
}
diff --git a/modules/openxr/openxr_interface.h b/modules/openxr/openxr_interface.h
index 737f22d642..e916c7dac2 100644
--- a/modules/openxr/openxr_interface.h
+++ b/modules/openxr/openxr_interface.h
@@ -35,8 +35,8 @@
#include "extensions/openxr_hand_tracking_extension.h"
#include "openxr_api.h"
+#include "servers/xr/xr_controller_tracker.h"
#include "servers/xr/xr_interface.h"
-#include "servers/xr/xr_positional_tracker.h"
// declare some default strings
#define INTERACTION_PROFILE_NONE "/interaction_profiles/none"
@@ -73,7 +73,7 @@ private:
struct Tracker { // A tracker we've registered with OpenXR
String tracker_name; // Name of our tracker (can be altered from the action map)
Vector<Action *> actions; // Actions related to this tracker
- Ref<XRPositionalTracker> positional_tracker; // Our positional tracker object that holds our tracker state
+ Ref<XRControllerTracker> controller_tracker; // Our positional tracker object that holds our tracker state
RID tracker_rid; // RID of the tracker registered with our OpenXR API
RID interaction_profile; // RID of the interaction profile bound to this tracker (can be null)
};
diff --git a/modules/webxr/doc_classes/WebXRInterface.xml b/modules/webxr/doc_classes/WebXRInterface.xml
index caf7958f6b..9fd4511d2b 100644
--- a/modules/webxr/doc_classes/WebXRInterface.xml
+++ b/modules/webxr/doc_classes/WebXRInterface.xml
@@ -114,10 +114,10 @@
</description>
</method>
<method name="get_input_source_tracker" qualifiers="const">
- <return type="XRPositionalTracker" />
+ <return type="XRControllerTracker" />
<param index="0" name="input_source_id" type="int" />
<description>
- Gets an [XRPositionalTracker] for the given [param input_source_id].
+ Gets an [XRControllerTracker] for the given [param input_source_id].
In the context of WebXR, an input source can be an advanced VR controller like the Oculus Touch or Index controllers, or even a tap on the screen, a spoken voice command or a button press on the device itself. When a non-traditional input source is used, interpret the position and orientation of the [XRPositionalTracker] as a ray pointing at the object the user wishes to interact with.
Use this method to get information about the input source that triggered one of these signals:
- [signal selectstart]
diff --git a/modules/webxr/webxr_interface.compat.inc b/modules/webxr/webxr_interface.compat.inc
new file mode 100644
index 0000000000..97a9d44ca9
--- /dev/null
+++ b/modules/webxr/webxr_interface.compat.inc
@@ -0,0 +1,41 @@
+/**************************************************************************/
+/* webxr_interface.compat.inc */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#ifndef DISABLE_DEPRECATED
+
+Ref<XRPositionalTracker> WebXRInterface::_get_input_source_tracker_bind_compat_90645(int p_input_source_id) const {
+ return get_input_source_tracker(p_input_source_id);
+}
+
+void WebXRInterface::_bind_compatibility_methods() {
+ ClassDB::bind_compatibility_method(D_METHOD("get_input_source_tracker", "input_source_id"), &WebXRInterface::_get_input_source_tracker_bind_compat_90645);
+}
+
+#endif // DISABLE_DEPRECATED
diff --git a/modules/webxr/webxr_interface.cpp b/modules/webxr/webxr_interface.cpp
index c3efebef0f..4795fcdcd6 100644
--- a/modules/webxr/webxr_interface.cpp
+++ b/modules/webxr/webxr_interface.cpp
@@ -29,6 +29,7 @@
/**************************************************************************/
#include "webxr_interface.h"
+#include "webxr_interface.compat.inc"
#include <stdlib.h>
diff --git a/modules/webxr/webxr_interface.h b/modules/webxr/webxr_interface.h
index 06c18d0486..241dc9fe76 100644
--- a/modules/webxr/webxr_interface.h
+++ b/modules/webxr/webxr_interface.h
@@ -31,8 +31,8 @@
#ifndef WEBXR_INTERFACE_H
#define WEBXR_INTERFACE_H
+#include "servers/xr/xr_controller_tracker.h"
#include "servers/xr/xr_interface.h"
-#include "servers/xr/xr_positional_tracker.h"
/**
The WebXR interface is a VR/AR interface that can be used on the web.
@@ -44,6 +44,11 @@ class WebXRInterface : public XRInterface {
protected:
static void _bind_methods();
+#ifndef DISABLE_DEPRECATED
+ static void _bind_compatibility_methods();
+ Ref<XRPositionalTracker> _get_input_source_tracker_bind_compat_90645(int p_input_source_id) const;
+#endif
+
public:
enum TargetRayMode {
TARGET_RAY_MODE_UNKNOWN,
@@ -64,7 +69,7 @@ public:
virtual String get_reference_space_type() const = 0;
virtual String get_enabled_features() const = 0;
virtual bool is_input_source_active(int p_input_source_id) const = 0;
- virtual Ref<XRPositionalTracker> get_input_source_tracker(int p_input_source_id) const = 0;
+ virtual Ref<XRControllerTracker> get_input_source_tracker(int p_input_source_id) const = 0;
virtual TargetRayMode get_input_source_target_ray_mode(int p_input_source_id) const = 0;
virtual String get_visibility_state() const = 0;
virtual float get_display_refresh_rate() const = 0;
diff --git a/modules/webxr/webxr_interface_js.cpp b/modules/webxr/webxr_interface_js.cpp
index c6213d1aae..535d464d6f 100644
--- a/modules/webxr/webxr_interface_js.cpp
+++ b/modules/webxr/webxr_interface_js.cpp
@@ -164,8 +164,8 @@ bool WebXRInterfaceJS::is_input_source_active(int p_input_source_id) const {
return input_sources[p_input_source_id].active;
}
-Ref<XRPositionalTracker> WebXRInterfaceJS::get_input_source_tracker(int p_input_source_id) const {
- ERR_FAIL_INDEX_V(p_input_source_id, input_source_count, Ref<XRPositionalTracker>());
+Ref<XRControllerTracker> WebXRInterfaceJS::get_input_source_tracker(int p_input_source_id) const {
+ ERR_FAIL_INDEX_V(p_input_source_id, input_source_count, Ref<XRControllerTracker>());
return input_sources[p_input_source_id].tracker;
}
@@ -307,7 +307,7 @@ void WebXRInterfaceJS::uninitialize() {
for (int i = 0; i < HAND_MAX; i++) {
if (hand_trackers[i].is_valid()) {
- xr_server->remove_hand_tracker(i == 0 ? "/user/left" : "/user/right");
+ xr_server->remove_tracker(hand_trackers[i]);
hand_trackers[i].unref();
}
@@ -616,7 +616,7 @@ void WebXRInterfaceJS::_update_input_source(int p_input_source_id) {
input_source.target_ray_mode = (WebXRInterface::TargetRayMode)tmp_target_ray_mode;
input_source.touch_index = touch_index;
- Ref<XRPositionalTracker> &tracker = input_source.tracker;
+ Ref<XRControllerTracker> &tracker = input_source.tracker;
if (tracker.is_null()) {
tracker.instantiate();
@@ -630,7 +630,6 @@ void WebXRInterfaceJS::_update_input_source(int p_input_source_id) {
// Input source id's 0 and 1 are always the left and right hands.
if (p_input_source_id < 2) {
- tracker->set_tracker_type(XRServer::TRACKER_CONTROLLER);
tracker->set_tracker_name(tracker_name);
tracker->set_tracker_desc(p_input_source_id == 0 ? "Left hand controller" : "Right hand controller");
tracker->set_tracker_hand(p_input_source_id == 0 ? XRPositionalTracker::TRACKER_HAND_LEFT : XRPositionalTracker::TRACKER_HAND_RIGHT);
@@ -715,6 +714,7 @@ void WebXRInterfaceJS::_update_input_source(int p_input_source_id) {
if (unlikely(hand_tracker.is_null())) {
hand_tracker.instantiate();
hand_tracker->set_hand(p_input_source_id == 0 ? XRHandTracker::HAND_LEFT : XRHandTracker::HAND_RIGHT);
+ hand_tracker->set_tracker_name(p_input_source_id == 0 ? "/user/hand_tracker/left" : "/user/hand_tracker/right");
// These flags always apply, since WebXR doesn't give us enough insight to be more fine grained.
BitField<XRHandTracker::HandJointFlags> joint_flags(XRHandTracker::HAND_JOINT_FLAG_POSITION_VALID | XRHandTracker::HAND_JOINT_FLAG_ORIENTATION_VALID | XRHandTracker::HAND_JOINT_FLAG_POSITION_TRACKED | XRHandTracker::HAND_JOINT_FLAG_ORIENTATION_TRACKED);
@@ -723,7 +723,7 @@ void WebXRInterfaceJS::_update_input_source(int p_input_source_id) {
}
hand_trackers[p_input_source_id] = hand_tracker;
- xr_server->add_hand_tracker(p_input_source_id == 0 ? "/user/left" : "/user/right", hand_tracker);
+ xr_server->add_tracker(hand_tracker);
}
hand_tracker->set_has_tracking_data(true);
@@ -746,10 +746,12 @@ void WebXRInterfaceJS::_update_input_source(int p_input_source_id) {
Transform3D palm_transform;
palm_transform.origin = (Vector3(start_pos[0], start_pos[1], start_pos[2]) + Vector3(end_pos[0], end_pos[1], end_pos[2])) / 2.0;
hand_tracker->set_hand_joint_transform(XRHandTracker::HAND_JOINT_PALM, palm_transform);
+ hand_tracker->set_pose("default", palm_transform, Vector3(), Vector3());
}
} else if (hand_tracker.is_valid()) {
hand_tracker->set_has_tracking_data(false);
+ hand_tracker->invalidate_pose("default");
}
}
}
diff --git a/modules/webxr/webxr_interface_js.h b/modules/webxr/webxr_interface_js.h
index fc5df3a59b..afce28d410 100644
--- a/modules/webxr/webxr_interface_js.h
+++ b/modules/webxr/webxr_interface_js.h
@@ -33,6 +33,8 @@
#ifdef WEB_ENABLED
+#include "servers/xr/xr_controller_tracker.h"
+#include "servers/xr/xr_hand_tracker.h"
#include "webxr_interface.h"
/**
@@ -68,7 +70,7 @@ private:
static constexpr uint8_t input_source_count = 16;
struct InputSource {
- Ref<XRPositionalTracker> tracker;
+ Ref<XRControllerTracker> tracker;
bool active = false;
TargetRayMode target_ray_mode;
int touch_index = -1;
@@ -102,7 +104,7 @@ public:
virtual String get_reference_space_type() const override;
virtual String get_enabled_features() const override;
virtual bool is_input_source_active(int p_input_source_id) const override;
- virtual Ref<XRPositionalTracker> get_input_source_tracker(int p_input_source_id) const override;
+ virtual Ref<XRControllerTracker> get_input_source_tracker(int p_input_source_id) const override;
virtual TargetRayMode get_input_source_target_ray_mode(int p_input_source_id) const override;
virtual String get_visibility_state() const override;
virtual PackedVector3Array get_play_area() const override;
diff --git a/platform/android/api/java_class_wrapper.h b/platform/android/api/java_class_wrapper.h
index b1481ebf7b..e21a331ab9 100644
--- a/platform/android/api/java_class_wrapper.h
+++ b/platform/android/api/java_class_wrapper.h
@@ -209,8 +209,6 @@ class JavaClassWrapper : public Object {
#ifdef ANDROID_ENABLED
RBMap<String, Ref<JavaClass>> class_cache;
friend class JavaClass;
- jclass activityClass;
- jmethodID findClass;
jmethodID getDeclaredMethods;
jmethodID getFields;
jmethodID getParameterTypes;
@@ -229,7 +227,6 @@ class JavaClassWrapper : public Object {
jmethodID Long_longValue;
jmethodID Float_floatValue;
jmethodID Double_doubleValue;
- jobject classLoader;
bool _get_type_sig(JNIEnv *env, jobject obj, uint32_t &sig, String &strsig);
#endif
diff --git a/platform/android/api/jni_singleton.h b/platform/android/api/jni_singleton.h
index a2d1c08168..5b30c392e7 100644
--- a/platform/android/api/jni_singleton.h
+++ b/platform/android/api/jni_singleton.h
@@ -241,6 +241,17 @@ public:
instance = nullptr;
#endif
}
+
+ ~JNISingleton() {
+#ifdef ANDROID_ENABLED
+ if (instance) {
+ JNIEnv *env = get_jni_env();
+ ERR_FAIL_NULL(env);
+
+ env->DeleteGlobalRef(instance);
+ }
+#endif
+ }
};
#endif // JNI_SINGLETON_H
diff --git a/platform/android/dir_access_jandroid.cpp b/platform/android/dir_access_jandroid.cpp
index 972a7dbe6a..ab90527bfa 100644
--- a/platform/android/dir_access_jandroid.cpp
+++ b/platform/android/dir_access_jandroid.cpp
@@ -321,6 +321,14 @@ void DirAccessJAndroid::setup(jobject p_dir_access_handler) {
_current_is_hidden = env->GetMethodID(cls, "isCurrentHidden", "(II)Z");
}
+void DirAccessJAndroid::terminate() {
+ JNIEnv *env = get_jni_env();
+ ERR_FAIL_NULL(env);
+
+ env->DeleteGlobalRef(cls);
+ env->DeleteGlobalRef(dir_access_handler);
+}
+
DirAccessJAndroid::DirAccessJAndroid() {
}
diff --git a/platform/android/dir_access_jandroid.h b/platform/android/dir_access_jandroid.h
index 9aaa78f38c..68578b0fa9 100644
--- a/platform/android/dir_access_jandroid.h
+++ b/platform/android/dir_access_jandroid.h
@@ -89,6 +89,7 @@ public:
virtual uint64_t get_space_left() override;
static void setup(jobject p_dir_access_handler);
+ static void terminate();
DirAccessJAndroid();
~DirAccessJAndroid();
diff --git a/platform/android/export/export.cpp b/platform/android/export/export.cpp
index 138714634f..6a6d7149ff 100644
--- a/platform/android/export/export.cpp
+++ b/platform/android/export/export.cpp
@@ -33,6 +33,7 @@
#include "export_plugin.h"
#include "core/os/os.h"
+#include "editor/editor_paths.h"
#include "editor/editor_settings.h"
#include "editor/export/editor_export.h"
@@ -46,10 +47,10 @@ void register_android_exporter() {
EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/android/java_sdk_path", PROPERTY_HINT_GLOBAL_DIR));
EDITOR_DEF("export/android/android_sdk_path", OS::get_singleton()->get_environment("ANDROID_HOME"));
EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/android/android_sdk_path", PROPERTY_HINT_GLOBAL_DIR));
- EDITOR_DEF("export/android/debug_keystore", "");
+ EDITOR_DEF("export/android/debug_keystore", EditorPaths::get_singleton()->get_debug_keystore_path());
EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/android/debug_keystore", PROPERTY_HINT_GLOBAL_FILE, "*.keystore,*.jks"));
- EDITOR_DEF("export/android/debug_keystore_user", "androiddebugkey");
- EDITOR_DEF("export/android/debug_keystore_pass", "android");
+ EDITOR_DEF("export/android/debug_keystore_user", DEFAULT_ANDROID_KEYSTORE_DEBUG_USER);
+ EDITOR_DEF("export/android/debug_keystore_pass", DEFAULT_ANDROID_KEYSTORE_DEBUG_PASSWORD);
EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/android/debug_keystore_pass", PROPERTY_HINT_PASSWORD));
EDITOR_DEF("export/android/force_system_user", false);
diff --git a/platform/android/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp
index 7cab5e8d90..3b1a534daf 100644
--- a/platform/android/export/export_plugin.cpp
+++ b/platform/android/export/export_plugin.cpp
@@ -831,14 +831,82 @@ bool EditorExportPlatformAndroid::_uses_vulkan() {
void EditorExportPlatformAndroid::_notification(int p_what) {
#ifndef ANDROID_ENABLED
- if (p_what == NOTIFICATION_POSTINITIALIZE) {
- if (EditorExport::get_singleton()) {
- EditorExport::get_singleton()->connect_presets_runnable_updated(callable_mp(this, &EditorExportPlatformAndroid::_update_preset_status));
- }
+ switch (p_what) {
+ case NOTIFICATION_POSTINITIALIZE: {
+ if (EditorExport::get_singleton()) {
+ EditorExport::get_singleton()->connect_presets_runnable_updated(callable_mp(this, &EditorExportPlatformAndroid::_update_preset_status));
+ }
+ } break;
+
+ case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: {
+ if (EditorSettings::get_singleton()->check_changed_settings_in_group("export/android")) {
+ _create_editor_debug_keystore_if_needed();
+ }
+ } break;
}
#endif
}
+void EditorExportPlatformAndroid::_create_editor_debug_keystore_if_needed() {
+ // Check if we have a valid keytool path.
+ String keytool_path = get_keytool_path();
+ if (!FileAccess::exists(keytool_path)) {
+ return;
+ }
+
+ // Check if the current editor debug keystore exists.
+ String editor_debug_keystore = EDITOR_GET("export/android/debug_keystore");
+ if (FileAccess::exists(editor_debug_keystore)) {
+ return;
+ }
+
+ // Generate the debug keystore.
+ String keystore_path = EditorPaths::get_singleton()->get_debug_keystore_path();
+ String keystores_dir = keystore_path.get_base_dir();
+ if (!DirAccess::exists(keystores_dir)) {
+ Ref<DirAccess> dir_access = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
+ Error err = dir_access->make_dir_recursive(keystores_dir);
+ if (err != OK) {
+ WARN_PRINT(TTR("Error creating keystores directory:") + "\n" + keystores_dir);
+ return;
+ }
+ }
+
+ if (!FileAccess::exists(keystore_path)) {
+ String output;
+ List<String> args;
+ args.push_back("-genkey");
+ args.push_back("-keystore");
+ args.push_back(keystore_path);
+ args.push_back("-storepass");
+ args.push_back("android");
+ args.push_back("-alias");
+ args.push_back(DEFAULT_ANDROID_KEYSTORE_DEBUG_USER);
+ args.push_back("-keypass");
+ args.push_back(DEFAULT_ANDROID_KEYSTORE_DEBUG_PASSWORD);
+ args.push_back("-keyalg");
+ args.push_back("RSA");
+ args.push_back("-keysize");
+ args.push_back("2048");
+ args.push_back("-validity");
+ args.push_back("10000");
+ args.push_back("-dname");
+ args.push_back("cn=Godot, ou=Godot Engine, o=Stichting Godot, c=NL");
+ Error error = OS::get_singleton()->execute(keytool_path, args, &output, nullptr, true);
+ print_verbose(output);
+ if (error != OK) {
+ WARN_PRINT("Error: Unable to create debug keystore");
+ return;
+ }
+ }
+
+ // Update the editor settings.
+ EditorSettings::get_singleton()->set("export/android/debug_keystore", keystore_path);
+ EditorSettings::get_singleton()->set("export/android/debug_keystore_user", DEFAULT_ANDROID_KEYSTORE_DEBUG_USER);
+ EditorSettings::get_singleton()->set("export/android/debug_keystore_pass", DEFAULT_ANDROID_KEYSTORE_DEBUG_PASSWORD);
+ print_verbose("Updated editor debug keystore to " + keystore_path);
+}
+
void EditorExportPlatformAndroid::_get_permissions(const Ref<EditorExportPreset> &p_preset, bool p_give_internet, Vector<String> &r_permissions) {
const char **aperms = android_perms;
while (*aperms) {
@@ -2210,6 +2278,15 @@ String EditorExportPlatformAndroid::get_java_path() {
return java_sdk_path.path_join("bin/java" + exe_ext);
}
+String EditorExportPlatformAndroid::get_keytool_path() {
+ String exe_ext;
+ if (OS::get_singleton()->get_name() == "Windows") {
+ exe_ext = ".exe";
+ }
+ String java_sdk_path = EDITOR_GET("export/android/java_sdk_path");
+ return java_sdk_path.path_join("bin/keytool" + exe_ext);
+}
+
String EditorExportPlatformAndroid::get_adb_path() {
String exe_ext;
if (OS::get_singleton()->get_name() == "Windows") {
@@ -3655,6 +3732,7 @@ EditorExportPlatformAndroid::EditorExportPlatformAndroid() {
android_plugins_changed.set();
#endif // DISABLE_DEPRECATED
#ifndef ANDROID_ENABLED
+ _create_editor_debug_keystore_if_needed();
_update_preset_status();
check_for_changes_thread.start(_check_for_changes_poll_thread, this);
#endif
diff --git a/platform/android/export/export_plugin.h b/platform/android/export/export_plugin.h
index 7bc7bbf9e9..679afdc50f 100644
--- a/platform/android/export/export_plugin.h
+++ b/platform/android/export/export_plugin.h
@@ -60,6 +60,9 @@ const String ENV_ANDROID_KEYSTORE_RELEASE_PATH = "GODOT_ANDROID_KEYSTORE_RELEASE
const String ENV_ANDROID_KEYSTORE_RELEASE_USER = "GODOT_ANDROID_KEYSTORE_RELEASE_USER";
const String ENV_ANDROID_KEYSTORE_RELEASE_PASS = "GODOT_ANDROID_KEYSTORE_RELEASE_PASSWORD";
+const String DEFAULT_ANDROID_KEYSTORE_DEBUG_USER = "androiddebugkey";
+const String DEFAULT_ANDROID_KEYSTORE_DEBUG_PASSWORD = "android";
+
struct LauncherIcon {
const char *export_path;
int dimensions = 0;
@@ -188,6 +191,8 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
const Ref<Image> &foreground,
const Ref<Image> &background);
+ static void _create_editor_debug_keystore_if_needed();
+
static Vector<ABI> get_enabled_abis(const Ref<EditorExportPreset> &p_preset);
static bool _uses_vulkan();
@@ -236,6 +241,8 @@ public:
static String get_java_path();
+ static String get_keytool_path();
+
virtual bool has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug = false) const override;
virtual bool has_valid_project_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error) const override;
static bool has_valid_username_and_password(const Ref<EditorExportPreset> &p_preset, String &r_error);
diff --git a/platform/android/file_access_android.cpp b/platform/android/file_access_android.cpp
index f56eda4694..ae336d6f9d 100644
--- a/platform/android/file_access_android.cpp
+++ b/platform/android/file_access_android.cpp
@@ -31,8 +31,12 @@
#include "file_access_android.h"
#include "core/string/print_string.h"
+#include "thread_jandroid.h"
+
+#include <android/asset_manager_jni.h>
AAssetManager *FileAccessAndroid::asset_manager = nullptr;
+jobject FileAccessAndroid::j_asset_manager = nullptr;
String FileAccessAndroid::get_path() const {
return path_src;
@@ -257,3 +261,16 @@ void FileAccessAndroid::close() {
FileAccessAndroid::~FileAccessAndroid() {
_close();
}
+
+void FileAccessAndroid::setup(jobject p_asset_manager) {
+ JNIEnv *env = get_jni_env();
+ j_asset_manager = env->NewGlobalRef(p_asset_manager);
+ asset_manager = AAssetManager_fromJava(env, j_asset_manager);
+}
+
+void FileAccessAndroid::terminate() {
+ JNIEnv *env = get_jni_env();
+ ERR_FAIL_NULL(env);
+
+ env->DeleteGlobalRef(j_asset_manager);
+}
diff --git a/platform/android/file_access_android.h b/platform/android/file_access_android.h
index ec613b6687..e79daeafb3 100644
--- a/platform/android/file_access_android.h
+++ b/platform/android/file_access_android.h
@@ -35,9 +35,13 @@
#include <android/asset_manager.h>
#include <android/log.h>
+#include <jni.h>
#include <stdio.h>
class FileAccessAndroid : public FileAccess {
+ static AAssetManager *asset_manager;
+ static jobject j_asset_manager;
+
mutable AAsset *asset = nullptr;
mutable uint64_t len = 0;
mutable uint64_t pos = 0;
@@ -48,8 +52,6 @@ class FileAccessAndroid : public FileAccess {
void _close();
public:
- static AAssetManager *asset_manager;
-
virtual Error open_internal(const String &p_path, int p_mode_flags) override; // open a file
virtual bool is_open() const override; // true when file is open
@@ -65,6 +67,7 @@ public:
virtual bool eof_reached() const override; // reading passed EOF
+ virtual Error resize(int64_t p_length) override { return ERR_UNAVAILABLE; }
virtual uint8_t get_8() const override; // get a byte
virtual uint16_t get_16() const override;
virtual uint32_t get_32() const override;
@@ -92,6 +95,10 @@ public:
virtual void close() override;
+ static void setup(jobject p_asset_manager);
+
+ static void terminate();
+
~FileAccessAndroid();
};
diff --git a/platform/android/file_access_filesystem_jandroid.cpp b/platform/android/file_access_filesystem_jandroid.cpp
index 46d9728632..b66a201a12 100644
--- a/platform/android/file_access_filesystem_jandroid.cpp
+++ b/platform/android/file_access_filesystem_jandroid.cpp
@@ -53,6 +53,7 @@ jmethodID FileAccessFilesystemJAndroid::_file_write = nullptr;
jmethodID FileAccessFilesystemJAndroid::_file_flush = nullptr;
jmethodID FileAccessFilesystemJAndroid::_file_exists = nullptr;
jmethodID FileAccessFilesystemJAndroid::_file_last_modified = nullptr;
+jmethodID FileAccessFilesystemJAndroid::_file_resize = nullptr;
String FileAccessFilesystemJAndroid::get_path() const {
return path_src;
@@ -324,6 +325,27 @@ Error FileAccessFilesystemJAndroid::get_error() const {
return OK;
}
+Error FileAccessFilesystemJAndroid::resize(int64_t p_length) {
+ if (_file_resize) {
+ JNIEnv *env = get_jni_env();
+ ERR_FAIL_NULL_V(env, FAILED);
+ ERR_FAIL_COND_V_MSG(!is_open(), FAILED, "File must be opened before use.");
+ int res = env->CallIntMethod(file_access_handler, _file_resize, id, p_length);
+ switch (res) {
+ case 0:
+ return OK;
+ case -3:
+ return ERR_INVALID_PARAMETER;
+ case -2:
+ return ERR_FILE_CANT_OPEN;
+ default:
+ return FAILED;
+ }
+ } else {
+ return ERR_UNAVAILABLE;
+ }
+}
+
void FileAccessFilesystemJAndroid::flush() {
if (_file_flush) {
JNIEnv *env = get_jni_env();
@@ -383,6 +405,15 @@ void FileAccessFilesystemJAndroid::setup(jobject p_file_access_handler) {
_file_flush = env->GetMethodID(cls, "fileFlush", "(I)V");
_file_exists = env->GetMethodID(cls, "fileExists", "(Ljava/lang/String;)Z");
_file_last_modified = env->GetMethodID(cls, "fileLastModified", "(Ljava/lang/String;)J");
+ _file_resize = env->GetMethodID(cls, "fileResize", "(IJ)I");
+}
+
+void FileAccessFilesystemJAndroid::terminate() {
+ JNIEnv *env = get_jni_env();
+ ERR_FAIL_NULL(env);
+
+ env->DeleteGlobalRef(cls);
+ env->DeleteGlobalRef(file_access_handler);
}
void FileAccessFilesystemJAndroid::close() {
diff --git a/platform/android/file_access_filesystem_jandroid.h b/platform/android/file_access_filesystem_jandroid.h
index f33aa64ebe..6a8fc524b7 100644
--- a/platform/android/file_access_filesystem_jandroid.h
+++ b/platform/android/file_access_filesystem_jandroid.h
@@ -52,6 +52,7 @@ class FileAccessFilesystemJAndroid : public FileAccess {
static jmethodID _file_close;
static jmethodID _file_exists;
static jmethodID _file_last_modified;
+ static jmethodID _file_resize;
int id;
String absolute_path;
@@ -76,6 +77,7 @@ public:
virtual bool eof_reached() const override; ///< reading passed EOF
+ virtual Error resize(int64_t p_length) override;
virtual uint8_t get_8() const override; ///< get a byte
virtual uint16_t get_16() const override;
virtual uint32_t get_32() const override;
@@ -95,6 +97,7 @@ public:
virtual bool file_exists(const String &p_path) override; ///< return true if a file exists
static void setup(jobject p_file_access_handler);
+ static void terminate();
virtual uint64_t _get_modified_time(const String &p_file) override;
virtual BitField<FileAccess::UnixPermissionFlags> _get_unix_permissions(const String &p_file) override { return 0; }
diff --git a/platform/android/java/app/build.gradle b/platform/android/java/app/build.gradle
index 7797f4bc9d..b83ef1471c 100644
--- a/platform/android/java/app/build.gradle
+++ b/platform/android/java/app/build.gradle
@@ -205,36 +205,66 @@ android {
}
task copyAndRenameDebugApk(type: Copy) {
+ // The 'doNotTrackState' is added to disable gradle's up-to-date checks for output files
+ // and directories. Otherwise this check may cause permissions access failures on Windows
+ // machines.
+ doNotTrackState("No need for up-to-date checks for the copy-and-rename operation")
+
from "$buildDir/outputs/apk/debug/android_debug.apk"
into getExportPath()
rename "android_debug.apk", getExportFilename()
}
task copyAndRenameDevApk(type: Copy) {
+ // The 'doNotTrackState' is added to disable gradle's up-to-date checks for output files
+ // and directories. Otherwise this check may cause permissions access failures on Windows
+ // machines.
+ doNotTrackState("No need for up-to-date checks for the copy-and-rename operation")
+
from "$buildDir/outputs/apk/dev/android_dev.apk"
into getExportPath()
rename "android_dev.apk", getExportFilename()
}
task copyAndRenameReleaseApk(type: Copy) {
+ // The 'doNotTrackState' is added to disable gradle's up-to-date checks for output files
+ // and directories. Otherwise this check may cause permissions access failures on Windows
+ // machines.
+ doNotTrackState("No need for up-to-date checks for the copy-and-rename operation")
+
from "$buildDir/outputs/apk/release/android_release.apk"
into getExportPath()
rename "android_release.apk", getExportFilename()
}
task copyAndRenameDebugAab(type: Copy) {
+ // The 'doNotTrackState' is added to disable gradle's up-to-date checks for output files
+ // and directories. Otherwise this check may cause permissions access failures on Windows
+ // machines.
+ doNotTrackState("No need for up-to-date checks for the copy-and-rename operation")
+
from "$buildDir/outputs/bundle/debug/build-debug.aab"
into getExportPath()
rename "build-debug.aab", getExportFilename()
}
task copyAndRenameDevAab(type: Copy) {
+ // The 'doNotTrackState' is added to disable gradle's up-to-date checks for output files
+ // and directories. Otherwise this check may cause permissions access failures on Windows
+ // machines.
+ doNotTrackState("No need for up-to-date checks for the copy-and-rename operation")
+
from "$buildDir/outputs/bundle/dev/build-dev.aab"
into getExportPath()
rename "build-dev.aab", getExportFilename()
}
task copyAndRenameReleaseAab(type: Copy) {
+ // The 'doNotTrackState' is added to disable gradle's up-to-date checks for output files
+ // and directories. Otherwise this check may cause permissions access failures on Windows
+ // machines.
+ doNotTrackState("No need for up-to-date checks for the copy-and-rename operation")
+
from "$buildDir/outputs/bundle/release/build-release.aab"
into getExportPath()
rename "build-release.aab", getExportFilename()
diff --git a/platform/android/java/lib/src/org/godotengine/godot/io/file/DataAccess.kt b/platform/android/java/lib/src/org/godotengine/godot/io/file/DataAccess.kt
index 0f447f0b05..b155c4e488 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/io/file/DataAccess.kt
+++ b/platform/android/java/lib/src/org/godotengine/godot/io/file/DataAccess.kt
@@ -36,7 +36,9 @@ import android.util.Log
import org.godotengine.godot.io.StorageScope
import java.io.IOException
import java.nio.ByteBuffer
+import java.nio.channels.ClosedChannelException
import java.nio.channels.FileChannel
+import java.nio.channels.NonWritableChannelException
import kotlin.math.max
/**
@@ -50,6 +52,11 @@ internal abstract class DataAccess(private val filePath: String) {
companion object {
private val TAG = DataAccess::class.java.simpleName
+ private const val OK_ERROR_ID = 0;
+ private const val FAILED_ERROR_ID = -1;
+ private const val FILE_CANT_OPEN_ERROR_ID = -2;
+ private const val INVALID_PARAMETER_ERROR_ID = -3;
+
fun generateDataAccess(
storageScope: StorageScope,
context: Context,
@@ -135,6 +142,21 @@ internal abstract class DataAccess(private val filePath: String) {
seek(positionFromBeginning)
}
+ fun resize(length: Long): Int {
+ return try {
+ fileChannel.truncate(length)
+ OK_ERROR_ID
+ } catch (e: NonWritableChannelException) {
+ FILE_CANT_OPEN_ERROR_ID
+ } catch (e: ClosedChannelException) {
+ FILE_CANT_OPEN_ERROR_ID
+ } catch (e: IllegalArgumentException) {
+ INVALID_PARAMETER_ERROR_ID
+ } catch (e: IOException) {
+ FAILED_ERROR_ID
+ }
+ }
+
fun position(): Long {
return try {
fileChannel.position()
diff --git a/platform/android/java/lib/src/org/godotengine/godot/io/file/FileAccessHandler.kt b/platform/android/java/lib/src/org/godotengine/godot/io/file/FileAccessHandler.kt
index 50741c1aab..6a8a10e56f 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/io/file/FileAccessHandler.kt
+++ b/platform/android/java/lib/src/org/godotengine/godot/io/file/FileAccessHandler.kt
@@ -45,6 +45,7 @@ class FileAccessHandler(val context: Context) {
companion object {
private val TAG = FileAccessHandler::class.java.simpleName
+ private const val FAILED_ERROR_ID = -1;
private const val FILE_NOT_FOUND_ERROR_ID = -1
internal const val INVALID_FILE_ID = 0
private const val STARTING_FILE_ID = 1
@@ -190,6 +191,14 @@ class FileAccessHandler(val context: Context) {
}
}
+ fun fileResize(fileId: Int, length: Long): Int {
+ if (!hasFileId(fileId)) {
+ return FAILED_ERROR_ID
+ }
+
+ return files[fileId].resize(length)
+ }
+
fun fileGetPosition(fileId: Int): Long {
if (!hasFileId(fileId)) {
return 0L
diff --git a/platform/android/java_class_wrapper.cpp b/platform/android/java_class_wrapper.cpp
index d6455cbf1c..a309a6ab74 100644
--- a/platform/android/java_class_wrapper.cpp
+++ b/platform/android/java_class_wrapper.cpp
@@ -1157,50 +1157,54 @@ JavaClassWrapper::JavaClassWrapper(jobject p_activity) {
JNIEnv *env = get_jni_env();
ERR_FAIL_NULL(env);
- jclass activity = env->FindClass("android/app/Activity");
- jmethodID getClassLoader = env->GetMethodID(activity, "getClassLoader", "()Ljava/lang/ClassLoader;");
- classLoader = env->CallObjectMethod(p_activity, getClassLoader);
- classLoader = (jclass)env->NewGlobalRef(classLoader);
- jclass classLoaderClass = env->FindClass("java/lang/ClassLoader");
- findClass = env->GetMethodID(classLoaderClass, "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;");
-
jclass bclass = env->FindClass("java/lang/Class");
getDeclaredMethods = env->GetMethodID(bclass, "getDeclaredMethods", "()[Ljava/lang/reflect/Method;");
getFields = env->GetMethodID(bclass, "getFields", "()[Ljava/lang/reflect/Field;");
Class_getName = env->GetMethodID(bclass, "getName", "()Ljava/lang/String;");
+ env->DeleteLocalRef(bclass);
bclass = env->FindClass("java/lang/reflect/Method");
getParameterTypes = env->GetMethodID(bclass, "getParameterTypes", "()[Ljava/lang/Class;");
getReturnType = env->GetMethodID(bclass, "getReturnType", "()Ljava/lang/Class;");
getName = env->GetMethodID(bclass, "getName", "()Ljava/lang/String;");
getModifiers = env->GetMethodID(bclass, "getModifiers", "()I");
+ env->DeleteLocalRef(bclass);
bclass = env->FindClass("java/lang/reflect/Field");
Field_getName = env->GetMethodID(bclass, "getName", "()Ljava/lang/String;");
Field_getModifiers = env->GetMethodID(bclass, "getModifiers", "()I");
Field_get = env->GetMethodID(bclass, "get", "(Ljava/lang/Object;)Ljava/lang/Object;");
+ env->DeleteLocalRef(bclass);
bclass = env->FindClass("java/lang/Boolean");
Boolean_booleanValue = env->GetMethodID(bclass, "booleanValue", "()Z");
+ env->DeleteLocalRef(bclass);
bclass = env->FindClass("java/lang/Byte");
Byte_byteValue = env->GetMethodID(bclass, "byteValue", "()B");
+ env->DeleteLocalRef(bclass);
bclass = env->FindClass("java/lang/Character");
Character_characterValue = env->GetMethodID(bclass, "charValue", "()C");
+ env->DeleteLocalRef(bclass);
bclass = env->FindClass("java/lang/Short");
Short_shortValue = env->GetMethodID(bclass, "shortValue", "()S");
+ env->DeleteLocalRef(bclass);
bclass = env->FindClass("java/lang/Integer");
Integer_integerValue = env->GetMethodID(bclass, "intValue", "()I");
+ env->DeleteLocalRef(bclass);
bclass = env->FindClass("java/lang/Long");
Long_longValue = env->GetMethodID(bclass, "longValue", "()J");
+ env->DeleteLocalRef(bclass);
bclass = env->FindClass("java/lang/Float");
Float_floatValue = env->GetMethodID(bclass, "floatValue", "()F");
+ env->DeleteLocalRef(bclass);
bclass = env->FindClass("java/lang/Double");
Double_doubleValue = env->GetMethodID(bclass, "doubleValue", "()D");
+ env->DeleteLocalRef(bclass);
}
diff --git a/platform/android/java_godot_io_wrapper.cpp b/platform/android/java_godot_io_wrapper.cpp
index 10716a5c79..49913b9c30 100644
--- a/platform/android/java_godot_io_wrapper.cpp
+++ b/platform/android/java_godot_io_wrapper.cpp
@@ -70,7 +70,11 @@ GodotIOJavaWrapper::GodotIOJavaWrapper(JNIEnv *p_env, jobject p_godot_io_instanc
}
GodotIOJavaWrapper::~GodotIOJavaWrapper() {
- // nothing to do here for now
+ JNIEnv *env = get_jni_env();
+ ERR_FAIL_NULL(env);
+
+ env->DeleteGlobalRef(cls);
+ env->DeleteGlobalRef(godot_io_instance);
}
jobject GodotIOJavaWrapper::get_instance() {
@@ -82,7 +86,9 @@ Error GodotIOJavaWrapper::open_uri(const String &p_uri) {
JNIEnv *env = get_jni_env();
ERR_FAIL_NULL_V(env, ERR_UNAVAILABLE);
jstring jStr = env->NewStringUTF(p_uri.utf8().get_data());
- return env->CallIntMethod(godot_io_instance, _open_URI, jStr) ? ERR_CANT_OPEN : OK;
+ Error result = env->CallIntMethod(godot_io_instance, _open_URI, jStr) ? ERR_CANT_OPEN : OK;
+ env->DeleteLocalRef(jStr);
+ return result;
} else {
return ERR_UNAVAILABLE;
}
@@ -220,6 +226,7 @@ void GodotIOJavaWrapper::show_vk(const String &p_existing, int p_type, int p_max
ERR_FAIL_NULL(env);
jstring jStr = env->NewStringUTF(p_existing.utf8().get_data());
env->CallVoidMethod(godot_io_instance, _show_keyboard, jStr, p_type, p_max_input_length, p_cursor_start, p_cursor_end);
+ env->DeleteLocalRef(jStr);
}
}
diff --git a/platform/android/java_godot_lib_jni.cpp b/platform/android/java_godot_lib_jni.cpp
index 2215f706c5..6cab7e74fd 100644
--- a/platform/android/java_godot_lib_jni.cpp
+++ b/platform/android/java_godot_lib_jni.cpp
@@ -95,6 +95,13 @@ static void _terminate(JNIEnv *env, bool p_restart = false) {
if (godot_io_java) {
delete godot_io_java;
}
+
+ TTS_Android::terminate();
+ FileAccessAndroid::terminate();
+ DirAccessJAndroid::terminate();
+ FileAccessFilesystemJAndroid::terminate();
+ NetSocketAndroid::terminate();
+
if (godot_java) {
if (!restart_on_cleanup) {
if (p_restart) {
@@ -125,10 +132,7 @@ JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_GodotLib_initialize(JNIEnv
init_thread_jandroid(jvm, env);
- jobject amgr = env->NewGlobalRef(p_asset_manager);
-
- FileAccessAndroid::asset_manager = AAssetManager_fromJava(env, amgr);
-
+ FileAccessAndroid::setup(p_asset_manager);
DirAccessJAndroid::setup(p_directory_access_handler);
FileAccessFilesystemJAndroid::setup(p_file_access_handler);
NetSocketAndroid::setup(p_net_utils);
diff --git a/platform/android/java_godot_view_wrapper.cpp b/platform/android/java_godot_view_wrapper.cpp
index a95f762e01..04424c1179 100644
--- a/platform/android/java_godot_view_wrapper.cpp
+++ b/platform/android/java_godot_view_wrapper.cpp
@@ -95,6 +95,7 @@ void GodotJavaViewWrapper::configure_pointer_icon(int pointer_type, const String
jstring jImagePath = env->NewStringUTF(image_path.utf8().get_data());
env->CallVoidMethod(_godot_view, _configure_pointer_icon, pointer_type, jImagePath, p_hotspot.x, p_hotspot.y);
+ env->DeleteLocalRef(jImagePath);
}
}
diff --git a/platform/android/java_godot_wrapper.cpp b/platform/android/java_godot_wrapper.cpp
index 3c950bb1b1..61be6fc5db 100644
--- a/platform/android/java_godot_wrapper.cpp
+++ b/platform/android/java_godot_wrapper.cpp
@@ -172,6 +172,8 @@ void GodotJavaWrapper::alert(const String &p_message, const String &p_title) {
jstring jStrMessage = env->NewStringUTF(p_message.utf8().get_data());
jstring jStrTitle = env->NewStringUTF(p_title.utf8().get_data());
env->CallVoidMethod(godot_instance, _alert, jStrMessage, jStrTitle);
+ env->DeleteLocalRef(jStrMessage);
+ env->DeleteLocalRef(jStrTitle);
}
}
@@ -231,6 +233,7 @@ void GodotJavaWrapper::set_clipboard(const String &p_text) {
ERR_FAIL_NULL(env);
jstring jStr = env->NewStringUTF(p_text.utf8().get_data());
env->CallVoidMethod(godot_instance, _set_clipboard, jStr);
+ env->DeleteLocalRef(jStr);
}
}
@@ -253,7 +256,9 @@ bool GodotJavaWrapper::request_permission(const String &p_name) {
JNIEnv *env = get_jni_env();
ERR_FAIL_NULL_V(env, false);
jstring jStrName = env->NewStringUTF(p_name.utf8().get_data());
- return env->CallBooleanMethod(godot_instance, _request_permission, jStrName);
+ bool result = env->CallBooleanMethod(godot_instance, _request_permission, jStrName);
+ env->DeleteLocalRef(jStrName);
+ return result;
} else {
return false;
}
@@ -340,7 +345,9 @@ int GodotJavaWrapper::create_new_godot_instance(const List<String> &args) {
ERR_FAIL_NULL_V(env, 0);
jobjectArray jargs = env->NewObjectArray(args.size(), env->FindClass("java/lang/String"), env->NewStringUTF(""));
for (int i = 0; i < args.size(); i++) {
- env->SetObjectArrayElement(jargs, i, env->NewStringUTF(args[i].utf8().get_data()));
+ jstring j_arg = env->NewStringUTF(args[i].utf8().get_data());
+ env->SetObjectArrayElement(jargs, i, j_arg);
+ env->DeleteLocalRef(j_arg);
}
return env->CallIntMethod(godot_instance, _create_new_godot_instance, jargs);
} else {
@@ -355,6 +362,8 @@ void GodotJavaWrapper::begin_benchmark_measure(const String &p_context, const St
jstring j_context = env->NewStringUTF(p_context.utf8().get_data());
jstring j_label = env->NewStringUTF(p_label.utf8().get_data());
env->CallVoidMethod(godot_instance, _begin_benchmark_measure, j_context, j_label);
+ env->DeleteLocalRef(j_context);
+ env->DeleteLocalRef(j_label);
}
}
@@ -365,6 +374,8 @@ void GodotJavaWrapper::end_benchmark_measure(const String &p_context, const Stri
jstring j_context = env->NewStringUTF(p_context.utf8().get_data());
jstring j_label = env->NewStringUTF(p_label.utf8().get_data());
env->CallVoidMethod(godot_instance, _end_benchmark_measure, j_context, j_label);
+ env->DeleteLocalRef(j_context);
+ env->DeleteLocalRef(j_label);
}
}
@@ -374,6 +385,7 @@ void GodotJavaWrapper::dump_benchmark(const String &benchmark_file) {
ERR_FAIL_NULL(env);
jstring j_benchmark_file = env->NewStringUTF(benchmark_file.utf8().get_data());
env->CallVoidMethod(godot_instance, _dump_benchmark, j_benchmark_file);
+ env->DeleteLocalRef(j_benchmark_file);
}
}
@@ -383,7 +395,9 @@ bool GodotJavaWrapper::has_feature(const String &p_feature) const {
ERR_FAIL_NULL_V(env, false);
jstring j_feature = env->NewStringUTF(p_feature.utf8().get_data());
- return env->CallBooleanMethod(godot_instance, _has_feature, j_feature);
+ bool result = env->CallBooleanMethod(godot_instance, _has_feature, j_feature);
+ env->DeleteLocalRef(j_feature);
+ return result;
} else {
return false;
}
diff --git a/platform/android/net_socket_android.cpp b/platform/android/net_socket_android.cpp
index a2befdc9be..8f0ee51fac 100644
--- a/platform/android/net_socket_android.cpp
+++ b/platform/android/net_socket_android.cpp
@@ -49,6 +49,14 @@ void NetSocketAndroid::setup(jobject p_net_utils) {
_multicast_lock_release = env->GetMethodID(cls, "multicastLockRelease", "()V");
}
+void NetSocketAndroid::terminate() {
+ JNIEnv *env = get_jni_env();
+ ERR_FAIL_NULL(env);
+
+ env->DeleteGlobalRef(cls);
+ env->DeleteGlobalRef(net_utils);
+}
+
void NetSocketAndroid::multicast_lock_acquire() {
if (_multicast_lock_acquire) {
JNIEnv *env = get_jni_env();
diff --git a/platform/android/net_socket_android.h b/platform/android/net_socket_android.h
index e5f46d3236..26cb2d4e3d 100644
--- a/platform/android/net_socket_android.h
+++ b/platform/android/net_socket_android.h
@@ -63,6 +63,7 @@ protected:
public:
static void make_default();
static void setup(jobject p_net_utils);
+ static void terminate();
virtual void close();
diff --git a/platform/android/os_android.cpp b/platform/android/os_android.cpp
index bf6b7a7372..463a307854 100644
--- a/platform/android/os_android.cpp
+++ b/platform/android/os_android.cpp
@@ -162,7 +162,39 @@ Vector<String> OS_Android::get_granted_permissions() const {
return godot_java->get_granted_permissions();
}
-Error OS_Android::open_dynamic_library(const String &p_path, void *&p_library_handle, bool p_also_set_library_path, String *r_resolved_path, bool p_generate_temp_files) {
+bool OS_Android::copy_dynamic_library(const String &p_library_path, const String &p_target_dir, String *r_copy_path) {
+ if (!FileAccess::exists(p_library_path)) {
+ return false;
+ }
+
+ Ref<DirAccess> da_ref = DirAccess::create_for_path(p_library_path);
+ if (!da_ref.is_valid()) {
+ return false;
+ }
+
+ String copy_path = p_target_dir.path_join(p_library_path.get_file());
+ bool copy_exists = FileAccess::exists(copy_path);
+ if (copy_exists) {
+ print_verbose("Deleting existing library copy " + copy_path);
+ if (da_ref->remove(copy_path) != OK) {
+ print_verbose("Unable to delete " + copy_path);
+ }
+ }
+
+ print_verbose("Copying " + p_library_path + " to " + p_target_dir);
+ Error create_dir_result = da_ref->make_dir_recursive(p_target_dir);
+ if (create_dir_result == OK || create_dir_result == ERR_ALREADY_EXISTS) {
+ copy_exists = da_ref->copy(p_library_path, copy_path) == OK;
+ }
+
+ if (copy_exists && r_copy_path != nullptr) {
+ *r_copy_path = copy_path;
+ }
+
+ return copy_exists;
+}
+
+Error OS_Android::open_dynamic_library(const String &p_path, void *&p_library_handle, GDExtensionData *p_data) {
String path = p_path;
bool so_file_exists = true;
if (!FileAccess::exists(path)) {
@@ -172,24 +204,32 @@ Error OS_Android::open_dynamic_library(const String &p_path, void *&p_library_ha
p_library_handle = dlopen(path.utf8().get_data(), RTLD_NOW);
if (!p_library_handle && so_file_exists) {
- // The library may be on the sdcard and thus inaccessible. Try to copy it to the internal
- // directory.
- uint64_t so_modified_time = FileAccess::get_modified_time(p_path);
- String dynamic_library_path = get_dynamic_libraries_path().path_join(String::num_uint64(so_modified_time));
- String internal_path = dynamic_library_path.path_join(p_path.get_file());
-
- bool internal_so_file_exists = FileAccess::exists(internal_path);
- if (!internal_so_file_exists) {
- Ref<DirAccess> da_ref = DirAccess::create_for_path(p_path);
- if (da_ref.is_valid()) {
- Error create_dir_result = da_ref->make_dir_recursive(dynamic_library_path);
- if (create_dir_result == OK || create_dir_result == ERR_ALREADY_EXISTS) {
- internal_so_file_exists = da_ref->copy(path, internal_path) == OK;
+ // The library (and its dependencies) may be on the sdcard and thus inaccessible.
+ // Try to copy to the internal directory for access.
+ const String dynamic_library_path = get_dynamic_libraries_path();
+
+ if (p_data != nullptr && p_data->library_dependencies != nullptr && !p_data->library_dependencies->is_empty()) {
+ // Copy the library dependencies
+ print_verbose("Copying library dependencies..");
+ for (const String &library_dependency_path : *p_data->library_dependencies) {
+ String internal_library_dependency_path;
+ if (!copy_dynamic_library(library_dependency_path, dynamic_library_path.path_join(library_dependency_path.get_base_dir()), &internal_library_dependency_path)) {
+ ERR_PRINT(vformat("Unable to copy library dependency %s", library_dependency_path));
+ } else {
+ void *lib_dependency_handle = dlopen(internal_library_dependency_path.utf8().get_data(), RTLD_NOW);
+ if (!lib_dependency_handle) {
+ ERR_PRINT(vformat("Can't open dynamic library dependency: %s. Error: %s.", internal_library_dependency_path, dlerror()));
+ }
}
}
}
+ String internal_path;
+ print_verbose("Copying library " + p_path);
+ const bool internal_so_file_exists = copy_dynamic_library(p_path, dynamic_library_path.path_join(p_path.get_base_dir()), &internal_path);
+
if (internal_so_file_exists) {
+ print_verbose("Opening library " + internal_path);
p_library_handle = dlopen(internal_path.utf8().get_data(), RTLD_NOW);
if (p_library_handle) {
path = internal_path;
@@ -199,8 +239,8 @@ Error OS_Android::open_dynamic_library(const String &p_path, void *&p_library_ha
ERR_FAIL_NULL_V_MSG(p_library_handle, ERR_CANT_OPEN, vformat("Can't open dynamic library: %s. Error: %s.", p_path, dlerror()));
- if (r_resolved_path != nullptr) {
- *r_resolved_path = path;
+ if (p_data != nullptr && p_data->r_resolved_path != nullptr) {
+ *p_data->r_resolved_path = path;
}
return OK;
@@ -736,6 +776,10 @@ void OS_Android::benchmark_dump() {
}
bool OS_Android::_check_internal_feature_support(const String &p_feature) {
+ if (p_feature == "macos" || p_feature == "web_ios" || p_feature == "web_macos" || p_feature == "windows") {
+ return false;
+ }
+
if (p_feature == "system_fonts") {
return true;
}
diff --git a/platform/android/os_android.h b/platform/android/os_android.h
index e01d759494..7bdbeef77a 100644
--- a/platform/android/os_android.h
+++ b/platform/android/os_android.h
@@ -113,7 +113,7 @@ public:
virtual void alert(const String &p_alert, const String &p_title) override;
- virtual Error open_dynamic_library(const String &p_path, void *&p_library_handle, bool p_also_set_library_path = false, String *r_resolved_path = nullptr, bool p_generate_temp_files = false) override;
+ virtual Error open_dynamic_library(const String &p_path, void *&p_library_handle, GDExtensionData *p_data = nullptr) override;
virtual String get_name() const override;
virtual String get_distribution_name() const override;
@@ -178,6 +178,8 @@ public:
private:
// Location where we relocate external dynamic libraries to make them accessible.
String get_dynamic_libraries_path() const;
+ // Copy a dynamic library to the given location to make it accessible for loading.
+ bool copy_dynamic_library(const String &p_library_path, const String &p_target_dir, String *r_copy_path = nullptr);
};
#endif // OS_ANDROID_H
diff --git a/platform/android/tts_android.cpp b/platform/android/tts_android.cpp
index 93517d8045..be85e47972 100644
--- a/platform/android/tts_android.cpp
+++ b/platform/android/tts_android.cpp
@@ -77,6 +77,14 @@ void TTS_Android::setup(jobject p_tts) {
}
}
+void TTS_Android::terminate() {
+ JNIEnv *env = get_jni_env();
+ ERR_FAIL_NULL(env);
+
+ env->DeleteGlobalRef(cls);
+ env->DeleteGlobalRef(tts);
+}
+
void TTS_Android::_java_utterance_callback(int p_event, int p_id, int p_pos) {
ERR_FAIL_COND_MSG(!initialized, "Enable the \"audio/general/text_to_speech\" project setting to use text-to-speech.");
if (ids.has(p_id)) {
@@ -170,6 +178,8 @@ void TTS_Android::speak(const String &p_text, const String &p_voice, int p_volum
jstring jStrT = env->NewStringUTF(p_text.utf8().get_data());
jstring jStrV = env->NewStringUTF(p_voice.utf8().get_data());
env->CallVoidMethod(tts, _speak, jStrT, jStrV, CLAMP(p_volume, 0, 100), CLAMP(p_pitch, 0.f, 2.f), CLAMP(p_rate, 0.1f, 10.f), p_utterance_id, p_interrupt);
+ env->DeleteLocalRef(jStrT);
+ env->DeleteLocalRef(jStrV);
}
}
diff --git a/platform/android/tts_android.h b/platform/android/tts_android.h
index 39efef6ed1..4cc7c12846 100644
--- a/platform/android/tts_android.h
+++ b/platform/android/tts_android.h
@@ -57,6 +57,7 @@ class TTS_Android {
public:
static void setup(jobject p_tts);
+ static void terminate();
static void _java_utterance_callback(int p_event, int p_id, int p_pos);
static bool is_speaking();
diff --git a/platform/ios/os_ios.h b/platform/ios/os_ios.h
index 6950720b38..c4782a4768 100644
--- a/platform/ios/os_ios.h
+++ b/platform/ios/os_ios.h
@@ -103,7 +103,7 @@ public:
virtual Vector<String> get_system_font_path_for_text(const String &p_font_name, const String &p_text, const String &p_locale = String(), const String &p_script = String(), int p_weight = 400, int p_stretch = 100, bool p_italic = false) const override;
virtual String get_system_font_path(const String &p_font_name, int p_weight = 400, int p_stretch = 100, bool p_italic = false) const override;
- virtual Error open_dynamic_library(const String &p_path, void *&p_library_handle, bool p_also_set_library_path = false, String *r_resolved_path = nullptr, bool p_generate_temp_files = false) override;
+ virtual Error open_dynamic_library(const String &p_path, void *&p_library_handle, GDExtensionData *p_data = nullptr) override;
virtual Error close_dynamic_library(void *p_library_handle) override;
virtual Error get_dynamic_library_symbol_handle(void *p_library_handle, const String &p_name, void *&p_symbol_handle, bool p_optional = false) override;
diff --git a/platform/ios/os_ios.mm b/platform/ios/os_ios.mm
index bcf21bc802..52d496d641 100644
--- a/platform/ios/os_ios.mm
+++ b/platform/ios/os_ios.mm
@@ -217,13 +217,13 @@ _FORCE_INLINE_ String OS_IOS::get_framework_executable(const String &p_path) {
return p_path;
}
-Error OS_IOS::open_dynamic_library(const String &p_path, void *&p_library_handle, bool p_also_set_library_path, String *r_resolved_path, bool p_generate_temp_files) {
+Error OS_IOS::open_dynamic_library(const String &p_path, void *&p_library_handle, GDExtensionData *p_data) {
if (p_path.length() == 0) {
// Static xcframework.
p_library_handle = RTLD_SELF;
- if (r_resolved_path != nullptr) {
- *r_resolved_path = p_path;
+ if (p_data != nullptr && p_data->r_resolved_path != nullptr) {
+ *p_data->r_resolved_path = p_path;
}
return OK;
@@ -256,8 +256,8 @@ Error OS_IOS::open_dynamic_library(const String &p_path, void *&p_library_handle
p_library_handle = dlopen(path.utf8().get_data(), RTLD_NOW);
ERR_FAIL_NULL_V_MSG(p_library_handle, ERR_CANT_OPEN, vformat("Can't open dynamic library: %s. Error: %s.", p_path, dlerror()));
- if (r_resolved_path != nullptr) {
- *r_resolved_path = path;
+ if (p_data != nullptr && p_data->r_resolved_path != nullptr) {
+ *p_data->r_resolved_path = path;
}
return OK;
diff --git a/platform/macos/os_macos.h b/platform/macos/os_macos.h
index 439e7d2fff..912a682a6b 100644
--- a/platform/macos/os_macos.h
+++ b/platform/macos/os_macos.h
@@ -85,7 +85,7 @@ public:
virtual void alert(const String &p_alert, const String &p_title = "ALERT!") override;
- virtual Error open_dynamic_library(const String &p_path, void *&p_library_handle, bool p_also_set_library_path = false, String *r_resolved_path = nullptr, bool p_generate_temp_files = false) override;
+ virtual Error open_dynamic_library(const String &p_path, void *&p_library_handle, GDExtensionData *p_data = nullptr) override;
virtual MainLoop *get_main_loop() const override;
diff --git a/platform/macos/os_macos.mm b/platform/macos/os_macos.mm
index 261777a4e8..9f0bea5951 100644
--- a/platform/macos/os_macos.mm
+++ b/platform/macos/os_macos.mm
@@ -217,7 +217,7 @@ _FORCE_INLINE_ String OS_MacOS::get_framework_executable(const String &p_path) {
return p_path;
}
-Error OS_MacOS::open_dynamic_library(const String &p_path, void *&p_library_handle, bool p_also_set_library_path, String *r_resolved_path, bool p_generate_temp_files) {
+Error OS_MacOS::open_dynamic_library(const String &p_path, void *&p_library_handle, GDExtensionData *p_data) {
String path = get_framework_executable(p_path);
if (!FileAccess::exists(path)) {
@@ -235,8 +235,8 @@ Error OS_MacOS::open_dynamic_library(const String &p_path, void *&p_library_hand
p_library_handle = dlopen(path.utf8().get_data(), RTLD_NOW);
ERR_FAIL_NULL_V_MSG(p_library_handle, ERR_CANT_OPEN, vformat("Can't open dynamic library: %s. Error: %s.", p_path, dlerror()));
- if (r_resolved_path != nullptr) {
- *r_resolved_path = path;
+ if (p_data != nullptr && p_data->r_resolved_path != nullptr) {
+ *p_data->r_resolved_path = path;
}
return OK;
diff --git a/platform/web/os_web.cpp b/platform/web/os_web.cpp
index 4158295520..ab4e7f8470 100644
--- a/platform/web/os_web.cpp
+++ b/platform/web/os_web.cpp
@@ -247,13 +247,13 @@ bool OS_Web::is_userfs_persistent() const {
return idb_available;
}
-Error OS_Web::open_dynamic_library(const String &p_path, void *&p_library_handle, bool p_also_set_library_path, String *r_resolved_path, bool p_generate_temp_files) {
+Error OS_Web::open_dynamic_library(const String &p_path, void *&p_library_handle, GDExtensionData *p_data) {
String path = p_path.get_file();
p_library_handle = dlopen(path.utf8().get_data(), RTLD_NOW);
ERR_FAIL_NULL_V_MSG(p_library_handle, ERR_CANT_OPEN, vformat("Can't open dynamic library: %s. Error: %s.", p_path, dlerror()));
- if (r_resolved_path != nullptr) {
- *r_resolved_path = path;
+ if (p_data != nullptr && p_data->r_resolved_path != nullptr) {
+ *p_data->r_resolved_path = path;
}
return OK;
diff --git a/platform/web/os_web.h b/platform/web/os_web.h
index eeeafdac34..a825938e96 100644
--- a/platform/web/os_web.h
+++ b/platform/web/os_web.h
@@ -109,7 +109,7 @@ public:
void alert(const String &p_alert, const String &p_title = "ALERT!") override;
- Error open_dynamic_library(const String &p_path, void *&p_library_handle, bool p_also_set_library_path = false, String *r_resolved_path = nullptr, bool p_generate_temp_files = false) override;
+ Error open_dynamic_library(const String &p_path, void *&p_library_handle, GDExtensionData *p_data = nullptr) override;
void resume_audio();
diff --git a/platform/windows/os_windows.cpp b/platform/windows/os_windows.cpp
index f2a9989606..abed93d414 100644
--- a/platform/windows/os_windows.cpp
+++ b/platform/windows/os_windows.cpp
@@ -359,7 +359,7 @@ void debug_dynamic_library_check_dependencies(const String &p_root_path, const S
}
#endif
-Error OS_Windows::open_dynamic_library(const String &p_path, void *&p_library_handle, bool p_also_set_library_path, String *r_resolved_path, bool p_generate_temp_files) {
+Error OS_Windows::open_dynamic_library(const String &p_path, void *&p_library_handle, GDExtensionData *p_data) {
String path = p_path.replace("/", "\\");
if (!FileAccess::exists(path)) {
@@ -371,7 +371,7 @@ Error OS_Windows::open_dynamic_library(const String &p_path, void *&p_library_ha
// Here we want a copy to be loaded.
// This is so the original file isn't locked and can be updated by a compiler.
- if (p_generate_temp_files) {
+ if (p_data != nullptr && p_data->generate_temp_files) {
// Copy the file to the same directory as the original with a prefix in the name.
// This is so relative path to dependencies are satisfied.
String copy_path = path.get_base_dir().path_join("~" + path.get_file());
@@ -407,13 +407,13 @@ Error OS_Windows::open_dynamic_library(const String &p_path, void *&p_library_ha
bool has_dll_directory_api = ((add_dll_directory != nullptr) && (remove_dll_directory != nullptr));
DLL_DIRECTORY_COOKIE cookie = nullptr;
- if (p_also_set_library_path && has_dll_directory_api) {
+ if (p_data != nullptr && p_data->also_set_library_path && has_dll_directory_api) {
cookie = add_dll_directory((LPCWSTR)(path.get_base_dir().utf16().get_data()));
}
- p_library_handle = (void *)LoadLibraryExW((LPCWSTR)(path.utf16().get_data()), nullptr, (p_also_set_library_path && has_dll_directory_api) ? LOAD_LIBRARY_SEARCH_DEFAULT_DIRS : 0);
+ p_library_handle = (void *)LoadLibraryExW((LPCWSTR)(path.utf16().get_data()), nullptr, (p_data != nullptr && p_data->also_set_library_path && has_dll_directory_api) ? LOAD_LIBRARY_SEARCH_DEFAULT_DIRS : 0);
if (!p_library_handle) {
- if (p_generate_temp_files) {
+ if (p_data != nullptr && p_data->generate_temp_files) {
DirAccess::remove_absolute(path);
}
@@ -446,11 +446,11 @@ Error OS_Windows::open_dynamic_library(const String &p_path, void *&p_library_ha
remove_dll_directory(cookie);
}
- if (r_resolved_path != nullptr) {
- *r_resolved_path = path;
+ if (p_data != nullptr && p_data->r_resolved_path != nullptr) {
+ *p_data->r_resolved_path = path;
}
- if (p_generate_temp_files) {
+ if (p_data != nullptr && p_data->generate_temp_files) {
temp_libraries[p_library_handle] = path;
}
diff --git a/platform/windows/os_windows.h b/platform/windows/os_windows.h
index 288154745f..b6a21ed42d 100644
--- a/platform/windows/os_windows.h
+++ b/platform/windows/os_windows.h
@@ -161,7 +161,7 @@ public:
virtual Error get_entropy(uint8_t *r_buffer, int p_bytes) override;
- virtual Error open_dynamic_library(const String &p_path, void *&p_library_handle, bool p_also_set_library_path = false, String *r_resolved_path = nullptr, bool p_generate_temp_files = false) override;
+ virtual Error open_dynamic_library(const String &p_path, void *&p_library_handle, GDExtensionData *p_data = nullptr) override;
virtual Error close_dynamic_library(void *p_library_handle) override;
virtual Error get_dynamic_library_symbol_handle(void *p_library_handle, const String &p_name, void *&p_symbol_handle, bool p_optional = false) override;
diff --git a/scene/2d/tile_map_layer.cpp b/scene/2d/tile_map_layer.cpp
index 873cfeebda..ab3c48562c 100644
--- a/scene/2d/tile_map_layer.cpp
+++ b/scene/2d/tile_map_layer.cpp
@@ -2588,6 +2588,11 @@ TileMapLayer::HighlightMode TileMapLayer::get_highlight_mode() const {
}
void TileMapLayer::set_tile_map_data_from_array(const Vector<uint8_t> &p_data) {
+ if (p_data.is_empty()) {
+ clear();
+ return;
+ }
+
const int cell_data_struct_size = 12;
int size = p_data.size();
@@ -2630,6 +2635,10 @@ Vector<uint8_t> TileMapLayer::get_tile_map_data_as_array() const {
const int cell_data_struct_size = 12;
Vector<uint8_t> tile_map_data_array;
+ if (tile_map_layer_data.is_empty()) {
+ return tile_map_data_array;
+ }
+
tile_map_data_array.resize(2 + tile_map_layer_data.size() * cell_data_struct_size);
uint8_t *ptr = tile_map_data_array.ptrw();
diff --git a/scene/3d/mesh_instance_3d.cpp b/scene/3d/mesh_instance_3d.cpp
index 616fb18d53..85bf8846b9 100644
--- a/scene/3d/mesh_instance_3d.cpp
+++ b/scene/3d/mesh_instance_3d.cpp
@@ -515,6 +515,162 @@ bool MeshInstance3D::_property_get_revert(const StringName &p_name, Variant &r_p
return false;
}
+Ref<ArrayMesh> MeshInstance3D::bake_mesh_from_current_blend_shape_mix(Ref<ArrayMesh> p_existing) {
+ Ref<ArrayMesh> source_mesh = get_mesh();
+ ERR_FAIL_NULL_V_MSG(source_mesh, Ref<ArrayMesh>(), "The source mesh must be a valid ArrayMesh.");
+
+ Ref<ArrayMesh> bake_mesh;
+
+ if (p_existing.is_valid()) {
+ ERR_FAIL_NULL_V_MSG(p_existing, Ref<ArrayMesh>(), "The existing mesh must be a valid ArrayMesh.");
+ ERR_FAIL_COND_V_MSG(source_mesh == p_existing, Ref<ArrayMesh>(), "The source mesh can not be the same mesh as the existing mesh.");
+
+ bake_mesh = p_existing;
+ } else {
+ bake_mesh.instantiate();
+ }
+
+ Mesh::BlendShapeMode blend_shape_mode = source_mesh->get_blend_shape_mode();
+ int mesh_surface_count = source_mesh->get_surface_count();
+
+ bake_mesh->clear_surfaces();
+ bake_mesh->set_blend_shape_mode(blend_shape_mode);
+
+ for (int surface_index = 0; surface_index < mesh_surface_count; surface_index++) {
+ uint32_t surface_format = source_mesh->surface_get_format(surface_index);
+
+ ERR_CONTINUE(0 == (surface_format & Mesh::ARRAY_FORMAT_VERTEX));
+
+ const Array &source_mesh_arrays = source_mesh->surface_get_arrays(surface_index);
+
+ ERR_FAIL_COND_V(source_mesh_arrays.size() != RS::ARRAY_MAX, Ref<ArrayMesh>());
+
+ const Vector<Vector3> &source_mesh_vertex_array = source_mesh_arrays[Mesh::ARRAY_VERTEX];
+ const Vector<Vector3> &source_mesh_normal_array = source_mesh_arrays[Mesh::ARRAY_NORMAL];
+ const Vector<float> &source_mesh_tangent_array = source_mesh_arrays[Mesh::ARRAY_TANGENT];
+
+ Array new_mesh_arrays;
+ new_mesh_arrays.resize(Mesh::ARRAY_MAX);
+ for (int i = 0; i < source_mesh_arrays.size(); i++) {
+ if (i == Mesh::ARRAY_VERTEX || i == Mesh::ARRAY_NORMAL || i == Mesh::ARRAY_TANGENT) {
+ continue;
+ }
+ new_mesh_arrays[i] = source_mesh_arrays[i];
+ }
+
+ bool use_normal_array = source_mesh_normal_array.size() == source_mesh_vertex_array.size();
+ bool use_tangent_array = source_mesh_tangent_array.size() / 4 == source_mesh_vertex_array.size();
+
+ Vector<Vector3> lerped_vertex_array = source_mesh_vertex_array;
+ Vector<Vector3> lerped_normal_array = source_mesh_normal_array;
+ Vector<float> lerped_tangent_array = source_mesh_tangent_array;
+
+ const Vector3 *source_vertices_ptr = source_mesh_vertex_array.ptr();
+ const Vector3 *source_normals_ptr = source_mesh_normal_array.ptr();
+ const float *source_tangents_ptr = source_mesh_tangent_array.ptr();
+
+ Vector3 *lerped_vertices_ptrw = lerped_vertex_array.ptrw();
+ Vector3 *lerped_normals_ptrw = lerped_normal_array.ptrw();
+ float *lerped_tangents_ptrw = lerped_tangent_array.ptrw();
+
+ const Array &blendshapes_mesh_arrays = source_mesh->surface_get_blend_shape_arrays(surface_index);
+ int blend_shape_count = source_mesh->get_blend_shape_count();
+ ERR_FAIL_COND_V(blendshapes_mesh_arrays.size() != blend_shape_count, Ref<ArrayMesh>());
+
+ for (int blendshape_index = 0; blendshape_index < blend_shape_count; blendshape_index++) {
+ float blend_weight = get_blend_shape_value(blendshape_index);
+ if (abs(blend_weight) <= 0.0001) {
+ continue;
+ }
+
+ const Array &blendshape_mesh_arrays = blendshapes_mesh_arrays[blendshape_index];
+
+ const Vector<Vector3> &blendshape_vertex_array = blendshape_mesh_arrays[Mesh::ARRAY_VERTEX];
+ const Vector<Vector3> &blendshape_normal_array = blendshape_mesh_arrays[Mesh::ARRAY_NORMAL];
+ const Vector<float> &blendshape_tangent_array = blendshape_mesh_arrays[Mesh::ARRAY_TANGENT];
+
+ ERR_FAIL_COND_V(source_mesh_vertex_array.size() != blendshape_vertex_array.size(), Ref<ArrayMesh>());
+ ERR_FAIL_COND_V(source_mesh_normal_array.size() != blendshape_normal_array.size(), Ref<ArrayMesh>());
+ ERR_FAIL_COND_V(source_mesh_tangent_array.size() != blendshape_tangent_array.size(), Ref<ArrayMesh>());
+
+ const Vector3 *blendshape_vertices_ptr = blendshape_vertex_array.ptr();
+ const Vector3 *blendshape_normals_ptr = blendshape_normal_array.ptr();
+ const float *blendshape_tangents_ptr = blendshape_tangent_array.ptr();
+
+ if (blend_shape_mode == Mesh::BLEND_SHAPE_MODE_NORMALIZED) {
+ for (int i = 0; i < source_mesh_vertex_array.size(); i++) {
+ const Vector3 &source_vertex = source_vertices_ptr[i];
+ const Vector3 &blendshape_vertex = blendshape_vertices_ptr[i];
+ Vector3 lerped_vertex = source_vertex.lerp(blendshape_vertex, blend_weight) - source_vertex;
+ lerped_vertices_ptrw[i] += lerped_vertex;
+
+ if (use_normal_array) {
+ const Vector3 &source_normal = source_normals_ptr[i];
+ const Vector3 &blendshape_normal = blendshape_normals_ptr[i];
+ Vector3 lerped_normal = source_normal.lerp(blendshape_normal, blend_weight) - source_normal;
+ lerped_normals_ptrw[i] += lerped_normal;
+ }
+
+ if (use_tangent_array) {
+ int tangent_index = i * 4;
+ const Vector4 source_tangent = Vector4(
+ source_tangents_ptr[tangent_index],
+ source_tangents_ptr[tangent_index + 1],
+ source_tangents_ptr[tangent_index + 2],
+ source_tangents_ptr[tangent_index + 3]);
+ const Vector4 blendshape_tangent = Vector4(
+ blendshape_tangents_ptr[tangent_index],
+ blendshape_tangents_ptr[tangent_index + 1],
+ blendshape_tangents_ptr[tangent_index + 2],
+ blendshape_tangents_ptr[tangent_index + 3]);
+ Vector4 lerped_tangent = source_tangent.lerp(blendshape_tangent, blend_weight);
+ lerped_tangents_ptrw[tangent_index] += lerped_tangent.x;
+ lerped_tangents_ptrw[tangent_index + 1] += lerped_tangent.y;
+ lerped_tangents_ptrw[tangent_index + 2] += lerped_tangent.z;
+ lerped_tangents_ptrw[tangent_index + 3] += lerped_tangent.w;
+ }
+ }
+ } else if (blend_shape_mode == Mesh::BLEND_SHAPE_MODE_RELATIVE) {
+ for (int i = 0; i < source_mesh_vertex_array.size(); i++) {
+ const Vector3 &blendshape_vertex = blendshape_vertices_ptr[i];
+ lerped_vertices_ptrw[i] += blendshape_vertex * blend_weight;
+
+ if (use_normal_array) {
+ const Vector3 &blendshape_normal = blendshape_normals_ptr[i];
+ lerped_normals_ptrw[i] += blendshape_normal * blend_weight;
+ }
+
+ if (use_tangent_array) {
+ int tangent_index = i * 4;
+ const Vector4 blendshape_tangent = Vector4(
+ blendshape_tangents_ptr[tangent_index],
+ blendshape_tangents_ptr[tangent_index + 1],
+ blendshape_tangents_ptr[tangent_index + 2],
+ blendshape_tangents_ptr[tangent_index + 3]);
+ Vector4 lerped_tangent = blendshape_tangent * blend_weight;
+ lerped_tangents_ptrw[tangent_index] += lerped_tangent.x;
+ lerped_tangents_ptrw[tangent_index + 1] += lerped_tangent.y;
+ lerped_tangents_ptrw[tangent_index + 2] += lerped_tangent.z;
+ lerped_tangents_ptrw[tangent_index + 3] += lerped_tangent.w;
+ }
+ }
+ }
+ }
+
+ new_mesh_arrays[Mesh::ARRAY_VERTEX] = lerped_vertex_array;
+ if (use_normal_array) {
+ new_mesh_arrays[Mesh::ARRAY_NORMAL] = lerped_normal_array;
+ }
+ if (use_tangent_array) {
+ new_mesh_arrays[Mesh::ARRAY_TANGENT] = lerped_tangent_array;
+ }
+
+ bake_mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, new_mesh_arrays, Array(), Dictionary(), surface_format);
+ }
+
+ return bake_mesh;
+}
+
void MeshInstance3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_mesh", "mesh"), &MeshInstance3D::set_mesh);
ClassDB::bind_method(D_METHOD("get_mesh"), &MeshInstance3D::get_mesh);
@@ -542,6 +698,9 @@ void MeshInstance3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_blend_shape_value", "blend_shape_idx", "value"), &MeshInstance3D::set_blend_shape_value);
ClassDB::bind_method(D_METHOD("create_debug_tangents"), &MeshInstance3D::create_debug_tangents);
+
+ ClassDB::bind_method(D_METHOD("bake_mesh_from_current_blend_shape_mix", "existing"), &MeshInstance3D::bake_mesh_from_current_blend_shape_mix, DEFVAL(Ref<ArrayMesh>()));
+
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "mesh", PROPERTY_HINT_RESOURCE_TYPE, "Mesh"), "set_mesh", "get_mesh");
ADD_GROUP("Skeleton", "");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "skin", PROPERTY_HINT_RESOURCE_TYPE, "Skin"), "set_skin", "get_skin");
diff --git a/scene/3d/mesh_instance_3d.h b/scene/3d/mesh_instance_3d.h
index d6ae1291d3..8a7e03c5b3 100644
--- a/scene/3d/mesh_instance_3d.h
+++ b/scene/3d/mesh_instance_3d.h
@@ -101,6 +101,8 @@ public:
virtual AABB get_aabb() const override;
+ Ref<ArrayMesh> bake_mesh_from_current_blend_shape_mix(Ref<ArrayMesh> p_existing = Ref<ArrayMesh>());
+
MeshInstance3D();
~MeshInstance3D();
};
diff --git a/scene/3d/xr_body_modifier_3d.cpp b/scene/3d/xr_body_modifier_3d.cpp
index 0099784a05..8aec3e856e 100644
--- a/scene/3d/xr_body_modifier_3d.cpp
+++ b/scene/3d/xr_body_modifier_3d.cpp
@@ -44,13 +44,9 @@ void XRBodyModifier3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_bone_update", "bone_update"), &XRBodyModifier3D::set_bone_update);
ClassDB::bind_method(D_METHOD("get_bone_update"), &XRBodyModifier3D::get_bone_update);
- ClassDB::bind_method(D_METHOD("set_show_when_tracked", "show"), &XRBodyModifier3D::set_show_when_tracked);
- ClassDB::bind_method(D_METHOD("get_show_when_tracked"), &XRBodyModifier3D::get_show_when_tracked);
-
- ADD_PROPERTY(PropertyInfo(Variant::STRING, "body_tracker", PROPERTY_HINT_ENUM_SUGGESTION, "/user/body"), "set_body_tracker", "get_body_tracker");
+ ADD_PROPERTY(PropertyInfo(Variant::STRING, "body_tracker", PROPERTY_HINT_ENUM_SUGGESTION, "/user/body_tracker"), "set_body_tracker", "get_body_tracker");
ADD_PROPERTY(PropertyInfo(Variant::INT, "body_update", PROPERTY_HINT_FLAGS, "Upper Body,Lower Body,Hands"), "set_body_update", "get_body_update");
ADD_PROPERTY(PropertyInfo(Variant::INT, "bone_update", PROPERTY_HINT_ENUM, "Full,Rotation Only"), "set_bone_update", "get_bone_update");
- ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_when_tracked"), "set_show_when_tracked", "get_show_when_tracked");
BIND_BITFIELD_FLAG(BODY_UPDATE_UPPER_BODY);
BIND_BITFIELD_FLAG(BODY_UPDATE_LOWER_BODY);
@@ -86,14 +82,6 @@ XRBodyModifier3D::BoneUpdate XRBodyModifier3D::get_bone_update() const {
return bone_update;
}
-void XRBodyModifier3D::set_show_when_tracked(bool p_show_when_tracked) {
- show_when_tracked = p_show_when_tracked;
-}
-
-bool XRBodyModifier3D::get_show_when_tracked() const {
- return show_when_tracked;
-}
-
void XRBodyModifier3D::_get_joint_data() {
// Table of Godot Humanoid bone names.
static const String bone_names[XRBodyTracker::JOINT_MAX] = {
@@ -189,7 +177,7 @@ void XRBodyModifier3D::_get_joint_data() {
joints[i].parent_joint = -1;
}
- Skeleton3D *skeleton = get_skeleton();
+ const Skeleton3D *skeleton = get_skeleton();
if (!skeleton) {
return;
}
@@ -257,27 +245,22 @@ void XRBodyModifier3D::_process_modification() {
return;
}
- XRServer *xr_server = XRServer::get_singleton();
+ const XRServer *xr_server = XRServer::get_singleton();
if (!xr_server) {
return;
}
- Ref<XRBodyTracker> tracker = xr_server->get_body_tracker(tracker_name);
- if (tracker.is_null()) {
+ const Ref<XRBodyTracker> tracker = xr_server->get_tracker(tracker_name);
+ if (!tracker.is_valid()) {
return;
}
- // Handle no tracking data.
+ // Skip if no tracking data.
if (!tracker->get_has_tracking_data()) {
- // If tracking-state determines visibility then hide the node.
- if (show_when_tracked) {
- set_visible(false);
- }
return;
}
// Get the world and skeleton scale.
- const float ws = xr_server->get_world_scale();
const float ss = skeleton->get_motion_scale();
// Read the relevant tracking data. This applies the skeleton motion scale to
@@ -296,12 +279,8 @@ void XRBodyModifier3D::_process_modification() {
}
}
- // Handle root joint not tracked.
+ // Skip if root joint not tracked.
if (!has_valid_data[XRBodyTracker::JOINT_ROOT]) {
- // If tracking-state determines visibility then hide the node.
- if (show_when_tracked) {
- set_visible(false);
- }
return;
}
@@ -331,16 +310,6 @@ void XRBodyModifier3D::_process_modification() {
// Always update the bone rotation.
skeleton->set_bone_pose_rotation(joints[joint].bone, Quaternion(relative_transform.basis));
}
-
- // Transform to the tracking data root pose. This also applies the XR world-scale to allow
- // scaling the avatars mesh and skeleton appropriately (if they are child nodes).
- set_transform(
- transforms[XRBodyTracker::JOINT_ROOT] * ws);
-
- // If tracking-state determines visibility then show the node.
- if (show_when_tracked) {
- set_visible(true);
- }
}
void XRBodyModifier3D::_tracker_changed(const StringName &p_tracker_name, const Ref<XRBodyTracker> &p_tracker) {
diff --git a/scene/3d/xr_body_modifier_3d.h b/scene/3d/xr_body_modifier_3d.h
index 03b1c07d53..9ff0cd7207 100644
--- a/scene/3d/xr_body_modifier_3d.h
+++ b/scene/3d/xr_body_modifier_3d.h
@@ -66,9 +66,6 @@ public:
void set_bone_update(BoneUpdate p_bone_update);
BoneUpdate get_bone_update() const;
- void set_show_when_tracked(bool p_show_when_tracked);
- bool get_show_when_tracked() const;
-
void _notification(int p_what);
protected:
@@ -83,10 +80,9 @@ private:
int parent_joint = -1;
};
- StringName tracker_name = "/user/body";
+ StringName tracker_name = "/user/body_tracker";
BitField<BodyUpdate> body_update = BODY_UPDATE_UPPER_BODY | BODY_UPDATE_LOWER_BODY | BODY_UPDATE_HANDS;
BoneUpdate bone_update = BONE_UPDATE_FULL;
- bool show_when_tracked = true;
JointData joints[XRBodyTracker::JOINT_MAX];
void _get_joint_data();
diff --git a/scene/3d/xr_face_modifier_3d.cpp b/scene/3d/xr_face_modifier_3d.cpp
index be92a587b0..43cef95fb9 100644
--- a/scene/3d/xr_face_modifier_3d.cpp
+++ b/scene/3d/xr_face_modifier_3d.cpp
@@ -495,7 +495,7 @@ static void remove_driven_unified_blend_shapes(RBMap<int, int> &p_blend_mapping)
void XRFaceModifier3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_face_tracker", "tracker_name"), &XRFaceModifier3D::set_face_tracker);
ClassDB::bind_method(D_METHOD("get_face_tracker"), &XRFaceModifier3D::get_face_tracker);
- ADD_PROPERTY(PropertyInfo(Variant::STRING, "face_tracker", PROPERTY_HINT_ENUM_SUGGESTION, "/user/head"), "set_face_tracker", "get_face_tracker");
+ ADD_PROPERTY(PropertyInfo(Variant::STRING, "face_tracker", PROPERTY_HINT_ENUM_SUGGESTION, "/user/face_tracker"), "set_face_tracker", "get_face_tracker");
ClassDB::bind_method(D_METHOD("set_target", "target"), &XRFaceModifier3D::set_target);
ClassDB::bind_method(D_METHOD("get_target"), &XRFaceModifier3D::get_target);
@@ -576,8 +576,8 @@ void XRFaceModifier3D::_update_face_blends() const {
}
// Get the face tracker.
- const Ref<XRFaceTracker> p = xr_server->get_face_tracker(tracker_name);
- if (!p.is_valid()) {
+ const Ref<XRFaceTracker> tracker = xr_server->get_tracker(tracker_name);
+ if (!tracker.is_valid()) {
return;
}
@@ -588,7 +588,7 @@ void XRFaceModifier3D::_update_face_blends() const {
}
// Get the blend weights.
- const PackedFloat32Array weights = p->get_blend_shapes();
+ const PackedFloat32Array weights = tracker->get_blend_shapes();
// Apply all the face blend weights to the mesh.
for (const KeyValue<int, int> &it : blend_mapping) {
diff --git a/scene/3d/xr_face_modifier_3d.h b/scene/3d/xr_face_modifier_3d.h
index 147c374e95..e5e59afe1d 100644
--- a/scene/3d/xr_face_modifier_3d.h
+++ b/scene/3d/xr_face_modifier_3d.h
@@ -47,7 +47,7 @@ class XRFaceModifier3D : public Node3D {
GDCLASS(XRFaceModifier3D, Node3D);
private:
- StringName tracker_name = "/user/head";
+ StringName tracker_name = "/user/face_tracker";
NodePath target;
// Map from XRFaceTracker blend shape index to mesh blend shape index.
diff --git a/scene/3d/xr_hand_modifier_3d.cpp b/scene/3d/xr_hand_modifier_3d.cpp
index 7fecb53008..1e78a4630f 100644
--- a/scene/3d/xr_hand_modifier_3d.cpp
+++ b/scene/3d/xr_hand_modifier_3d.cpp
@@ -40,7 +40,7 @@ void XRHandModifier3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_bone_update", "bone_update"), &XRHandModifier3D::set_bone_update);
ClassDB::bind_method(D_METHOD("get_bone_update"), &XRHandModifier3D::get_bone_update);
- ADD_PROPERTY(PropertyInfo(Variant::STRING, "hand_tracker", PROPERTY_HINT_ENUM_SUGGESTION, "/user/left,/user/right"), "set_hand_tracker", "get_hand_tracker");
+ ADD_PROPERTY(PropertyInfo(Variant::STRING, "hand_tracker", PROPERTY_HINT_ENUM_SUGGESTION, "/user/hand_tracker/left,/user/hand_tracker/right"), "set_hand_tracker", "get_hand_tracker");
ADD_PROPERTY(PropertyInfo(Variant::INT, "bone_update", PROPERTY_HINT_ENUM, "Full,Rotation Only"), "set_bone_update", "get_bone_update");
BIND_ENUM_CONSTANT(BONE_UPDATE_FULL);
@@ -111,22 +111,30 @@ void XRHandModifier3D::_get_joint_data() {
joints[i].parent_joint = -1;
}
- Skeleton3D *skeleton = get_skeleton();
+ const Skeleton3D *skeleton = get_skeleton();
if (!skeleton) {
return;
}
- XRServer *xr_server = XRServer::get_singleton();
+ const XRServer *xr_server = XRServer::get_singleton();
if (!xr_server) {
return;
}
- Ref<XRHandTracker> tracker = xr_server->get_hand_tracker(tracker_name);
+ const Ref<XRHandTracker> tracker = xr_server->get_tracker(tracker_name);
if (tracker.is_null()) {
return;
}
- XRHandTracker::Hand hand = tracker->get_hand();
+ // Verify we have a left or right hand tracker.
+ const XRPositionalTracker::TrackerHand tracker_hand = tracker->get_tracker_hand();
+ if (tracker_hand != XRPositionalTracker::TRACKER_HAND_LEFT &&
+ tracker_hand != XRPositionalTracker::TRACKER_HAND_RIGHT) {
+ return;
+ }
+
+ // Get the hand index (0 = left, 1 = right).
+ const int hand = tracker_hand == XRPositionalTracker::TRACKER_HAND_LEFT ? 0 : 1;
// Find the skeleton-bones associated with each joint.
int bones[XRHandTracker::HAND_JOINT_MAX];
@@ -176,18 +184,22 @@ void XRHandModifier3D::_process_modification() {
return;
}
- XRServer *xr_server = XRServer::get_singleton();
+ const XRServer *xr_server = XRServer::get_singleton();
if (!xr_server) {
return;
}
- Ref<XRHandTracker> tracker = xr_server->get_hand_tracker(tracker_name);
+ const Ref<XRHandTracker> tracker = xr_server->get_tracker(tracker_name);
if (tracker.is_null()) {
return;
}
+ // Skip if no tracking data
+ if (!tracker->get_has_tracking_data()) {
+ return;
+ }
+
// Get the world and skeleton scale.
- const float ws = xr_server->get_world_scale();
const float ss = skeleton->get_motion_scale();
// We cache our transforms so we can quickly calculate local transforms.
@@ -195,55 +207,44 @@ void XRHandModifier3D::_process_modification() {
Transform3D transforms[XRHandTracker::HAND_JOINT_MAX];
Transform3D inv_transforms[XRHandTracker::HAND_JOINT_MAX];
- if (tracker->get_has_tracking_data()) {
- for (int joint = 0; joint < XRHandTracker::HAND_JOINT_MAX; joint++) {
- BitField<XRHandTracker::HandJointFlags> flags = tracker->get_hand_joint_flags((XRHandTracker::HandJoint)joint);
- has_valid_data[joint] = flags.has_flag(XRHandTracker::HAND_JOINT_FLAG_ORIENTATION_VALID);
+ for (int joint = 0; joint < XRHandTracker::HAND_JOINT_MAX; joint++) {
+ BitField<XRHandTracker::HandJointFlags> flags = tracker->get_hand_joint_flags((XRHandTracker::HandJoint)joint);
+ has_valid_data[joint] = flags.has_flag(XRHandTracker::HAND_JOINT_FLAG_ORIENTATION_VALID);
- if (has_valid_data[joint]) {
- transforms[joint] = tracker->get_hand_joint_transform((XRHandTracker::HandJoint)joint);
- transforms[joint].origin *= ss;
- inv_transforms[joint] = transforms[joint].inverse();
- }
+ if (has_valid_data[joint]) {
+ transforms[joint] = tracker->get_hand_joint_transform((XRHandTracker::HandJoint)joint);
+ transforms[joint].origin *= ss;
+ inv_transforms[joint] = transforms[joint].inverse();
}
+ }
- if (has_valid_data[XRHandTracker::HAND_JOINT_PALM]) {
- for (int joint = 0; joint < XRHandTracker::HAND_JOINT_MAX; joint++) {
- // Get the skeleton bone (skip if none).
- const int bone = joints[joint].bone;
- if (bone == -1) {
- continue;
- }
-
- // Calculate the relative relationship to the parent bone joint.
- const int parent_joint = joints[joint].parent_joint;
- const Transform3D relative_transform = inv_transforms[parent_joint] * transforms[joint];
-
- // Update the bone position if enabled by update mode.
- if (bone_update == BONE_UPDATE_FULL) {
- skeleton->set_bone_pose_position(joints[joint].bone, relative_transform.origin);
- }
-
- // Always update the bone rotation.
- skeleton->set_bone_pose_rotation(joints[joint].bone, Quaternion(relative_transform.basis));
- }
+ // Skip if palm has no tracking data
+ if (!has_valid_data[XRHandTracker::HAND_JOINT_PALM]) {
+ return;
+ }
+
+ for (int joint = 0; joint < XRHandTracker::HAND_JOINT_MAX; joint++) {
+ // Get the skeleton bone (skip if none).
+ const int bone = joints[joint].bone;
+ if (bone == -1) {
+ continue;
+ }
- // Transform to the skeleton pose. This uses the HAND_JOINT_PALM position without skeleton-scaling, as it
- // must be positioned to match the physical hand location. It is scaled with the world space to match
- // the scaling done to the camera and eyes.
- set_transform(
- tracker->get_hand_joint_transform(XRHandTracker::HAND_JOINT_PALM) * ws);
+ // Calculate the relative relationship to the parent bone joint.
+ const int parent_joint = joints[joint].parent_joint;
+ const Transform3D relative_transform = inv_transforms[parent_joint] * transforms[joint];
- set_visible(true);
- } else {
- set_visible(false);
+ // Update the bone position if enabled by update mode.
+ if (bone_update == BONE_UPDATE_FULL) {
+ skeleton->set_bone_pose_position(joints[joint].bone, relative_transform.origin);
}
- } else {
- set_visible(false);
+
+ // Always update the bone rotation.
+ skeleton->set_bone_pose_rotation(joints[joint].bone, Quaternion(relative_transform.basis));
}
}
-void XRHandModifier3D::_tracker_changed(StringName p_tracker_name, const Ref<XRHandTracker> &p_tracker) {
+void XRHandModifier3D::_tracker_changed(StringName p_tracker_name, XRServer::TrackerType p_tracker_type) {
if (tracker_name == p_tracker_name) {
_get_joint_data();
}
@@ -258,9 +259,9 @@ void XRHandModifier3D::_notification(int p_what) {
case NOTIFICATION_ENTER_TREE: {
XRServer *xr_server = XRServer::get_singleton();
if (xr_server) {
- xr_server->connect("hand_tracker_added", callable_mp(this, &XRHandModifier3D::_tracker_changed));
- xr_server->connect("hand_tracker_updated", callable_mp(this, &XRHandModifier3D::_tracker_changed));
- xr_server->connect("hand_tracker_removed", callable_mp(this, &XRHandModifier3D::_tracker_changed).bind(Ref<XRHandTracker>()));
+ xr_server->connect("tracker_added", callable_mp(this, &XRHandModifier3D::_tracker_changed));
+ xr_server->connect("tracker_updated", callable_mp(this, &XRHandModifier3D::_tracker_changed));
+ xr_server->connect("tracker_removed", callable_mp(this, &XRHandModifier3D::_tracker_changed));
}
_get_joint_data();
@@ -268,9 +269,9 @@ void XRHandModifier3D::_notification(int p_what) {
case NOTIFICATION_EXIT_TREE: {
XRServer *xr_server = XRServer::get_singleton();
if (xr_server) {
- xr_server->disconnect("hand_tracker_added", callable_mp(this, &XRHandModifier3D::_tracker_changed));
- xr_server->disconnect("hand_tracker_updated", callable_mp(this, &XRHandModifier3D::_tracker_changed));
- xr_server->disconnect("hand_tracker_removed", callable_mp(this, &XRHandModifier3D::_tracker_changed).bind(Ref<XRHandTracker>()));
+ xr_server->disconnect("tracker_added", callable_mp(this, &XRHandModifier3D::_tracker_changed));
+ xr_server->disconnect("tracker_updated", callable_mp(this, &XRHandModifier3D::_tracker_changed));
+ xr_server->disconnect("tracker_removed", callable_mp(this, &XRHandModifier3D::_tracker_changed));
}
for (int i = 0; i < XRHandTracker::HAND_JOINT_MAX; i++) {
diff --git a/scene/3d/xr_hand_modifier_3d.h b/scene/3d/xr_hand_modifier_3d.h
index 9f7ce45c9d..67d1694d41 100644
--- a/scene/3d/xr_hand_modifier_3d.h
+++ b/scene/3d/xr_hand_modifier_3d.h
@@ -69,12 +69,12 @@ private:
int parent_joint = -1;
};
- StringName tracker_name = "/user/left";
+ StringName tracker_name = "/user/hand_tracker/left";
BoneUpdate bone_update = BONE_UPDATE_FULL;
JointData joints[XRHandTracker::HAND_JOINT_MAX];
void _get_joint_data();
- void _tracker_changed(StringName p_tracker_name, const Ref<XRHandTracker> &p_tracker);
+ void _tracker_changed(StringName p_tracker_name, XRServer::TrackerType p_tracker_type);
};
VARIANT_ENUM_CAST(XRHandModifier3D::BoneUpdate)
diff --git a/scene/3d/xr_nodes.cpp b/scene/3d/xr_nodes.cpp
index 12a9f50ed7..3f4b962641 100644
--- a/scene/3d/xr_nodes.cpp
+++ b/scene/3d/xr_nodes.cpp
@@ -80,10 +80,11 @@ PackedStringArray XRCamera3D::get_configuration_warnings() const {
PackedStringArray warnings = Node::get_configuration_warnings();
if (is_visible() && is_inside_tree()) {
- // must be child node of XROrigin3D!
- XROrigin3D *origin = Object::cast_to<XROrigin3D>(get_parent());
- if (origin == nullptr) {
- warnings.push_back(RTR("XRCamera3D must have an XROrigin3D node as its parent."));
+ // Warn if the node has a parent which isn't an XROrigin3D!
+ Node *parent = get_parent();
+ XROrigin3D *origin = Object::cast_to<XROrigin3D>(parent);
+ if (parent && origin == nullptr) {
+ warnings.push_back(RTR("XRCamera3D may not function as expected without an XROrigin3D node as its parent."));
};
}
@@ -229,6 +230,10 @@ void XRNode3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_pose_name"), &XRNode3D::get_pose_name);
ADD_PROPERTY(PropertyInfo(Variant::STRING, "pose", PROPERTY_HINT_ENUM_SUGGESTION), "set_pose_name", "get_pose_name");
+ ClassDB::bind_method(D_METHOD("set_show_when_tracked", "show"), &XRNode3D::set_show_when_tracked);
+ ClassDB::bind_method(D_METHOD("get_show_when_tracked"), &XRNode3D::get_show_when_tracked);
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_when_tracked"), "set_show_when_tracked", "get_show_when_tracked");
+
ClassDB::bind_method(D_METHOD("get_is_active"), &XRNode3D::get_is_active);
ClassDB::bind_method(D_METHOD("get_has_tracking_data"), &XRNode3D::get_has_tracking_data);
ClassDB::bind_method(D_METHOD("get_pose"), &XRNode3D::get_pose);
@@ -296,6 +301,14 @@ StringName XRNode3D::get_pose_name() const {
return pose_name;
}
+void XRNode3D::set_show_when_tracked(bool p_show) {
+ show_when_tracked = p_show;
+}
+
+bool XRNode3D::get_show_when_tracked() const {
+ return show_when_tracked;
+}
+
bool XRNode3D::get_is_active() const {
if (tracker.is_null()) {
return false;
@@ -402,6 +415,11 @@ void XRNode3D::_set_has_tracking_data(bool p_has_tracking_data) {
// Handle change of has_tracking_data.
has_tracking_data = p_has_tracking_data;
emit_signal(SNAME("tracking_changed"), has_tracking_data);
+
+ // If configured, show or hide the node based on tracking data.
+ if (show_when_tracked) {
+ set_visible(has_tracking_data);
+ }
}
XRNode3D::XRNode3D() {
@@ -428,11 +446,12 @@ PackedStringArray XRNode3D::get_configuration_warnings() const {
PackedStringArray warnings = Node::get_configuration_warnings();
if (is_visible() && is_inside_tree()) {
- // must be child node of XROrigin!
- XROrigin3D *origin = Object::cast_to<XROrigin3D>(get_parent());
- if (origin == nullptr) {
- warnings.push_back(RTR("XRController3D must have an XROrigin3D node as its parent."));
- }
+ // Warn if the node has a parent which isn't an XROrigin3D!
+ Node *parent = get_parent();
+ XROrigin3D *origin = Object::cast_to<XROrigin3D>(parent);
+ if (parent && origin == nullptr) {
+ warnings.push_back(RTR("XRNode3D may not function as expected without an XROrigin3D node as its parent."));
+ };
if (tracker_name == "") {
warnings.push_back(RTR("No tracker name is set."));
diff --git a/scene/3d/xr_nodes.h b/scene/3d/xr_nodes.h
index bdcccd51ea..a42f6d470f 100644
--- a/scene/3d/xr_nodes.h
+++ b/scene/3d/xr_nodes.h
@@ -79,6 +79,7 @@ private:
StringName tracker_name;
StringName pose_name = "default";
bool has_tracking_data = false;
+ bool show_when_tracked = false;
protected:
Ref<XRPositionalTracker> tracker;
@@ -105,6 +106,9 @@ public:
bool get_is_active() const;
bool get_has_tracking_data() const;
+ void set_show_when_tracked(bool p_show);
+ bool get_show_when_tracked() const;
+
void trigger_haptic_pulse(const String &p_action_name, double p_frequency, double p_amplitude, double p_duration_sec, double p_delay_sec = 0);
Ref<XRPose> get_pose();
diff --git a/scene/gui/color_picker.cpp b/scene/gui/color_picker.cpp
index 3b62f6c23d..848a598ebb 100644
--- a/scene/gui/color_picker.cpp
+++ b/scene/gui/color_picker.cpp
@@ -403,8 +403,9 @@ void ColorPicker::add_mode(ColorMode *p_mode) {
}
void ColorPicker::create_slider(GridContainer *gc, int idx) {
- Label *lbl = memnew(Label());
+ Label *lbl = memnew(Label);
lbl->set_v_size_flags(SIZE_SHRINK_CENTER);
+ lbl->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
gc->add_child(lbl);
HSlider *slider = memnew(HSlider);
diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp
index 4a838fc7f6..02553bf16a 100644
--- a/scene/gui/rich_text_label.cpp
+++ b/scene/gui/rich_text_label.cpp
@@ -3212,33 +3212,6 @@ void RichTextLabel::_add_item(Item *p_item, bool p_enter, bool p_ensure_newline)
queue_redraw();
}
-void RichTextLabel::_remove_item(Item *p_item, const int p_line) {
- int size = p_item->subitems.size();
- if (size == 0) {
- p_item->parent->subitems.erase(p_item);
- // If a newline was erased, all lines AFTER the newline need to be decremented.
- if (p_item->type == ITEM_NEWLINE) {
- current_frame->lines.remove_at(p_line);
- if (p_line < (int)current_frame->lines.size() && current_frame->lines[p_line].from) {
- for (List<Item *>::Element *E = current_frame->lines[p_line].from->E; E; E = E->next()) {
- if (E->get()->line > p_line) {
- E->get()->line--;
- }
- }
- }
- }
- } else {
- // First, remove all child items for the provided item.
- while (p_item->subitems.size()) {
- _remove_item(p_item->subitems.front()->get(), p_line);
- }
- // Then remove the provided item itself.
- p_item->parent->subitems.erase(p_item);
- }
- items.free(p_item->rid);
- memdelete(p_item);
-}
-
Size2 RichTextLabel::_get_image_size(const Ref<Texture2D> &p_image, int p_width, int p_height, const Rect2 &p_region) {
Size2 ret;
if (p_width > 0) {
@@ -3424,48 +3397,97 @@ void RichTextLabel::add_newline() {
queue_redraw();
}
+void RichTextLabel::_remove_frame(HashSet<Item *> &r_erase_list, ItemFrame *p_frame, int p_line, bool p_erase, int p_char_offset, int p_line_offset) {
+ Line &l = p_frame->lines[p_line];
+ Item *it_to = (p_line + 1 < (int)p_frame->lines.size()) ? p_frame->lines[p_line + 1].from : nullptr;
+ if (!p_erase) {
+ l.char_offset -= p_char_offset;
+ }
+
+ for (Item *it = l.from; it && it != it_to;) {
+ Item *next_it = _get_next_item(it);
+ it->line -= p_line_offset;
+ if (!p_erase) {
+ while (r_erase_list.has(it->parent)) {
+ it->E->erase();
+ it->parent = it->parent->parent;
+ it->E = it->parent->subitems.push_back(it);
+ }
+ }
+ if (it->type == ITEM_TABLE) {
+ ItemTable *table = static_cast<ItemTable *>(it);
+ for (List<Item *>::Element *sub_it = table->subitems.front(); sub_it; sub_it = sub_it->next()) {
+ ERR_CONTINUE(sub_it->get()->type != ITEM_FRAME); // Children should all be frames.
+ ItemFrame *frame = static_cast<ItemFrame *>(sub_it->get());
+ for (int i = 0; i < (int)frame->lines.size(); i++) {
+ _remove_frame(r_erase_list, frame, i, p_erase, p_char_offset, 0);
+ }
+ if (p_erase) {
+ r_erase_list.insert(frame);
+ } else {
+ frame->char_ofs -= p_char_offset;
+ }
+ }
+ }
+ if (p_erase) {
+ r_erase_list.insert(it);
+ } else {
+ it->char_ofs -= p_char_offset;
+ }
+ it = next_it;
+ }
+}
+
bool RichTextLabel::remove_paragraph(const int p_paragraph) {
_stop_thread();
MutexLock data_lock(data_mutex);
- if (p_paragraph >= (int)current_frame->lines.size() || p_paragraph < 0) {
+ if (p_paragraph >= (int)main->lines.size() || p_paragraph < 0) {
return false;
}
- // Remove all subitems with the same line as that provided.
- Vector<List<Item *>::Element *> subitem_to_remove;
- if (current_frame->lines[p_paragraph].from) {
- for (List<Item *>::Element *E = current_frame->lines[p_paragraph].from->E; E; E = E->next()) {
- if (E->get()->line == p_paragraph) {
- subitem_to_remove.push_back(E);
+ if (main->lines.size() == 1) {
+ // Clear all.
+ main->_clear_children();
+ current = main;
+ current_frame = main;
+ main->lines.clear();
+ main->lines.resize(1);
+
+ current_char_ofs = 0;
+ } else {
+ HashSet<Item *> erase_list;
+ Line &l = main->lines[p_paragraph];
+ int off = l.char_count;
+ for (int i = p_paragraph; i < (int)main->lines.size(); i++) {
+ if (i == p_paragraph) {
+ _remove_frame(erase_list, main, i, true, off, 0);
} else {
- break;
+ _remove_frame(erase_list, main, i, false, off, 1);
}
}
- }
-
- bool had_newline = false;
- // Reverse for loop to remove items from the end first.
- for (int i = subitem_to_remove.size() - 1; i >= 0; i--) {
- List<Item *>::Element *subitem = subitem_to_remove[i];
- had_newline = had_newline || subitem->get()->type == ITEM_NEWLINE;
- if (subitem->get() == current) {
- pop();
+ for (HashSet<Item *>::Iterator E = erase_list.begin(); E; ++E) {
+ Item *it = *E;
+ if (current_frame == it) {
+ current_frame = main;
+ }
+ if (current == it) {
+ current = main;
+ }
+ if (!erase_list.has(it->parent)) {
+ it->E->erase();
+ }
+ items.free(it->rid);
+ it->subitems.clear();
+ memdelete(it);
}
- _remove_item(subitem->get(), p_paragraph);
+ main->lines.remove_at(p_paragraph);
+ current_char_ofs -= off;
}
- if (!had_newline) {
- current_frame->lines.remove_at(p_paragraph);
- }
-
- if (current_frame->lines.is_empty()) {
- current_frame->lines.resize(1);
- }
-
- if (p_paragraph == 0 && current->subitems.size() > 0) {
- main->lines[0].from = main;
- }
+ selection.click_frame = nullptr;
+ selection.click_item = nullptr;
+ deselect();
main->first_invalid_line.store(MIN(main->first_invalid_line.load(), p_paragraph));
main->first_resized_line.store(MIN(main->first_resized_line.load(), p_paragraph));
@@ -5197,7 +5219,7 @@ void RichTextLabel::scroll_to_paragraph(int p_paragraph) {
}
int RichTextLabel::get_paragraph_count() const {
- return current_frame->lines.size();
+ return main->lines.size();
}
int RichTextLabel::get_visible_paragraph_count() const {
diff --git a/scene/gui/rich_text_label.h b/scene/gui/rich_text_label.h
index f8dd9e663c..a993d922d2 100644
--- a/scene/gui/rich_text_label.h
+++ b/scene/gui/rich_text_label.h
@@ -485,7 +485,7 @@ private:
_FORCE_INLINE_ float _update_scroll_exceeds(float p_total_height, float p_ctrl_height, float p_width, int p_idx, float p_old_scroll, float p_text_rect_height);
void _add_item(Item *p_item, bool p_enter = false, bool p_ensure_newline = false);
- void _remove_item(Item *p_item, const int p_line);
+ void _remove_frame(HashSet<Item *> &r_erase_list, ItemFrame *p_frame, int p_line, bool p_erase, int p_char_offset, int p_line_offset);
void _texture_changed(RID p_item);
diff --git a/scene/gui/tab_container.cpp b/scene/gui/tab_container.cpp
index 105f4484b2..16358ac21f 100644
--- a/scene/gui/tab_container.cpp
+++ b/scene/gui/tab_container.cpp
@@ -30,9 +30,6 @@
#include "tab_container.h"
-#include "scene/gui/box_container.h"
-#include "scene/gui/label.h"
-#include "scene/gui/texture_rect.h"
#include "scene/theme/theme_db.h"
int TabContainer::_get_tab_height() const {
@@ -782,6 +779,7 @@ void TabContainer::set_tab_icon(int p_tab, const Ref<Texture2D> &p_icon) {
_update_margins();
_repaint();
+ queue_redraw();
}
Ref<Texture2D> TabContainer::get_tab_icon(int p_tab) const {
diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp
index 6bb745ac57..0bb77a92f2 100644
--- a/scene/gui/text_edit.cpp
+++ b/scene/gui/text_edit.cpp
@@ -1187,7 +1187,7 @@ void TextEdit::_notification(int p_what) {
}
if (!clipped && lookup_symbol_word.length() != 0) { // Highlight word
- if (is_ascii_char(lookup_symbol_word[0]) || lookup_symbol_word[0] == '_' || lookup_symbol_word[0] == '.') {
+ if (is_ascii_alphabet_char(lookup_symbol_word[0]) || lookup_symbol_word[0] == '_' || lookup_symbol_word[0] == '.') {
int lookup_symbol_word_col = _get_column_pos_of_word(lookup_symbol_word, str, SEARCH_MATCH_CASE | SEARCH_WHOLE_WORDS, 0);
int lookup_symbol_word_len = lookup_symbol_word.length();
while (lookup_symbol_word_col != -1) {
diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp
index 9a2ba23ce8..0c48fac29f 100644
--- a/scene/gui/tree.cpp
+++ b/scene/gui/tree.cpp
@@ -35,8 +35,6 @@
#include "core/math/math_funcs.h"
#include "core/os/keyboard.h"
#include "core/os/os.h"
-#include "core/string/print_string.h"
-#include "core/string/translation.h"
#include "scene/gui/box_container.h"
#include "scene/gui/text_edit.h"
#include "scene/main/window.h"
@@ -5267,6 +5265,86 @@ TreeItem *Tree::_find_item_at_pos(TreeItem *p_item, const Point2 &p_pos, int &r_
return nullptr;
}
+// When on a button, r_index is valid.
+// When on an item, both r_item and r_column are valid.
+// Otherwise, all output arguments are invalid.
+void Tree::_find_button_at_pos(const Point2 &p_pos, TreeItem *&r_item, int &r_column, int &r_index) const {
+ r_item = nullptr;
+ r_column = -1;
+ r_index = -1;
+
+ if (!root) {
+ return;
+ }
+
+ Point2 pos = p_pos - theme_cache.panel_style->get_offset();
+ pos.y -= _get_title_button_height();
+ if (pos.y < 0) {
+ return;
+ }
+
+ if (cache.rtl) {
+ pos.x = get_size().width - pos.x;
+ }
+ pos += theme_cache.offset; // Scrolling.
+
+ int col, h, section;
+ TreeItem *it = _find_item_at_pos(root, pos, col, h, section);
+ if (!it) {
+ return;
+ }
+
+ r_item = it;
+ r_column = col;
+
+ const TreeItem::Cell &c = it->cells[col];
+ if (c.buttons.is_empty()) {
+ return;
+ }
+
+ int x_limit = get_size().width - theme_cache.panel_style->get_minimum_size().width + theme_cache.offset.x;
+ if (v_scroll->is_visible_in_tree()) {
+ x_limit -= v_scroll->get_minimum_size().width;
+ }
+
+ for (int i = 0; i < col; i++) {
+ const int col_w = get_column_width(i) + theme_cache.h_separation;
+ pos.x -= col_w;
+ x_limit -= col_w;
+ }
+
+ int x_check;
+ if (cache.rtl) {
+ x_check = get_column_width(col);
+ } else {
+ // Right edge of the buttons area, relative to the start of the column.
+ int buttons_area_min = 0;
+ if (col == 0) {
+ // Content of column 0 should take indentation into account.
+ for (TreeItem *current = it; current && (current != root || !hide_root); current = current->parent) {
+ buttons_area_min += theme_cache.item_margin;
+ }
+ }
+ for (int i = c.buttons.size() - 1; i >= 0; i--) {
+ Ref<Texture2D> b = c.buttons[i].texture;
+ buttons_area_min += b->get_size().width + theme_cache.button_pressed->get_minimum_size().width + theme_cache.button_margin;
+ }
+
+ x_check = MAX(buttons_area_min, MIN(get_column_width(col), x_limit));
+ }
+
+ for (int i = c.buttons.size() - 1; i >= 0; i--) {
+ Ref<Texture2D> b = c.buttons[i].texture;
+ Size2 size = b->get_size() + theme_cache.button_pressed->get_minimum_size();
+ if (pos.x > x_check - size.width) {
+ x_limit -= theme_cache.item_margin;
+ r_index = i;
+ return;
+ }
+ x_check -= size.width + theme_cache.button_margin;
+ }
+}
+
int Tree::get_column_at_position(const Point2 &p_pos) const {
if (root) {
Point2 pos = p_pos;
@@ -5358,92 +5436,37 @@ TreeItem *Tree::get_item_at_position(const Point2 &p_pos) const {
}
int Tree::get_button_id_at_position(const Point2 &p_pos) const {
- if (root) {
- Point2 pos = p_pos;
- pos -= theme_cache.panel_style->get_offset();
- pos.y -= _get_title_button_height();
- if (pos.y < 0) {
- return -1;
- }
-
- if (h_scroll->is_visible_in_tree()) {
- pos.x += h_scroll->get_value();
- }
- if (v_scroll->is_visible_in_tree()) {
- pos.y += v_scroll->get_value();
- }
-
- int col, h, section;
- TreeItem *it = _find_item_at_pos(root, pos, col, h, section);
+ TreeItem *it;
+ int col, index;
+ _find_button_at_pos(p_pos, it, col, index);
- if (it) {
- const TreeItem::Cell &c = it->cells[col];
- int col_width = get_column_width(col);
-
- for (int i = 0; i < col; i++) {
- pos.x -= get_column_width(i);
- }
-
- for (int j = c.buttons.size() - 1; j >= 0; j--) {
- Ref<Texture2D> b = c.buttons[j].texture;
- Size2 size = b->get_size() + theme_cache.button_pressed->get_minimum_size();
- if (pos.x > col_width - size.width) {
- return c.buttons[j].id;
- }
- col_width -= size.width;
- }
- }
+ if (index == -1) {
+ return -1;
}
-
- return -1;
+ return it->cells[col].buttons[index].id;
}
String Tree::get_tooltip(const Point2 &p_pos) const {
- if (root) {
- Point2 pos = p_pos;
- pos -= theme_cache.panel_style->get_offset();
- pos.y -= _get_title_button_height();
- if (pos.y < 0) {
- return Control::get_tooltip(p_pos);
- }
-
- if (h_scroll->is_visible_in_tree()) {
- pos.x += h_scroll->get_value();
- }
- if (v_scroll->is_visible_in_tree()) {
- pos.y += v_scroll->get_value();
- }
+ Point2 pos = p_pos - theme_cache.panel_style->get_offset();
+ pos.y -= _get_title_button_height();
+ if (pos.y < 0) {
+ return Control::get_tooltip(p_pos);
+ }
- int col, h, section;
- TreeItem *it = _find_item_at_pos(root, pos, col, h, section);
+ TreeItem *it;
+ int col, index;
+ _find_button_at_pos(p_pos, it, col, index);
- if (it) {
- const TreeItem::Cell &c = it->cells[col];
- int col_width = get_column_width(col);
-
- for (int i = 0; i < col; i++) {
- pos.x -= get_column_width(i);
- }
+ if (index != -1) {
+ return it->cells[col].buttons[index].tooltip;
+ }
- for (int j = c.buttons.size() - 1; j >= 0; j--) {
- Ref<Texture2D> b = c.buttons[j].texture;
- Size2 size = b->get_size() + theme_cache.button_pressed->get_minimum_size();
- if (pos.x > col_width - size.width) {
- String tooltip = c.buttons[j].tooltip;
- if (!tooltip.is_empty()) {
- return tooltip;
- }
- }
- col_width -= size.width;
- }
- String ret;
- if (it->get_tooltip_text(col) == "") {
- ret = it->get_text(col);
- } else {
- ret = it->get_tooltip_text(col);
- }
- return ret;
+ if (it) {
+ const String item_tooltip = it->get_tooltip_text(col);
+ if (item_tooltip.is_empty()) {
+ return it->get_text(col);
}
+ return item_tooltip;
}
return Control::get_tooltip(p_pos);
diff --git a/scene/gui/tree.h b/scene/gui/tree.h
index c7c266a2e7..311055a2f8 100644
--- a/scene/gui/tree.h
+++ b/scene/gui/tree.h
@@ -645,6 +645,8 @@ private:
TreeItem *_find_item_at_pos(TreeItem *p_item, const Point2 &p_pos, int &r_column, int &h, int &section) const;
+ void _find_button_at_pos(const Point2 &p_pos, TreeItem *&r_item, int &r_column, int &r_index) const;
+
/* float drag_speed;
float drag_accum;
diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp
index d13f338444..6fac096c93 100644
--- a/scene/register_scene_types.cpp
+++ b/scene/register_scene_types.cpp
@@ -545,7 +545,7 @@ void register_scene_types() {
GDREGISTER_CLASS(Camera3D);
GDREGISTER_CLASS(AudioListener3D);
GDREGISTER_CLASS(XRCamera3D);
- GDREGISTER_ABSTRACT_CLASS(XRNode3D);
+ GDREGISTER_CLASS(XRNode3D);
GDREGISTER_CLASS(XRController3D);
GDREGISTER_CLASS(XRAnchor3D);
GDREGISTER_CLASS(XROrigin3D);
diff --git a/scene/resources/2d/navigation_mesh_source_geometry_data_2d.cpp b/scene/resources/2d/navigation_mesh_source_geometry_data_2d.cpp
index d613b498a1..aee743ccf2 100644
--- a/scene/resources/2d/navigation_mesh_source_geometry_data_2d.cpp
+++ b/scene/resources/2d/navigation_mesh_source_geometry_data_2d.cpp
@@ -97,6 +97,24 @@ TypedArray<Vector<Vector2>> NavigationMeshSourceGeometryData2D::get_obstruction_
return typed_array_obstruction_outlines;
}
+void NavigationMeshSourceGeometryData2D::append_traversable_outlines(const TypedArray<Vector<Vector2>> &p_traversable_outlines) {
+ RWLockWrite write_lock(geometry_rwlock);
+ int traversable_outlines_size = traversable_outlines.size();
+ traversable_outlines.resize(traversable_outlines_size + p_traversable_outlines.size());
+ for (int i = traversable_outlines_size; i < p_traversable_outlines.size(); i++) {
+ traversable_outlines.write[i] = p_traversable_outlines[i];
+ }
+}
+
+void NavigationMeshSourceGeometryData2D::append_obstruction_outlines(const TypedArray<Vector<Vector2>> &p_obstruction_outlines) {
+ RWLockWrite write_lock(geometry_rwlock);
+ int obstruction_outlines_size = obstruction_outlines.size();
+ obstruction_outlines.resize(obstruction_outlines_size + p_obstruction_outlines.size());
+ for (int i = obstruction_outlines_size; i < p_obstruction_outlines.size(); i++) {
+ obstruction_outlines.write[i] = p_obstruction_outlines[i];
+ }
+}
+
void NavigationMeshSourceGeometryData2D::add_traversable_outline(const PackedVector2Array &p_shape_outline) {
if (p_shape_outline.size() > 1) {
Vector<Vector2> traversable_outline;
@@ -240,6 +258,9 @@ void NavigationMeshSourceGeometryData2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_obstruction_outlines", "obstruction_outlines"), &NavigationMeshSourceGeometryData2D::set_obstruction_outlines);
ClassDB::bind_method(D_METHOD("get_obstruction_outlines"), &NavigationMeshSourceGeometryData2D::get_obstruction_outlines);
+ ClassDB::bind_method(D_METHOD("append_traversable_outlines", "traversable_outlines"), &NavigationMeshSourceGeometryData2D::append_traversable_outlines);
+ ClassDB::bind_method(D_METHOD("append_obstruction_outlines", "obstruction_outlines"), &NavigationMeshSourceGeometryData2D::append_obstruction_outlines);
+
ClassDB::bind_method(D_METHOD("add_traversable_outline", "shape_outline"), &NavigationMeshSourceGeometryData2D::add_traversable_outline);
ClassDB::bind_method(D_METHOD("add_obstruction_outline", "shape_outline"), &NavigationMeshSourceGeometryData2D::add_obstruction_outline);
diff --git a/scene/resources/2d/navigation_mesh_source_geometry_data_2d.h b/scene/resources/2d/navigation_mesh_source_geometry_data_2d.h
index 11fc5d3850..aaa02ab40e 100644
--- a/scene/resources/2d/navigation_mesh_source_geometry_data_2d.h
+++ b/scene/resources/2d/navigation_mesh_source_geometry_data_2d.h
@@ -82,6 +82,9 @@ public:
void set_obstruction_outlines(const TypedArray<Vector<Vector2>> &p_obstruction_outlines);
TypedArray<Vector<Vector2>> get_obstruction_outlines() const;
+ void append_traversable_outlines(const TypedArray<Vector<Vector2>> &p_traversable_outlines);
+ void append_obstruction_outlines(const TypedArray<Vector<Vector2>> &p_obstruction_outlines);
+
void add_traversable_outline(const PackedVector2Array &p_shape_outline);
void add_obstruction_outline(const PackedVector2Array &p_shape_outline);
diff --git a/scene/resources/3d/importer_mesh.cpp b/scene/resources/3d/importer_mesh.cpp
index 952e99608d..375efcb227 100644
--- a/scene/resources/3d/importer_mesh.cpp
+++ b/scene/resources/3d/importer_mesh.cpp
@@ -1139,7 +1139,7 @@ Error ImporterMesh::lightmap_unwrap_cached(const Transform3D &p_base_transform,
s.material = get_surface_material(i);
s.name = get_surface_name(i);
- SurfaceTool::create_vertex_array_from_triangle_arrays(arrays, s.vertices, &s.format);
+ SurfaceTool::create_vertex_array_from_arrays(arrays, s.vertices, &s.format);
PackedVector3Array rvertices = arrays[Mesh::ARRAY_VERTEX];
int vc = rvertices.size();
diff --git a/scene/resources/3d/navigation_mesh_source_geometry_data_3d.cpp b/scene/resources/3d/navigation_mesh_source_geometry_data_3d.cpp
index 39a17946fa..5d920af400 100644
--- a/scene/resources/3d/navigation_mesh_source_geometry_data_3d.cpp
+++ b/scene/resources/3d/navigation_mesh_source_geometry_data_3d.cpp
@@ -35,9 +35,24 @@ void NavigationMeshSourceGeometryData3D::set_vertices(const Vector<float> &p_ver
}
void NavigationMeshSourceGeometryData3D::set_indices(const Vector<int> &p_indices) {
+ ERR_FAIL_COND(vertices.size() < p_indices.size());
indices = p_indices;
}
+void NavigationMeshSourceGeometryData3D::append_arrays(const Vector<float> &p_vertices, const Vector<int> &p_indices) {
+ RWLockWrite write_lock(geometry_rwlock);
+
+ const int64_t number_of_vertices_before_merge = vertices.size();
+ const int64_t number_of_indices_before_merge = indices.size();
+
+ vertices.append_array(p_vertices);
+ indices.append_array(p_indices);
+
+ for (int64_t i = number_of_indices_before_merge; i < indices.size(); i++) {
+ indices.set(i, indices[i] + number_of_vertices_before_merge / 3);
+ }
+}
+
void NavigationMeshSourceGeometryData3D::clear() {
vertices.clear();
indices.clear();
@@ -174,14 +189,7 @@ void NavigationMeshSourceGeometryData3D::add_faces(const PackedVector3Array &p_f
void NavigationMeshSourceGeometryData3D::merge(const Ref<NavigationMeshSourceGeometryData3D> &p_other_geometry) {
ERR_FAIL_NULL(p_other_geometry);
- // No need to worry about `root_node_transform` here as the vertices are already xformed.
- const int64_t number_of_vertices_before_merge = vertices.size();
- const int64_t number_of_indices_before_merge = indices.size();
- vertices.append_array(p_other_geometry->vertices);
- indices.append_array(p_other_geometry->indices);
- for (int64_t i = number_of_indices_before_merge; i < indices.size(); i++) {
- indices.set(i, indices[i] + number_of_vertices_before_merge / 3);
- }
+ append_arrays(p_other_geometry->vertices, p_other_geometry->indices);
if (p_other_geometry->_projected_obstructions.size() > 0) {
RWLockWrite write_lock(geometry_rwlock);
@@ -306,6 +314,8 @@ void NavigationMeshSourceGeometryData3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_indices", "indices"), &NavigationMeshSourceGeometryData3D::set_indices);
ClassDB::bind_method(D_METHOD("get_indices"), &NavigationMeshSourceGeometryData3D::get_indices);
+ ClassDB::bind_method(D_METHOD("append_arrays", "vertices", "indices"), &NavigationMeshSourceGeometryData3D::append_arrays);
+
ClassDB::bind_method(D_METHOD("clear"), &NavigationMeshSourceGeometryData3D::clear);
ClassDB::bind_method(D_METHOD("has_data"), &NavigationMeshSourceGeometryData3D::has_data);
diff --git a/scene/resources/3d/navigation_mesh_source_geometry_data_3d.h b/scene/resources/3d/navigation_mesh_source_geometry_data_3d.h
index 79e2f3740d..6c1ca760ea 100644
--- a/scene/resources/3d/navigation_mesh_source_geometry_data_3d.h
+++ b/scene/resources/3d/navigation_mesh_source_geometry_data_3d.h
@@ -80,6 +80,8 @@ public:
void set_indices(const Vector<int> &p_indices);
const Vector<int> &get_indices() const { return indices; }
+ void append_arrays(const Vector<float> &p_vertices, const Vector<int> &p_indices);
+
bool has_data() { return vertices.size() && indices.size(); };
void clear();
void clear_projected_obstructions();
diff --git a/scene/resources/mesh.cpp b/scene/resources/mesh.cpp
index f766d1d2c7..73f3009fd1 100644
--- a/scene/resources/mesh.cpp
+++ b/scene/resources/mesh.cpp
@@ -2086,7 +2086,7 @@ Error ArrayMesh::lightmap_unwrap_cached(const Transform3D &p_base_transform, flo
Array arrays = surface_get_arrays(i);
s.material = surface_get_material(i);
- SurfaceTool::create_vertex_array_from_triangle_arrays(arrays, s.vertices, &s.format);
+ SurfaceTool::create_vertex_array_from_arrays(arrays, s.vertices, &s.format);
PackedVector3Array rvertices = arrays[Mesh::ARRAY_VERTEX];
int vc = rvertices.size();
diff --git a/scene/resources/surface_tool.cpp b/scene/resources/surface_tool.cpp
index 06d53e4e2f..9f2fad410c 100644
--- a/scene/resources/surface_tool.cpp
+++ b/scene/resources/surface_tool.cpp
@@ -802,7 +802,7 @@ void SurfaceTool::_create_list(const Ref<Mesh> &p_existing, int p_surface, Local
const uint32_t SurfaceTool::custom_mask[RS::ARRAY_CUSTOM_COUNT] = { Mesh::ARRAY_FORMAT_CUSTOM0, Mesh::ARRAY_FORMAT_CUSTOM1, Mesh::ARRAY_FORMAT_CUSTOM2, Mesh::ARRAY_FORMAT_CUSTOM3 };
const uint32_t SurfaceTool::custom_shift[RS::ARRAY_CUSTOM_COUNT] = { Mesh::ARRAY_FORMAT_CUSTOM0_SHIFT, Mesh::ARRAY_FORMAT_CUSTOM1_SHIFT, Mesh::ARRAY_FORMAT_CUSTOM2_SHIFT, Mesh::ARRAY_FORMAT_CUSTOM3_SHIFT };
-void SurfaceTool::create_vertex_array_from_triangle_arrays(const Array &p_arrays, LocalVector<SurfaceTool::Vertex> &ret, uint64_t *r_format) {
+void SurfaceTool::create_vertex_array_from_arrays(const Array &p_arrays, LocalVector<SurfaceTool::Vertex> &ret, uint64_t *r_format) {
ret.clear();
Vector<Vector3> varr = p_arrays[RS::ARRAY_VERTEX];
@@ -932,7 +932,7 @@ void SurfaceTool::create_vertex_array_from_triangle_arrays(const Array &p_arrays
}
void SurfaceTool::_create_list_from_arrays(Array arr, LocalVector<Vertex> *r_vertex, LocalVector<int> *r_index, uint64_t &lformat) {
- create_vertex_array_from_triangle_arrays(arr, *r_vertex, &lformat);
+ create_vertex_array_from_arrays(arr, *r_vertex, &lformat);
ERR_FAIL_COND(r_vertex->size() == 0);
//indices
@@ -949,9 +949,9 @@ void SurfaceTool::_create_list_from_arrays(Array arr, LocalVector<Vertex> *r_ver
}
}
-void SurfaceTool::create_from_triangle_arrays(const Array &p_arrays) {
+void SurfaceTool::create_from_arrays(const Array &p_arrays, Mesh::PrimitiveType p_primitive_type) {
clear();
- primitive = Mesh::PRIMITIVE_TRIANGLES;
+ primitive = p_primitive_type;
_create_list_from_arrays(p_arrays, &vertex_array, &index_array, format);
for (int j = 0; j < RS::ARRAY_CUSTOM_COUNT; j++) {
@@ -961,6 +961,10 @@ void SurfaceTool::create_from_triangle_arrays(const Array &p_arrays) {
}
}
+void SurfaceTool::create_from_triangle_arrays(const Array &p_arrays) {
+ create_from_arrays(p_arrays, Mesh::PRIMITIVE_TRIANGLES);
+}
+
void SurfaceTool::create_from(const Ref<Mesh> &p_existing, int p_surface) {
ERR_FAIL_NULL_MSG(p_existing, "First argument in SurfaceTool::create_from() must be a valid object of type Mesh");
@@ -1377,6 +1381,7 @@ void SurfaceTool::_bind_methods() {
ClassDB::bind_method(D_METHOD("clear"), &SurfaceTool::clear);
ClassDB::bind_method(D_METHOD("create_from", "existing", "surface"), &SurfaceTool::create_from);
+ ClassDB::bind_method(D_METHOD("create_from_arrays", "arrays", "primitive_type"), &SurfaceTool::create_from_arrays, DEFVAL(Mesh::PRIMITIVE_TRIANGLES));
ClassDB::bind_method(D_METHOD("create_from_blend_shape", "existing", "surface", "blend_shape"), &SurfaceTool::create_from_blend_shape);
ClassDB::bind_method(D_METHOD("append_from", "existing", "surface", "transform"), &SurfaceTool::append_from);
ClassDB::bind_method(D_METHOD("commit", "existing", "flags"), &SurfaceTool::commit, DEFVAL(Variant()), DEFVAL(0));
diff --git a/scene/resources/surface_tool.h b/scene/resources/surface_tool.h
index 9dfb298b9e..a072df5bee 100644
--- a/scene/resources/surface_tool.h
+++ b/scene/resources/surface_tool.h
@@ -219,7 +219,8 @@ public:
LocalVector<Vertex> &get_vertex_array() { return vertex_array; }
void create_from_triangle_arrays(const Array &p_arrays);
- static void create_vertex_array_from_triangle_arrays(const Array &p_arrays, LocalVector<Vertex> &ret, uint64_t *r_format = nullptr);
+ void create_from_arrays(const Array &p_arrays, Mesh::PrimitiveType p_primitive_type = Mesh::PRIMITIVE_TRIANGLES);
+ static void create_vertex_array_from_arrays(const Array &p_arrays, LocalVector<Vertex> &ret, uint64_t *r_format = nullptr);
Array commit_to_arrays();
void create_from(const Ref<Mesh> &p_existing, int p_surface);
void create_from_blend_shape(const Ref<Mesh> &p_existing, int p_surface, const String &p_blend_shape_name);
diff --git a/scene/resources/syntax_highlighter.cpp b/scene/resources/syntax_highlighter.cpp
index 441c8859cf..c735395829 100644
--- a/scene/resources/syntax_highlighter.cpp
+++ b/scene/resources/syntax_highlighter.cpp
@@ -313,7 +313,7 @@ Dictionary CodeHighlighter::_get_line_syntax_highlighting_impl(int p_line) {
}
}
- if (!in_word && (is_ascii_char(str[j]) || is_underscore(str[j])) && !is_number) {
+ if (!in_word && (is_ascii_alphabet_char(str[j]) || is_underscore(str[j])) && !is_number) {
in_word = true;
}
diff --git a/scene/resources/visual_shader.cpp b/scene/resources/visual_shader.cpp
index 7e80d0be3c..6f1aa5c850 100644
--- a/scene/resources/visual_shader.cpp
+++ b/scene/resources/visual_shader.cpp
@@ -1463,7 +1463,7 @@ String VisualShader::validate_port_name(const String &p_port_name, VisualShaderN
return String();
}
- while (port_name.length() && !is_ascii_char(port_name[0])) {
+ while (port_name.length() && !is_ascii_alphabet_char(port_name[0])) {
port_name = port_name.substr(1, port_name.length() - 1);
}
@@ -1508,7 +1508,7 @@ String VisualShader::validate_port_name(const String &p_port_name, VisualShaderN
String VisualShader::validate_parameter_name(const String &p_name, const Ref<VisualShaderNodeParameter> &p_parameter) const {
String param_name = p_name; //validate name first
- while (param_name.length() && !is_ascii_char(param_name[0])) {
+ while (param_name.length() && !is_ascii_alphabet_char(param_name[0])) {
param_name = param_name.substr(1, param_name.length() - 1);
}
if (!param_name.is_empty()) {
diff --git a/servers/register_server_types.cpp b/servers/register_server_types.cpp
index c03c0b7a40..1a75614a4c 100644
--- a/servers/register_server_types.cpp
+++ b/servers/register_server_types.cpp
@@ -94,6 +94,7 @@
#include "physics_server_3d_wrap_mt.h"
#include "servers/extensions/physics_server_3d_extension.h"
#include "xr/xr_body_tracker.h"
+#include "xr/xr_controller_tracker.h"
#include "xr/xr_face_tracker.h"
#include "xr/xr_hand_tracker.h"
#include "xr/xr_interface.h"
@@ -325,12 +326,14 @@ void register_server_types() {
GDREGISTER_ABSTRACT_CLASS(XRInterface);
GDREGISTER_CLASS(XRBodyTracker);
+ GDREGISTER_CLASS(XRControllerTracker);
GDREGISTER_CLASS(XRFaceTracker);
GDREGISTER_CLASS(XRHandTracker);
GDREGISTER_CLASS(XRInterfaceExtension); // can't register this as virtual because we need a creation function for our extensions.
GDREGISTER_CLASS(XRPose);
GDREGISTER_CLASS(XRPositionalTracker);
GDREGISTER_CLASS(XRServer);
+ GDREGISTER_ABSTRACT_CLASS(XRTracker);
#endif // _3D_DISABLED
GDREGISTER_ABSTRACT_CLASS(NavigationServer3D);
diff --git a/servers/xr/xr_body_tracker.cpp b/servers/xr/xr_body_tracker.cpp
index cd58c14348..9c82b80911 100644
--- a/servers/xr/xr_body_tracker.cpp
+++ b/servers/xr/xr_body_tracker.cpp
@@ -134,6 +134,14 @@ void XRBodyTracker::_bind_methods() {
BIND_BITFIELD_FLAG(JOINT_FLAG_POSITION_TRACKED);
}
+void XRBodyTracker::set_tracker_type(XRServer::TrackerType p_type) {
+ ERR_FAIL_COND_MSG(p_type != XRServer::TRACKER_BODY, "XRBodyTracker must be of type TRACKER_BODY.");
+}
+
+void XRBodyTracker::set_tracker_hand(const XRPositionalTracker::TrackerHand p_hand) {
+ ERR_FAIL_COND_MSG(p_hand != XRPositionalTracker::TRACKER_HAND_UNKNOWN, "XRBodyTracker cannot specify hand.");
+}
+
void XRBodyTracker::set_has_tracking_data(bool p_has_tracking_data) {
has_tracking_data = p_has_tracking_data;
}
@@ -169,3 +177,7 @@ Transform3D XRBodyTracker::get_joint_transform(Joint p_joint) const {
ERR_FAIL_INDEX_V(p_joint, JOINT_MAX, Transform3D());
return joint_transforms[p_joint];
}
+
+XRBodyTracker::XRBodyTracker() {
+ type = XRServer::TRACKER_BODY;
+}
diff --git a/servers/xr/xr_body_tracker.h b/servers/xr/xr_body_tracker.h
index 659aa39df1..544de8ca8b 100644
--- a/servers/xr/xr_body_tracker.h
+++ b/servers/xr/xr_body_tracker.h
@@ -31,10 +31,10 @@
#ifndef XR_BODY_TRACKER_H
#define XR_BODY_TRACKER_H
-#include "core/object/ref_counted.h"
+#include "servers/xr/xr_positional_tracker.h"
-class XRBodyTracker : public RefCounted {
- GDCLASS(XRBodyTracker, RefCounted);
+class XRBodyTracker : public XRPositionalTracker {
+ GDCLASS(XRBodyTracker, XRPositionalTracker);
_THREAD_SAFE_CLASS_
public:
@@ -140,6 +140,9 @@ public:
JOINT_FLAG_POSITION_TRACKED = 8,
};
+ void set_tracker_type(XRServer::TrackerType p_type) override;
+ void set_tracker_hand(const XRPositionalTracker::TrackerHand p_hand) override;
+
void set_has_tracking_data(bool p_has_tracking_data);
bool get_has_tracking_data() const;
@@ -152,6 +155,8 @@ public:
void set_joint_transform(Joint p_joint, const Transform3D &p_transform);
Transform3D get_joint_transform(Joint p_joint) const;
+ XRBodyTracker();
+
protected:
static void _bind_methods();
diff --git a/servers/xr/xr_controller_tracker.cpp b/servers/xr/xr_controller_tracker.cpp
new file mode 100644
index 0000000000..df85e86b7e
--- /dev/null
+++ b/servers/xr/xr_controller_tracker.cpp
@@ -0,0 +1,39 @@
+/**************************************************************************/
+/* xr_controller_tracker.cpp */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#include "xr_controller_tracker.h"
+
+#include "core/input/input.h"
+
+void XRControllerTracker::_bind_methods(){};
+
+XRControllerTracker::XRControllerTracker() {
+ type = XRServer::TRACKER_CONTROLLER;
+}
diff --git a/servers/xr/xr_controller_tracker.h b/servers/xr/xr_controller_tracker.h
new file mode 100644
index 0000000000..a443cc1fd8
--- /dev/null
+++ b/servers/xr/xr_controller_tracker.h
@@ -0,0 +1,52 @@
+/**************************************************************************/
+/* xr_controller_tracker.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#ifndef XR_CONTROLLER_TRACKER_H
+#define XR_CONTROLLER_TRACKER_H
+
+#include "core/os/thread_safe.h"
+#include "servers/xr/xr_positional_tracker.h"
+
+/**
+ The controller tracker object as an object that represents the position and orientation of a controller.
+*/
+
+class XRControllerTracker : public XRPositionalTracker {
+ GDCLASS(XRControllerTracker, XRPositionalTracker);
+ _THREAD_SAFE_CLASS_
+
+protected:
+ static void _bind_methods();
+
+public:
+ XRControllerTracker();
+};
+
+#endif // XR_CONTROLLER_TRACKER_H
diff --git a/servers/xr/xr_face_tracker.cpp b/servers/xr/xr_face_tracker.cpp
index a38ccfd527..7015cd0805 100644
--- a/servers/xr/xr_face_tracker.cpp
+++ b/servers/xr/xr_face_tracker.cpp
@@ -187,6 +187,10 @@ void XRFaceTracker::_bind_methods() {
ADD_PROPERTY_DEFAULT("blend_shapes", PackedFloat32Array()); // To prevent ludicrously large default values.
}
+void XRFaceTracker::set_tracker_type(XRServer::TrackerType p_type) {
+ ERR_FAIL_COND_MSG(p_type != XRServer::TRACKER_FACE, "XRFaceTracker must be of type TRACKER_FACE.");
+}
+
float XRFaceTracker::get_blend_shape(BlendShapeEntry p_blend_shape) const {
// Fail if the blend shape index is out of range.
ERR_FAIL_INDEX_V(p_blend_shape, FT_MAX, 0.0f);
@@ -220,3 +224,7 @@ void XRFaceTracker::set_blend_shapes(const PackedFloat32Array &p_blend_shapes) {
// Copy the blend shape values into the blend shape array.
memcpy(blend_shape_values, p_blend_shapes.ptr(), sizeof(blend_shape_values));
}
+
+XRFaceTracker::XRFaceTracker() {
+ type = XRServer::TRACKER_FACE;
+}
diff --git a/servers/xr/xr_face_tracker.h b/servers/xr/xr_face_tracker.h
index b9f553cba6..a753a7abdc 100644
--- a/servers/xr/xr_face_tracker.h
+++ b/servers/xr/xr_face_tracker.h
@@ -31,7 +31,7 @@
#ifndef XR_FACE_TRACKER_H
#define XR_FACE_TRACKER_H
-#include "core/object/ref_counted.h"
+#include "servers/xr/xr_tracker.h"
/**
The XRFaceTracker class provides face blend shape weights.
@@ -41,8 +41,8 @@
and Meta Movement standards.
*/
-class XRFaceTracker : public RefCounted {
- GDCLASS(XRFaceTracker, RefCounted);
+class XRFaceTracker : public XRTracker {
+ GDCLASS(XRFaceTracker, XRTracker);
_THREAD_SAFE_CLASS_
public:
@@ -195,12 +195,16 @@ public:
FT_MAX // Maximum blend shape.
};
+ void set_tracker_type(XRServer::TrackerType p_type) override;
+
float get_blend_shape(BlendShapeEntry p_blend_shape) const;
void set_blend_shape(BlendShapeEntry p_blend_shape, float p_value);
PackedFloat32Array get_blend_shapes() const;
void set_blend_shapes(const PackedFloat32Array &p_blend_shapes);
+ XRFaceTracker();
+
protected:
static void _bind_methods();
diff --git a/servers/xr/xr_hand_tracker.cpp b/servers/xr/xr_hand_tracker.cpp
index 8cc2d5f7d2..cb0fbfb35f 100644
--- a/servers/xr/xr_hand_tracker.cpp
+++ b/servers/xr/xr_hand_tracker.cpp
@@ -30,6 +30,8 @@
#include "xr_hand_tracker.h"
+#include "xr_body_tracker.h"
+
void XRHandTracker::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_hand", "hand"), &XRHandTracker::set_hand);
ClassDB::bind_method(D_METHOD("get_hand"), &XRHandTracker::get_hand);
@@ -55,7 +57,6 @@ void XRHandTracker::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_hand_joint_angular_velocity", "joint", "angular_velocity"), &XRHandTracker::set_hand_joint_angular_velocity);
ClassDB::bind_method(D_METHOD("get_hand_joint_angular_velocity", "joint"), &XRHandTracker::get_hand_joint_angular_velocity);
- ADD_PROPERTY(PropertyInfo(Variant::INT, "hand", PROPERTY_HINT_ENUM, "Left,Right"), "set_hand", "get_hand");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "has_tracking_data", PROPERTY_HINT_NONE), "set_has_tracking_data", "get_has_tracking_data");
ADD_PROPERTY(PropertyInfo(Variant::INT, "hand_tracking_source", PROPERTY_HINT_ENUM, "Unknown,Unobstructed,Controller"), "set_hand_tracking_source", "get_hand_tracking_source");
@@ -104,8 +105,49 @@ void XRHandTracker::_bind_methods() {
BIND_BITFIELD_FLAG(HAND_JOINT_FLAG_ANGULAR_VELOCITY_VALID);
}
+void XRHandTracker::set_tracker_type(XRServer::TrackerType p_type) {
+ ERR_FAIL_COND_MSG(p_type != XRServer::TRACKER_HAND, "XRHandTracker must be of type TRACKER_HAND.");
+}
+
+void XRHandTracker::set_tracker_hand(const XRPositionalTracker::TrackerHand p_hand) {
+ ERR_FAIL_INDEX(p_hand, TRACKER_HAND_MAX);
+
+ switch (p_hand) {
+ case TRACKER_HAND_LEFT:
+ tracker_hand = TRACKER_HAND_LEFT;
+ hand = HAND_LEFT;
+ break;
+
+ case TRACKER_HAND_RIGHT:
+ tracker_hand = TRACKER_HAND_RIGHT;
+ hand = HAND_RIGHT;
+ break;
+
+ case TRACKER_HAND_UNKNOWN:
+ default:
+ ERR_FAIL_MSG("XRHandTracker must specify hand");
+ break;
+ }
+}
+
void XRHandTracker::set_hand(XRHandTracker::Hand p_hand) {
- hand = p_hand;
+ ERR_FAIL_INDEX(p_hand, HAND_MAX);
+
+ switch (p_hand) {
+ case HAND_LEFT:
+ tracker_hand = TRACKER_HAND_LEFT;
+ hand = HAND_LEFT;
+ break;
+
+ case HAND_RIGHT:
+ tracker_hand = TRACKER_HAND_RIGHT;
+ hand = HAND_RIGHT;
+ break;
+
+ default:
+ ERR_FAIL_MSG("XRHandTracker must specify hand");
+ break;
+ }
}
XRHandTracker::Hand XRHandTracker::get_hand() const {
@@ -177,3 +219,7 @@ Vector3 XRHandTracker::get_hand_joint_angular_velocity(XRHandTracker::HandJoint
ERR_FAIL_INDEX_V(p_joint, HAND_JOINT_MAX, Vector3());
return hand_joint_angular_velocities[p_joint];
}
+
+XRHandTracker::XRHandTracker() {
+ type = XRServer::TRACKER_HAND;
+}
diff --git a/servers/xr/xr_hand_tracker.h b/servers/xr/xr_hand_tracker.h
index 648f02d1f8..8ef3c229c3 100644
--- a/servers/xr/xr_hand_tracker.h
+++ b/servers/xr/xr_hand_tracker.h
@@ -31,10 +31,10 @@
#ifndef XR_HAND_TRACKER_H
#define XR_HAND_TRACKER_H
-#include "core/object/ref_counted.h"
+#include "servers/xr/xr_positional_tracker.h"
-class XRHandTracker : public RefCounted {
- GDCLASS(XRHandTracker, RefCounted);
+class XRHandTracker : public XRPositionalTracker {
+ GDCLASS(XRHandTracker, XRPositionalTracker);
_THREAD_SAFE_CLASS_
public:
@@ -90,6 +90,9 @@ public:
HAND_JOINT_FLAG_ANGULAR_VELOCITY_VALID = 32,
};
+ void set_tracker_type(XRServer::TrackerType p_type) override;
+ void set_tracker_hand(const XRPositionalTracker::TrackerHand p_hand) override;
+
void set_hand(Hand p_hand);
Hand get_hand() const;
@@ -114,6 +117,8 @@ public:
void set_hand_joint_angular_velocity(HandJoint p_joint, const Vector3 &p_velocity);
Vector3 get_hand_joint_angular_velocity(HandJoint p_joint) const;
+ XRHandTracker();
+
protected:
static void _bind_methods();
diff --git a/servers/xr/xr_positional_tracker.cpp b/servers/xr/xr_positional_tracker.cpp
index 6c15e4c1b0..b479237730 100644
--- a/servers/xr/xr_positional_tracker.cpp
+++ b/servers/xr/xr_positional_tracker.cpp
@@ -31,23 +31,13 @@
#include "xr_positional_tracker.h"
#include "core/input/input.h"
+#include "xr_controller_tracker.h"
void XRPositionalTracker::_bind_methods() {
BIND_ENUM_CONSTANT(TRACKER_HAND_UNKNOWN);
BIND_ENUM_CONSTANT(TRACKER_HAND_LEFT);
BIND_ENUM_CONSTANT(TRACKER_HAND_RIGHT);
-
- ClassDB::bind_method(D_METHOD("get_tracker_type"), &XRPositionalTracker::get_tracker_type);
- ClassDB::bind_method(D_METHOD("set_tracker_type", "type"), &XRPositionalTracker::set_tracker_type);
- ADD_PROPERTY(PropertyInfo(Variant::INT, "type"), "set_tracker_type", "get_tracker_type");
-
- ClassDB::bind_method(D_METHOD("get_tracker_name"), &XRPositionalTracker::get_tracker_name);
- ClassDB::bind_method(D_METHOD("set_tracker_name", "name"), &XRPositionalTracker::set_tracker_name);
- ADD_PROPERTY(PropertyInfo(Variant::STRING, "name"), "set_tracker_name", "get_tracker_name");
-
- ClassDB::bind_method(D_METHOD("get_tracker_desc"), &XRPositionalTracker::get_tracker_desc);
- ClassDB::bind_method(D_METHOD("set_tracker_desc", "description"), &XRPositionalTracker::set_tracker_desc);
- ADD_PROPERTY(PropertyInfo(Variant::STRING, "description"), "set_tracker_desc", "get_tracker_desc");
+ BIND_ENUM_CONSTANT(TRACKER_HAND_MAX);
ClassDB::bind_method(D_METHOD("get_tracker_profile"), &XRPositionalTracker::get_tracker_profile);
ClassDB::bind_method(D_METHOD("set_tracker_profile", "profile"), &XRPositionalTracker::set_tracker_profile);
@@ -73,34 +63,6 @@ void XRPositionalTracker::_bind_methods() {
ADD_SIGNAL(MethodInfo("profile_changed", PropertyInfo(Variant::STRING, "role")));
};
-void XRPositionalTracker::set_tracker_type(XRServer::TrackerType p_type) {
- if (type != p_type) {
- type = p_type;
- hand = XRPositionalTracker::TRACKER_HAND_UNKNOWN;
- };
-};
-
-XRServer::TrackerType XRPositionalTracker::get_tracker_type() const {
- return type;
-};
-
-void XRPositionalTracker::set_tracker_name(const StringName &p_name) {
- // Note: this should not be changed after the tracker is registered with the XRServer!
- name = p_name;
-};
-
-StringName XRPositionalTracker::get_tracker_name() const {
- return name;
-};
-
-void XRPositionalTracker::set_tracker_desc(const String &p_desc) {
- description = p_desc;
-}
-
-String XRPositionalTracker::get_tracker_desc() const {
- return description;
-}
-
void XRPositionalTracker::set_tracker_profile(const String &p_profile) {
if (profile != p_profile) {
profile = p_profile;
@@ -114,19 +76,12 @@ String XRPositionalTracker::get_tracker_profile() const {
}
XRPositionalTracker::TrackerHand XRPositionalTracker::get_tracker_hand() const {
- return hand;
+ return tracker_hand;
};
void XRPositionalTracker::set_tracker_hand(const XRPositionalTracker::TrackerHand p_hand) {
- XRServer *xr_server = XRServer::get_singleton();
- ERR_FAIL_NULL(xr_server);
-
- if (hand != p_hand) {
- // we can only set this if we've previously set this to be a controller!!
- ERR_FAIL_COND((type != XRServer::TRACKER_CONTROLLER) && (p_hand != XRPositionalTracker::TRACKER_HAND_UNKNOWN));
-
- hand = p_hand;
- };
+ ERR_FAIL_INDEX(p_hand, TRACKER_HAND_MAX);
+ tracker_hand = p_hand;
};
bool XRPositionalTracker::has_pose(const StringName &p_action_name) const {
@@ -177,6 +132,11 @@ void XRPositionalTracker::set_pose(const StringName &p_action_name, const Transf
}
Variant XRPositionalTracker::get_input(const StringName &p_action_name) const {
+ // Complain if this method is called on a XRPositionalTracker instance.
+ if (!dynamic_cast<const XRControllerTracker *>(this)) {
+ WARN_DEPRECATED_MSG(R"*(The "get_input()" method is deprecated, use "XRControllerTracker" instead.)*");
+ }
+
if (inputs.has(p_action_name)) {
return inputs[p_action_name];
} else {
@@ -185,10 +145,13 @@ Variant XRPositionalTracker::get_input(const StringName &p_action_name) const {
}
void XRPositionalTracker::set_input(const StringName &p_action_name, const Variant &p_value) {
- bool changed = false;
+ // Complain if this method is called on a XRPositionalTracker instance.
+ if (!dynamic_cast<XRControllerTracker *>(this)) {
+ WARN_DEPRECATED_MSG(R"*(The "set_input()" method is deprecated, use "XRControllerTracker" instead.)*");
+ }
// XR inputs
-
+ bool changed;
if (inputs.has(p_action_name)) {
changed = inputs[p_action_name] != p_value;
} else {
@@ -227,9 +190,3 @@ void XRPositionalTracker::set_input(const StringName &p_action_name, const Varia
}
}
}
-
-XRPositionalTracker::XRPositionalTracker() {
- type = XRServer::TRACKER_UNKNOWN;
- name = "Unknown";
- hand = TRACKER_HAND_UNKNOWN;
-};
diff --git a/servers/xr/xr_positional_tracker.h b/servers/xr/xr_positional_tracker.h
index d8939b4582..9e4e4d1cc3 100644
--- a/servers/xr/xr_positional_tracker.h
+++ b/servers/xr/xr_positional_tracker.h
@@ -34,6 +34,7 @@
#include "core/os/thread_safe.h"
#include "scene/resources/mesh.h"
#include "servers/xr/xr_pose.h"
+#include "servers/xr/xr_tracker.h"
#include "servers/xr_server.h"
/**
@@ -42,41 +43,33 @@
This is where potentially additional AR/VR interfaces may be active as there are AR/VR SDKs that solely deal with positional tracking.
*/
-class XRPositionalTracker : public RefCounted {
- GDCLASS(XRPositionalTracker, RefCounted);
+class XRPositionalTracker : public XRTracker {
+ GDCLASS(XRPositionalTracker, XRTracker);
_THREAD_SAFE_CLASS_
public:
enum TrackerHand {
TRACKER_HAND_UNKNOWN, /* unknown or not applicable */
TRACKER_HAND_LEFT, /* controller is the left hand controller */
- TRACKER_HAND_RIGHT /* controller is the right hand controller */
+ TRACKER_HAND_RIGHT, /* controller is the right hand controller */
+ TRACKER_HAND_MAX
};
-private:
- XRServer::TrackerType type; // type of tracker
- StringName name; // (unique) name of the tracker
- String description; // description of the tracker
+protected:
String profile; // this is interface dependent, for OpenXR this will be the interaction profile bound for to the tracker
- TrackerHand hand; // if known, the hand this tracker is held in
+ TrackerHand tracker_hand = TRACKER_HAND_UNKNOWN; // if known, the hand this tracker is held in
HashMap<StringName, Ref<XRPose>> poses;
HashMap<StringName, Variant> inputs;
-protected:
static void _bind_methods();
public:
- void set_tracker_type(XRServer::TrackerType p_type);
- XRServer::TrackerType get_tracker_type() const;
- void set_tracker_name(const StringName &p_name);
- StringName get_tracker_name() const;
- void set_tracker_desc(const String &p_desc);
- String get_tracker_desc() const;
void set_tracker_profile(const String &p_profile);
String get_tracker_profile() const;
+
XRPositionalTracker::TrackerHand get_tracker_hand() const;
- void set_tracker_hand(const XRPositionalTracker::TrackerHand p_hand);
+ virtual void set_tracker_hand(const XRPositionalTracker::TrackerHand p_hand);
bool has_pose(const StringName &p_action_name) const;
Ref<XRPose> get_pose(const StringName &p_action_name) const;
@@ -85,9 +78,6 @@ public:
Variant get_input(const StringName &p_action_name) const;
void set_input(const StringName &p_action_name, const Variant &p_value);
-
- XRPositionalTracker();
- ~XRPositionalTracker() {}
};
VARIANT_ENUM_CAST(XRPositionalTracker::TrackerHand);
diff --git a/servers/xr/xr_tracker.cpp b/servers/xr/xr_tracker.cpp
new file mode 100644
index 0000000000..0b917a5dc3
--- /dev/null
+++ b/servers/xr/xr_tracker.cpp
@@ -0,0 +1,70 @@
+/**************************************************************************/
+/* xr_tracker.cpp */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#include "xr_tracker.h"
+
+void XRTracker::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("get_tracker_type"), &XRTracker::get_tracker_type);
+ ClassDB::bind_method(D_METHOD("set_tracker_type", "type"), &XRTracker::set_tracker_type);
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "type"), "set_tracker_type", "get_tracker_type");
+
+ ClassDB::bind_method(D_METHOD("get_tracker_name"), &XRTracker::get_tracker_name);
+ ClassDB::bind_method(D_METHOD("set_tracker_name", "name"), &XRTracker::set_tracker_name);
+ ADD_PROPERTY(PropertyInfo(Variant::STRING, "name"), "set_tracker_name", "get_tracker_name");
+
+ ClassDB::bind_method(D_METHOD("get_tracker_desc"), &XRTracker::get_tracker_desc);
+ ClassDB::bind_method(D_METHOD("set_tracker_desc", "description"), &XRTracker::set_tracker_desc);
+ ADD_PROPERTY(PropertyInfo(Variant::STRING, "description"), "set_tracker_desc", "get_tracker_desc");
+};
+
+void XRTracker::set_tracker_type(XRServer::TrackerType p_type) {
+ type = p_type;
+};
+
+XRServer::TrackerType XRTracker::get_tracker_type() const {
+ return type;
+};
+
+void XRTracker::set_tracker_name(const StringName &p_name) {
+ // Note: this should not be changed after the tracker is registered with the XRServer!
+ name = p_name;
+};
+
+StringName XRTracker::get_tracker_name() const {
+ return name;
+};
+
+void XRTracker::set_tracker_desc(const String &p_desc) {
+ description = p_desc;
+}
+
+String XRTracker::get_tracker_desc() const {
+ return description;
+}
diff --git a/servers/xr/xr_tracker.h b/servers/xr/xr_tracker.h
new file mode 100644
index 0000000000..3348e164d8
--- /dev/null
+++ b/servers/xr/xr_tracker.h
@@ -0,0 +1,61 @@
+/**************************************************************************/
+/* xr_tracker.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#ifndef XR_TRACKER_H
+#define XR_TRACKER_H
+
+#include "core/os/thread_safe.h"
+#include "servers/xr_server.h"
+
+/**
+ The XR tracker object is a common base for all different types of XR trackers.
+*/
+
+class XRTracker : public RefCounted {
+ GDCLASS(XRTracker, RefCounted);
+ _THREAD_SAFE_CLASS_
+
+protected:
+ XRServer::TrackerType type = XRServer::TRACKER_UNKNOWN; // type of tracker
+ StringName name = "Unknown"; // (unique) name of the tracker
+ String description; // description of the tracker
+
+ static void _bind_methods();
+
+public:
+ virtual void set_tracker_type(XRServer::TrackerType p_type);
+ XRServer::TrackerType get_tracker_type() const;
+ void set_tracker_name(const StringName &p_name);
+ StringName get_tracker_name() const;
+ void set_tracker_desc(const String &p_desc);
+ String get_tracker_desc() const;
+};
+
+#endif // XR_TRACKER_H
diff --git a/servers/xr_server.compat.inc b/servers/xr_server.compat.inc
new file mode 100644
index 0000000000..967666fa6f
--- /dev/null
+++ b/servers/xr_server.compat.inc
@@ -0,0 +1,51 @@
+/**************************************************************************/
+/* xr_server.compat.inc */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#ifndef DISABLE_DEPRECATED
+
+void XRServer::_add_tracker_bind_compat_90645(const Ref<XRPositionalTracker> &p_tracker) {
+ add_tracker(p_tracker);
+}
+
+void XRServer::_remove_tracker_bind_compat_90645(const Ref<XRPositionalTracker> &p_tracker) {
+ remove_tracker(p_tracker);
+}
+
+Ref<XRPositionalTracker> XRServer::_get_tracker_bind_compat_90645(const StringName &p_name) const {
+ return get_tracker(p_name);
+}
+
+void XRServer::_bind_compatibility_methods() {
+ ClassDB::bind_compatibility_method(D_METHOD("add_tracker", "tracker"), &XRServer::_add_tracker_bind_compat_90645);
+ ClassDB::bind_compatibility_method(D_METHOD("remove_tracker", "tracker"), &XRServer::_remove_tracker_bind_compat_90645);
+ ClassDB::bind_compatibility_method(D_METHOD("get_tracker", "name"), &XRServer::_get_tracker_bind_compat_90645);
+}
+
+#endif // DISABLE_DEPRECATED
diff --git a/servers/xr_server.cpp b/servers/xr_server.cpp
index af14ba4a00..f1105a650d 100644
--- a/servers/xr_server.cpp
+++ b/servers/xr_server.cpp
@@ -35,6 +35,7 @@
#include "xr/xr_hand_tracker.h"
#include "xr/xr_interface.h"
#include "xr/xr_positional_tracker.h"
+#include "xr_server.compat.inc"
XRServer::XRMode XRServer::xr_mode = XRMODE_DEFAULT;
@@ -77,21 +78,6 @@ void XRServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_trackers", "tracker_types"), &XRServer::get_trackers);
ClassDB::bind_method(D_METHOD("get_tracker", "tracker_name"), &XRServer::get_tracker);
- ClassDB::bind_method(D_METHOD("add_hand_tracker", "tracker_name", "hand_tracker"), &XRServer::add_hand_tracker);
- ClassDB::bind_method(D_METHOD("remove_hand_tracker", "tracker_name"), &XRServer::remove_hand_tracker);
- ClassDB::bind_method(D_METHOD("get_hand_trackers"), &XRServer::get_hand_trackers);
- ClassDB::bind_method(D_METHOD("get_hand_tracker", "tracker_name"), &XRServer::get_hand_tracker);
-
- ClassDB::bind_method(D_METHOD("add_face_tracker", "tracker_name", "face_tracker"), &XRServer::add_face_tracker);
- ClassDB::bind_method(D_METHOD("remove_face_tracker", "tracker_name"), &XRServer::remove_face_tracker);
- ClassDB::bind_method(D_METHOD("get_face_trackers"), &XRServer::get_face_trackers);
- ClassDB::bind_method(D_METHOD("get_face_tracker", "tracker_name"), &XRServer::get_face_tracker);
-
- ClassDB::bind_method(D_METHOD("add_body_tracker", "tracker_name", "body_tracker"), &XRServer::add_body_tracker);
- ClassDB::bind_method(D_METHOD("remove_body_tracker", "tracker_name"), &XRServer::remove_body_tracker);
- ClassDB::bind_method(D_METHOD("get_body_trackers"), &XRServer::get_body_trackers);
- ClassDB::bind_method(D_METHOD("get_body_tracker", "tracker_name"), &XRServer::get_body_tracker);
-
ClassDB::bind_method(D_METHOD("get_primary_interface"), &XRServer::get_primary_interface);
ClassDB::bind_method(D_METHOD("set_primary_interface", "interface"), &XRServer::set_primary_interface);
@@ -101,6 +87,9 @@ void XRServer::_bind_methods() {
BIND_ENUM_CONSTANT(TRACKER_CONTROLLER);
BIND_ENUM_CONSTANT(TRACKER_BASESTATION);
BIND_ENUM_CONSTANT(TRACKER_ANCHOR);
+ BIND_ENUM_CONSTANT(TRACKER_HAND);
+ BIND_ENUM_CONSTANT(TRACKER_BODY);
+ BIND_ENUM_CONSTANT(TRACKER_FACE);
BIND_ENUM_CONSTANT(TRACKER_ANY_KNOWN);
BIND_ENUM_CONSTANT(TRACKER_UNKNOWN);
BIND_ENUM_CONSTANT(TRACKER_ANY);
@@ -115,18 +104,6 @@ void XRServer::_bind_methods() {
ADD_SIGNAL(MethodInfo("tracker_added", PropertyInfo(Variant::STRING_NAME, "tracker_name"), PropertyInfo(Variant::INT, "type")));
ADD_SIGNAL(MethodInfo("tracker_updated", PropertyInfo(Variant::STRING_NAME, "tracker_name"), PropertyInfo(Variant::INT, "type")));
ADD_SIGNAL(MethodInfo("tracker_removed", PropertyInfo(Variant::STRING_NAME, "tracker_name"), PropertyInfo(Variant::INT, "type")));
-
- ADD_SIGNAL(MethodInfo("hand_tracker_added", PropertyInfo(Variant::STRING_NAME, "tracker_name"), PropertyInfo(Variant::OBJECT, "hand_tracker", PROPERTY_HINT_RESOURCE_TYPE, "XRHandTracker")));
- ADD_SIGNAL(MethodInfo("hand_tracker_updated", PropertyInfo(Variant::STRING_NAME, "tracker_name"), PropertyInfo(Variant::OBJECT, "hand_tracker", PROPERTY_HINT_RESOURCE_TYPE, "XRHandTracker")));
- ADD_SIGNAL(MethodInfo("hand_tracker_removed", PropertyInfo(Variant::STRING_NAME, "tracker_name")));
-
- ADD_SIGNAL(MethodInfo("face_tracker_added", PropertyInfo(Variant::STRING_NAME, "tracker_name"), PropertyInfo(Variant::OBJECT, "face_tracker", PROPERTY_HINT_RESOURCE_TYPE, "XRFaceTracker")));
- ADD_SIGNAL(MethodInfo("face_tracker_updated", PropertyInfo(Variant::STRING_NAME, "tracker_name"), PropertyInfo(Variant::OBJECT, "face_tracker", PROPERTY_HINT_RESOURCE_TYPE, "XRFaceTracker")));
- ADD_SIGNAL(MethodInfo("face_tracker_removed", PropertyInfo(Variant::STRING_NAME, "tracker_name")));
-
- ADD_SIGNAL(MethodInfo("body_tracker_added", PropertyInfo(Variant::STRING_NAME, "tracker_name"), PropertyInfo(Variant::OBJECT, "body_tracker", PROPERTY_HINT_RESOURCE_TYPE, "XRBodyTracker")));
- ADD_SIGNAL(MethodInfo("body_tracker_updated", PropertyInfo(Variant::STRING_NAME, "tracker_name"), PropertyInfo(Variant::OBJECT, "body_tracker", PROPERTY_HINT_RESOURCE_TYPE, "XRBodyTracker")));
- ADD_SIGNAL(MethodInfo("body_tracker_removed", PropertyInfo(Variant::STRING_NAME, "tracker_name")));
};
double XRServer::get_world_scale() const {
@@ -281,7 +258,7 @@ void XRServer::set_primary_interface(const Ref<XRInterface> &p_primary_interface
}
};
-void XRServer::add_tracker(Ref<XRPositionalTracker> p_tracker) {
+void XRServer::add_tracker(const Ref<XRTracker> &p_tracker) {
ERR_FAIL_COND(p_tracker.is_null());
StringName tracker_name = p_tracker->get_tracker_name();
@@ -297,7 +274,7 @@ void XRServer::add_tracker(Ref<XRPositionalTracker> p_tracker) {
}
};
-void XRServer::remove_tracker(Ref<XRPositionalTracker> p_tracker) {
+void XRServer::remove_tracker(const Ref<XRTracker> &p_tracker) {
ERR_FAIL_COND(p_tracker.is_null());
StringName tracker_name = p_tracker->get_tracker_name();
@@ -323,12 +300,12 @@ Dictionary XRServer::get_trackers(int p_tracker_types) {
return res;
}
-Ref<XRPositionalTracker> XRServer::get_tracker(const StringName &p_name) const {
+Ref<XRTracker> XRServer::get_tracker(const StringName &p_name) const {
if (trackers.has(p_name)) {
return trackers[p_name];
} else {
// tracker hasn't been registered yet, which is fine, no need to spam the error log...
- return Ref<XRPositionalTracker>();
+ return Ref<XRTracker>();
}
};
@@ -382,120 +359,6 @@ PackedStringArray XRServer::get_suggested_pose_names(const StringName &p_tracker
return arr;
}
-void XRServer::add_hand_tracker(const StringName &p_tracker_name, Ref<XRHandTracker> p_hand_tracker) {
- ERR_FAIL_COND(p_hand_tracker.is_null());
-
- if (!hand_trackers.has(p_tracker_name)) {
- // We don't have a tracker with this name, we're going to add it.
- hand_trackers[p_tracker_name] = p_hand_tracker;
- emit_signal(SNAME("hand_tracker_added"), p_tracker_name, p_hand_tracker);
- } else if (hand_trackers[p_tracker_name] != p_hand_tracker) {
- // We already have a tracker with this name, we're going to replace it.
- hand_trackers[p_tracker_name] = p_hand_tracker;
- emit_signal(SNAME("hand_tracker_updated"), p_tracker_name, p_hand_tracker);
- }
-}
-
-void XRServer::remove_hand_tracker(const StringName &p_tracker_name) {
- // Skip if no hand tracker is found.
- if (!hand_trackers.has(p_tracker_name)) {
- return;
- }
-
- // Send the removed signal, then remove the hand tracker.
- emit_signal(SNAME("hand_tracker_removed"), p_tracker_name);
- hand_trackers.erase(p_tracker_name);
-}
-
-Dictionary XRServer::get_hand_trackers() const {
- return hand_trackers;
-}
-
-Ref<XRHandTracker> XRServer::get_hand_tracker(const StringName &p_tracker_name) const {
- // Skip if no tracker is found.
- if (!hand_trackers.has(p_tracker_name)) {
- return Ref<XRHandTracker>();
- }
-
- return hand_trackers[p_tracker_name];
-}
-
-void XRServer::add_face_tracker(const StringName &p_tracker_name, Ref<XRFaceTracker> p_face_tracker) {
- ERR_FAIL_COND(p_face_tracker.is_null());
-
- if (!face_trackers.has(p_tracker_name)) {
- // We don't have a tracker with this name, we're going to add it.
- face_trackers[p_tracker_name] = p_face_tracker;
- emit_signal(SNAME("face_tracker_added"), p_tracker_name, p_face_tracker);
- } else if (face_trackers[p_tracker_name] != p_face_tracker) {
- // We already have a tracker with this name, we're going to replace it.
- face_trackers[p_tracker_name] = p_face_tracker;
- emit_signal(SNAME("face_tracker_updated"), p_tracker_name, p_face_tracker);
- }
-}
-
-void XRServer::remove_face_tracker(const StringName &p_tracker_name) {
- // Skip if no face tracker is found.
- if (!face_trackers.has(p_tracker_name)) {
- return;
- }
-
- // Send the removed signal, then remove the face tracker.
- emit_signal(SNAME("face_tracker_removed"), p_tracker_name);
- face_trackers.erase(p_tracker_name);
-}
-
-Dictionary XRServer::get_face_trackers() const {
- return face_trackers;
-}
-
-Ref<XRFaceTracker> XRServer::get_face_tracker(const StringName &p_tracker_name) const {
- // Skip if no tracker is found.
- if (!face_trackers.has(p_tracker_name)) {
- return Ref<XRFaceTracker>();
- }
-
- return face_trackers[p_tracker_name];
-}
-
-void XRServer::add_body_tracker(const StringName &p_tracker_name, Ref<XRBodyTracker> p_body_tracker) {
- ERR_FAIL_COND(p_body_tracker.is_null());
-
- if (!body_trackers.has(p_tracker_name)) {
- // We don't have a tracker with this name, we're going to add it.
- body_trackers[p_tracker_name] = p_body_tracker;
- emit_signal(SNAME("body_tracker_added"), p_tracker_name, p_body_tracker);
- } else if (body_trackers[p_tracker_name] != p_body_tracker) {
- // We already have a tracker with this name, we're going to replace it.
- body_trackers[p_tracker_name] = p_body_tracker;
- emit_signal(SNAME("body_tracker_updated"), p_tracker_name, p_body_tracker);
- }
-}
-
-void XRServer::remove_body_tracker(const StringName &p_tracker_name) {
- // Skip if no face tracker is found.
- if (!body_trackers.has(p_tracker_name)) {
- return;
- }
-
- // Send the removed signal, then remove the face tracker.
- emit_signal(SNAME("body_tracker_removed"), p_tracker_name);
- body_trackers.erase(p_tracker_name);
-}
-
-Dictionary XRServer::get_body_trackers() const {
- return body_trackers;
-}
-
-Ref<XRBodyTracker> XRServer::get_body_tracker(const StringName &p_tracker_name) const {
- // Skip if no tracker is found.
- if (!body_trackers.has(p_tracker_name)) {
- return Ref<XRBodyTracker>();
- }
-
- return body_trackers[p_tracker_name];
-}
-
void XRServer::_process() {
// called from our main game loop before we handle physics and game logic
// note that we can have multiple interfaces active if we have interfaces that purely handle tracking
@@ -545,14 +408,8 @@ XRServer::XRServer() {
XRServer::~XRServer() {
primary_interface.unref();
- while (interfaces.size() > 0) {
- interfaces.remove_at(0);
- }
-
- // TODO pretty sure there is a clear function or something...
- while (trackers.size() > 0) {
- trackers.erase(trackers.get_key_at_index(0));
- }
+ interfaces.clear();
+ trackers.clear();
singleton = nullptr;
};
diff --git a/servers/xr_server.h b/servers/xr_server.h
index 6aaa34b21d..717728171a 100644
--- a/servers/xr_server.h
+++ b/servers/xr_server.h
@@ -38,10 +38,8 @@
#include "core/variant/variant.h"
class XRInterface;
+class XRTracker;
class XRPositionalTracker;
-class XRHandTracker;
-class XRFaceTracker;
-class XRBodyTracker;
/**
The XR server is a singleton object that gives access to the various
@@ -71,6 +69,9 @@ public:
TRACKER_CONTROLLER = 0x02, /* tracks a controller */
TRACKER_BASESTATION = 0x04, /* tracks location of a base station */
TRACKER_ANCHOR = 0x08, /* tracks an anchor point, used in AR to track a real live location */
+ TRACKER_HAND = 0x10, /* tracks a hand */
+ TRACKER_BODY = 0x20, /* tracks a body */
+ TRACKER_FACE = 0x40, /* tracks a face */
TRACKER_UNKNOWN = 0x80, /* unknown tracker */
TRACKER_ANY_KNOWN = 0x7f, /* all except unknown */
@@ -88,9 +89,6 @@ private:
Vector<Ref<XRInterface>> interfaces;
Dictionary trackers;
- Dictionary hand_trackers;
- Dictionary face_trackers;
- Dictionary body_trackers;
Ref<XRInterface> primary_interface; /* we'll identify one interface as primary, this will be used by our viewports */
@@ -103,6 +101,13 @@ protected:
static void _bind_methods();
+#ifndef DISABLE_DEPRECATED
+ static void _bind_compatibility_methods();
+ void _add_tracker_bind_compat_90645(const Ref<XRPositionalTracker> &p_tracker);
+ void _remove_tracker_bind_compat_90645(const Ref<XRPositionalTracker> &p_tracker);
+ Ref<XRPositionalTracker> _get_tracker_bind_compat_90645(const StringName &p_name) const;
+#endif
+
public:
static XRMode get_xr_mode();
static void set_xr_mode(XRMode p_mode);
@@ -174,13 +179,13 @@ public:
void set_primary_interface(const Ref<XRInterface> &p_primary_interface);
/*
- Our trackers are objects that expose the orientation and position of physical devices such as controller, anchor points, etc.
+ Our trackers are objects that expose tracked information about physical objects such as controller, anchor points, faces, hands etc.
They are created and managed by our active AR/VR interfaces.
*/
- void add_tracker(Ref<XRPositionalTracker> p_tracker);
- void remove_tracker(Ref<XRPositionalTracker> p_tracker);
+ void add_tracker(const Ref<XRTracker> &p_tracker);
+ void remove_tracker(const Ref<XRTracker> &p_tracker);
Dictionary get_trackers(int p_tracker_types);
- Ref<XRPositionalTracker> get_tracker(const StringName &p_name) const;
+ Ref<XRTracker> get_tracker(const StringName &p_name) const;
/*
We don't know which trackers and actions will existing during runtime but we can request suggested names from our interfaces to help our IDE UI.
@@ -189,30 +194,6 @@ public:
PackedStringArray get_suggested_pose_names(const StringName &p_tracker_name) const;
// Q: Should we add get_suggested_input_names and get_suggested_haptic_names even though we don't use them for the IDE?
- /*
- Hand trackers are objects that expose the tracked joints of a hand.
- */
- void add_hand_tracker(const StringName &p_tracker_name, Ref<XRHandTracker> p_hand_tracker);
- void remove_hand_tracker(const StringName &p_tracker_name);
- Dictionary get_hand_trackers() const;
- Ref<XRHandTracker> get_hand_tracker(const StringName &p_tracker_name) const;
-
- /*
- Face trackers are objects that expose the tracked blend shapes of a face.
- */
- void add_face_tracker(const StringName &p_tracker_name, Ref<XRFaceTracker> p_face_tracker);
- void remove_face_tracker(const StringName &p_tracker_name);
- Dictionary get_face_trackers() const;
- Ref<XRFaceTracker> get_face_tracker(const StringName &p_tracker_name) const;
-
- /*
- Body trackers are objects that expose the tracked joints of a body.
- */
- void add_body_tracker(const StringName &p_tracker_name, Ref<XRBodyTracker> p_face_tracker);
- void remove_body_tracker(const StringName &p_tracker_name);
- Dictionary get_body_trackers() const;
- Ref<XRBodyTracker> get_body_tracker(const StringName &p_tracker_name) const;
-
// Process is called before we handle our physics process and game process. This is where our interfaces will update controller data and such.
void _process();
diff --git a/tests/core/math/test_geometry_2d.h b/tests/core/math/test_geometry_2d.h
index 50b2575700..a4bb6dfca0 100644
--- a/tests/core/math/test_geometry_2d.h
+++ b/tests/core/math/test_geometry_2d.h
@@ -711,12 +711,12 @@ TEST_CASE("[Geometry2D] Clip polyline with polygon") {
r = Geometry2D::clip_polyline_with_polygon(l, p);
REQUIRE_MESSAGE(r.size() == 2, "There should be 2 resulting clipped lines.");
REQUIRE_MESSAGE(r[0].size() == 3, "The resulting clipped line should have 3 vertices.");
- CHECK(r[0][0].is_equal_approx(Vector2(160, 320)));
+ CHECK(r[0][0].is_equal_approx(Vector2(121.412682, 225.038757)));
CHECK(r[0][1].is_equal_approx(Vector2(122, 250)));
- CHECK(r[0][2].is_equal_approx(Vector2(121.412682, 225.038757)));
+ CHECK(r[0][2].is_equal_approx(Vector2(160, 320)));
REQUIRE_MESSAGE(r[1].size() == 2, "The resulting clipped line should have 2 vertices.");
- CHECK(r[1][0].is_equal_approx(Vector2(53.07737, 116.143021)));
- CHECK(r[1][1].is_equal_approx(Vector2(55, 70)));
+ CHECK(r[1][0].is_equal_approx(Vector2(55, 70)));
+ CHECK(r[1][1].is_equal_approx(Vector2(53.07737, 116.143021)));
}
}
diff --git a/thirdparty/misc/clipper.cpp b/thirdparty/misc/clipper.cpp
deleted file mode 100644
index c67045d113..0000000000
--- a/thirdparty/misc/clipper.cpp
+++ /dev/null
@@ -1,4661 +0,0 @@
-/*******************************************************************************
-* *
-* Author : Angus Johnson *
-* Version : 6.4.2 *
-* Date : 27 February 2017 *
-* Website : http://www.angusj.com *
-* Copyright : Angus Johnson 2010-2017 *
-* *
-* License: *
-* Use, modification & distribution is subject to Boost Software License Ver 1. *
-* http://www.boost.org/LICENSE_1_0.txt *
-* *
-* Attributions: *
-* The code in this library is an extension of Bala Vatti's clipping algorithm: *
-* "A generic solution to polygon clipping" *
-* Communications of the ACM, Vol 35, Issue 7 (July 1992) pp 56-63. *
-* http://portal.acm.org/citation.cfm?id=129906 *
-* *
-* Computer graphics and geometric modeling: implementation and algorithms *
-* By Max K. Agoston *
-* Springer; 1 edition (January 4, 2005) *
-* http://books.google.com/books?q=vatti+clipping+agoston *
-* *
-* See also: *
-* "Polygon Offsetting by Computing Winding Numbers" *
-* Paper no. DETC2005-85513 pp. 565-575 *
-* ASME 2005 International Design Engineering Technical Conferences *
-* and Computers and Information in Engineering Conference (IDETC/CIE2005) *
-* September 24-28, 2005 , Long Beach, California, USA *
-* http://www.me.berkeley.edu/~mcmains/pubs/DAC05OffsetPolygon.pdf *
-* *
-*******************************************************************************/
-
-/*******************************************************************************
-* *
-* This is a translation of the Delphi Clipper library and the naming style *
-* used has retained a Delphi flavour. *
-* *
-*******************************************************************************/
-
-#include "clipper.hpp"
-#include <cmath>
-#include <vector>
-#include <algorithm>
-#include <stdexcept>
-#include <cstring>
-#include <cstdlib>
-#include <ostream>
-#include <functional>
-
-//Explicitly disables exceptions handling for target platform
-//#define CLIPPER_NOEXCEPTION
-
-#define CLIPPER_THROW(exception) std::abort()
-#define CLIPPER_TRY if(true)
-#define CLIPPER_CATCH(exception) if(false)
-
-#if defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)
- #ifndef CLIPPER_NOEXCEPTION
- #undef CLIPPER_THROW
- #define CLIPPER_THROW(exception) throw exception
- #undef CLIPPER_TRY
- #define CLIPPER_TRY try
- #undef CLIPPER_CATCH
- #define CLIPPER_CATCH(exception) catch(exception)
- #endif
-#endif
-
-//Optionally allows to override exception macros
-#if defined(CLIPPER_THROW_USER)
- #undef CLIPPER_THROW
- #define CLIPPER_THROW CLIPPER_THROW_USER
-#endif
-#if defined(CLIPPER_TRY_USER)
- #undef CLIPPER_TRY
- #define CLIPPER_TRY CLIPPER_TRY_USER
-#endif
-#if defined(CLIPPER_CATCH_USER)
- #undef CLIPPER_CATCH
- #define CLIPPER_CATCH CLIPPER_CATCH_USER
-#endif
-
-namespace ClipperLib {
-
-static double const pi = 3.141592653589793238;
-static double const two_pi = pi *2;
-static double const def_arc_tolerance = 0.25;
-
-enum Direction { dRightToLeft, dLeftToRight };
-
-static int const Unassigned = -1; //edge not currently 'owning' a solution
-static int const Skip = -2; //edge that would otherwise close a path
-
-#define HORIZONTAL (-1.0E+40)
-#define TOLERANCE (1.0e-20)
-#define NEAR_ZERO(val) (((val) > -TOLERANCE) && ((val) < TOLERANCE))
-
-struct TEdge {
- IntPoint Bot;
- IntPoint Curr; //current (updated for every new scanbeam)
- IntPoint Top;
- double Dx;
- PolyType PolyTyp;
- EdgeSide Side; //side only refers to current side of solution poly
- int WindDelta; //1 or -1 depending on winding direction
- int WindCnt;
- int WindCnt2; //winding count of the opposite polytype
- int OutIdx;
- TEdge *Next;
- TEdge *Prev;
- TEdge *NextInLML;
- TEdge *NextInAEL;
- TEdge *PrevInAEL;
- TEdge *NextInSEL;
- TEdge *PrevInSEL;
-};
-
-struct IntersectNode {
- TEdge *Edge1;
- TEdge *Edge2;
- IntPoint Pt;
-};
-
-struct LocalMinimum {
- cInt Y;
- TEdge *LeftBound;
- TEdge *RightBound;
-};
-
-struct OutPt;
-
-//OutRec: contains a path in the clipping solution. Edges in the AEL will
-//carry a pointer to an OutRec when they are part of the clipping solution.
-struct OutRec {
- int Idx;
- bool IsHole;
- bool IsOpen;
- OutRec *FirstLeft; //see comments in clipper.pas
- PolyNode *PolyNd;
- OutPt *Pts;
- OutPt *BottomPt;
-};
-
-struct OutPt {
- int Idx;
- IntPoint Pt;
- OutPt *Next;
- OutPt *Prev;
-};
-
-struct Join {
- OutPt *OutPt1;
- OutPt *OutPt2;
- IntPoint OffPt;
-};
-
-struct LocMinSorter
-{
- inline bool operator()(const LocalMinimum& locMin1, const LocalMinimum& locMin2)
- {
- return locMin2.Y < locMin1.Y;
- }
-};
-
-//------------------------------------------------------------------------------
-//------------------------------------------------------------------------------
-
-inline cInt Round(double val)
-{
- if ((val < 0)) return static_cast<cInt>(val - 0.5);
- else return static_cast<cInt>(val + 0.5);
-}
-//------------------------------------------------------------------------------
-
-inline cInt Abs(cInt val)
-{
- return val < 0 ? -val : val;
-}
-
-//------------------------------------------------------------------------------
-// PolyTree methods ...
-//------------------------------------------------------------------------------
-
-void PolyTree::Clear()
-{
- for (PolyNodes::size_type i = 0; i < AllNodes.size(); ++i)
- delete AllNodes[i];
- AllNodes.resize(0);
- Childs.resize(0);
-}
-//------------------------------------------------------------------------------
-
-PolyNode* PolyTree::GetFirst() const
-{
- if (!Childs.empty())
- return Childs[0];
- else
- return 0;
-}
-//------------------------------------------------------------------------------
-
-int PolyTree::Total() const
-{
- int result = (int)AllNodes.size();
- //with negative offsets, ignore the hidden outer polygon ...
- if (result > 0 && Childs[0] != AllNodes[0]) result--;
- return result;
-}
-
-//------------------------------------------------------------------------------
-// PolyNode methods ...
-//------------------------------------------------------------------------------
-
-PolyNode::PolyNode(): Parent(0), Index(0), m_IsOpen(false)
-{
-}
-//------------------------------------------------------------------------------
-
-int PolyNode::ChildCount() const
-{
- return (int)Childs.size();
-}
-//------------------------------------------------------------------------------
-
-void PolyNode::AddChild(PolyNode& child)
-{
- unsigned cnt = (unsigned)Childs.size();
- Childs.push_back(&child);
- child.Parent = this;
- child.Index = cnt;
-}
-//------------------------------------------------------------------------------
-
-PolyNode* PolyNode::GetNext() const
-{
- if (!Childs.empty())
- return Childs[0];
- else
- return GetNextSiblingUp();
-}
-//------------------------------------------------------------------------------
-
-PolyNode* PolyNode::GetNextSiblingUp() const
-{
- if (!Parent) //protects against PolyTree.GetNextSiblingUp()
- return 0;
- else if (Index == Parent->Childs.size() - 1)
- return Parent->GetNextSiblingUp();
- else
- return Parent->Childs[Index + 1];
-}
-//------------------------------------------------------------------------------
-
-bool PolyNode::IsHole() const
-{
- bool result = true;
- PolyNode* node = Parent;
- while (node)
- {
- result = !result;
- node = node->Parent;
- }
- return result;
-}
-//------------------------------------------------------------------------------
-
-bool PolyNode::IsOpen() const
-{
- return m_IsOpen;
-}
-//------------------------------------------------------------------------------
-
-#ifndef use_int32
-
-//------------------------------------------------------------------------------
-// Int128 class (enables safe math on signed 64bit integers)
-// eg Int128 val1((long64)9223372036854775807); //ie 2^63 -1
-// Int128 val2((long64)9223372036854775807);
-// Int128 val3 = val1 * val2;
-// val3.AsString => "85070591730234615847396907784232501249" (8.5e+37)
-//------------------------------------------------------------------------------
-
-class Int128
-{
- public:
- ulong64 lo;
- long64 hi;
-
- Int128(long64 _lo = 0)
- {
- lo = (ulong64)_lo;
- if (_lo < 0) hi = -1; else hi = 0;
- }
-
-
- Int128(const Int128 &val): lo(val.lo), hi(val.hi){}
-
- Int128(const long64& _hi, const ulong64& _lo): lo(_lo), hi(_hi){}
-
- Int128& operator = (const long64 &val)
- {
- lo = (ulong64)val;
- if (val < 0) hi = -1; else hi = 0;
- return *this;
- }
-
- bool operator == (const Int128 &val) const
- {return (hi == val.hi && lo == val.lo);}
-
- bool operator != (const Int128 &val) const
- { return !(*this == val);}
-
- bool operator > (const Int128 &val) const
- {
- if (hi != val.hi)
- return hi > val.hi;
- else
- return lo > val.lo;
- }
-
- bool operator < (const Int128 &val) const
- {
- if (hi != val.hi)
- return hi < val.hi;
- else
- return lo < val.lo;
- }
-
- bool operator >= (const Int128 &val) const
- { return !(*this < val);}
-
- bool operator <= (const Int128 &val) const
- { return !(*this > val);}
-
- Int128& operator += (const Int128 &rhs)
- {
- hi += rhs.hi;
- lo += rhs.lo;
- if (lo < rhs.lo) hi++;
- return *this;
- }
-
- Int128 operator + (const Int128 &rhs) const
- {
- Int128 result(*this);
- result+= rhs;
- return result;
- }
-
- Int128& operator -= (const Int128 &rhs)
- {
- *this += -rhs;
- return *this;
- }
-
- Int128 operator - (const Int128 &rhs) const
- {
- Int128 result(*this);
- result -= rhs;
- return result;
- }
-
- Int128 operator-() const //unary negation
- {
- if (lo == 0)
- return Int128(-hi, 0);
- else
- return Int128(~hi, ~lo + 1);
- }
-
- operator double() const
- {
- const double shift64 = 18446744073709551616.0; //2^64
- if (hi < 0)
- {
- if (lo == 0) return (double)hi * shift64;
- else return -(double)(~lo + ~hi * shift64);
- }
- else
- return (double)(lo + hi * shift64);
- }
-
-};
-//------------------------------------------------------------------------------
-
-Int128 Int128Mul (long64 lhs, long64 rhs)
-{
- bool negate = (lhs < 0) != (rhs < 0);
-
- if (lhs < 0) lhs = -lhs;
- ulong64 int1Hi = ulong64(lhs) >> 32;
- ulong64 int1Lo = ulong64(lhs & 0xFFFFFFFF);
-
- if (rhs < 0) rhs = -rhs;
- ulong64 int2Hi = ulong64(rhs) >> 32;
- ulong64 int2Lo = ulong64(rhs & 0xFFFFFFFF);
-
- //nb: see comments in clipper.pas
- ulong64 a = int1Hi * int2Hi;
- ulong64 b = int1Lo * int2Lo;
- ulong64 c = int1Hi * int2Lo + int1Lo * int2Hi;
-
- Int128 tmp;
- tmp.hi = long64(a + (c >> 32));
- tmp.lo = long64(c << 32);
- tmp.lo += long64(b);
- if (tmp.lo < b) tmp.hi++;
- if (negate) tmp = -tmp;
- return tmp;
-};
-#endif
-
-//------------------------------------------------------------------------------
-// Miscellaneous global functions
-//------------------------------------------------------------------------------
-
-bool Orientation(const Path &poly)
-{
- return Area(poly) >= 0;
-}
-//------------------------------------------------------------------------------
-
-double Area(const Path &poly)
-{
- int size = (int)poly.size();
- if (size < 3) return 0;
-
- double a = 0;
- for (int i = 0, j = size -1; i < size; ++i)
- {
- a += ((double)poly[j].X + poly[i].X) * ((double)poly[j].Y - poly[i].Y);
- j = i;
- }
- return -a * 0.5;
-}
-//------------------------------------------------------------------------------
-
-double Area(const OutPt *op)
-{
- const OutPt *startOp = op;
- if (!op) return 0;
- double a = 0;
- do {
- a += (double)(op->Prev->Pt.X + op->Pt.X) * (double)(op->Prev->Pt.Y - op->Pt.Y);
- op = op->Next;
- } while (op != startOp);
- return a * 0.5;
-}
-//------------------------------------------------------------------------------
-
-double Area(const OutRec &outRec)
-{
- return Area(outRec.Pts);
-}
-//------------------------------------------------------------------------------
-
-bool PointIsVertex(const IntPoint &Pt, OutPt *pp)
-{
- OutPt *pp2 = pp;
- do
- {
- if (pp2->Pt == Pt) return true;
- pp2 = pp2->Next;
- }
- while (pp2 != pp);
- return false;
-}
-//------------------------------------------------------------------------------
-
-//See "The Point in Polygon Problem for Arbitrary Polygons" by Hormann & Agathos
-//http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.88.5498&rep=rep1&type=pdf
-int PointInPolygon(const IntPoint &pt, const Path &path)
-{
- //returns 0 if false, +1 if true, -1 if pt ON polygon boundary
- int result = 0;
- size_t cnt = path.size();
- if (cnt < 3) return 0;
- IntPoint ip = path[0];
- for(size_t i = 1; i <= cnt; ++i)
- {
- IntPoint ipNext = (i == cnt ? path[0] : path[i]);
- if (ipNext.Y == pt.Y)
- {
- if ((ipNext.X == pt.X) || (ip.Y == pt.Y &&
- ((ipNext.X > pt.X) == (ip.X < pt.X)))) return -1;
- }
- if ((ip.Y < pt.Y) != (ipNext.Y < pt.Y))
- {
- if (ip.X >= pt.X)
- {
- if (ipNext.X > pt.X) result = 1 - result;
- else
- {
- double d = (double)(ip.X - pt.X) * (ipNext.Y - pt.Y) -
- (double)(ipNext.X - pt.X) * (ip.Y - pt.Y);
- if (!d) return -1;
- if ((d > 0) == (ipNext.Y > ip.Y)) result = 1 - result;
- }
- } else
- {
- if (ipNext.X > pt.X)
- {
- double d = (double)(ip.X - pt.X) * (ipNext.Y - pt.Y) -
- (double)(ipNext.X - pt.X) * (ip.Y - pt.Y);
- if (!d) return -1;
- if ((d > 0) == (ipNext.Y > ip.Y)) result = 1 - result;
- }
- }
- }
- ip = ipNext;
- }
- return result;
-}
-//------------------------------------------------------------------------------
-
-int PointInPolygon (const IntPoint &pt, OutPt *op)
-{
- //returns 0 if false, +1 if true, -1 if pt ON polygon boundary
- int result = 0;
- OutPt* startOp = op;
- for(;;)
- {
- if (op->Next->Pt.Y == pt.Y)
- {
- if ((op->Next->Pt.X == pt.X) || (op->Pt.Y == pt.Y &&
- ((op->Next->Pt.X > pt.X) == (op->Pt.X < pt.X)))) return -1;
- }
- if ((op->Pt.Y < pt.Y) != (op->Next->Pt.Y < pt.Y))
- {
- if (op->Pt.X >= pt.X)
- {
- if (op->Next->Pt.X > pt.X) result = 1 - result;
- else
- {
- double d = (double)(op->Pt.X - pt.X) * (op->Next->Pt.Y - pt.Y) -
- (double)(op->Next->Pt.X - pt.X) * (op->Pt.Y - pt.Y);
- if (!d) return -1;
- if ((d > 0) == (op->Next->Pt.Y > op->Pt.Y)) result = 1 - result;
- }
- } else
- {
- if (op->Next->Pt.X > pt.X)
- {
- double d = (double)(op->Pt.X - pt.X) * (op->Next->Pt.Y - pt.Y) -
- (double)(op->Next->Pt.X - pt.X) * (op->Pt.Y - pt.Y);
- if (!d) return -1;
- if ((d > 0) == (op->Next->Pt.Y > op->Pt.Y)) result = 1 - result;
- }
- }
- }
- op = op->Next;
- if (startOp == op) break;
- }
- return result;
-}
-//------------------------------------------------------------------------------
-
-bool Poly2ContainsPoly1(OutPt *OutPt1, OutPt *OutPt2)
-{
- OutPt* op = OutPt1;
- do
- {
- //nb: PointInPolygon returns 0 if false, +1 if true, -1 if pt on polygon
- int res = PointInPolygon(op->Pt, OutPt2);
- if (res >= 0) return res > 0;
- op = op->Next;
- }
- while (op != OutPt1);
- return true;
-}
-//----------------------------------------------------------------------
-
-bool SlopesEqual(const TEdge &e1, const TEdge &e2, bool UseFullInt64Range)
-{
-#ifndef use_int32
- if (UseFullInt64Range)
- return Int128Mul(e1.Top.Y - e1.Bot.Y, e2.Top.X - e2.Bot.X) ==
- Int128Mul(e1.Top.X - e1.Bot.X, e2.Top.Y - e2.Bot.Y);
- else
-#endif
- return (e1.Top.Y - e1.Bot.Y) * (e2.Top.X - e2.Bot.X) ==
- (e1.Top.X - e1.Bot.X) * (e2.Top.Y - e2.Bot.Y);
-}
-//------------------------------------------------------------------------------
-
-bool SlopesEqual(const IntPoint pt1, const IntPoint pt2,
- const IntPoint pt3, bool UseFullInt64Range)
-{
-#ifndef use_int32
- if (UseFullInt64Range)
- return Int128Mul(pt1.Y-pt2.Y, pt2.X-pt3.X) == Int128Mul(pt1.X-pt2.X, pt2.Y-pt3.Y);
- else
-#endif
- return (pt1.Y-pt2.Y)*(pt2.X-pt3.X) == (pt1.X-pt2.X)*(pt2.Y-pt3.Y);
-}
-//------------------------------------------------------------------------------
-
-bool SlopesEqual(const IntPoint pt1, const IntPoint pt2,
- const IntPoint pt3, const IntPoint pt4, bool UseFullInt64Range)
-{
-#ifndef use_int32
- if (UseFullInt64Range)
- return Int128Mul(pt1.Y-pt2.Y, pt3.X-pt4.X) == Int128Mul(pt1.X-pt2.X, pt3.Y-pt4.Y);
- else
-#endif
- return (pt1.Y-pt2.Y)*(pt3.X-pt4.X) == (pt1.X-pt2.X)*(pt3.Y-pt4.Y);
-}
-//------------------------------------------------------------------------------
-
-inline bool IsHorizontal(TEdge &e)
-{
- return e.Dx == HORIZONTAL;
-}
-//------------------------------------------------------------------------------
-
-inline double GetDx(const IntPoint pt1, const IntPoint pt2)
-{
- return (pt1.Y == pt2.Y) ?
- HORIZONTAL : (double)(pt2.X - pt1.X) / (pt2.Y - pt1.Y);
-}
-//---------------------------------------------------------------------------
-
-inline void SetDx(TEdge &e)
-{
- cInt dy = (e.Top.Y - e.Bot.Y);
- if (dy == 0) e.Dx = HORIZONTAL;
- else e.Dx = (double)(e.Top.X - e.Bot.X) / dy;
-}
-//---------------------------------------------------------------------------
-
-inline void SwapSides(TEdge &Edge1, TEdge &Edge2)
-{
- EdgeSide Side = Edge1.Side;
- Edge1.Side = Edge2.Side;
- Edge2.Side = Side;
-}
-//------------------------------------------------------------------------------
-
-inline void SwapPolyIndexes(TEdge &Edge1, TEdge &Edge2)
-{
- int OutIdx = Edge1.OutIdx;
- Edge1.OutIdx = Edge2.OutIdx;
- Edge2.OutIdx = OutIdx;
-}
-//------------------------------------------------------------------------------
-
-inline cInt TopX(TEdge &edge, const cInt currentY)
-{
- return ( currentY == edge.Top.Y ) ?
- edge.Top.X : edge.Bot.X + Round(edge.Dx *(currentY - edge.Bot.Y));
-}
-//------------------------------------------------------------------------------
-
-void IntersectPoint(TEdge &Edge1, TEdge &Edge2, IntPoint &ip)
-{
-#ifdef use_xyz
- ip.Z = 0;
-#endif
-
- double b1, b2;
- if (Edge1.Dx == Edge2.Dx)
- {
- ip.Y = Edge1.Curr.Y;
- ip.X = TopX(Edge1, ip.Y);
- return;
- }
- else if (Edge1.Dx == 0)
- {
- ip.X = Edge1.Bot.X;
- if (IsHorizontal(Edge2))
- ip.Y = Edge2.Bot.Y;
- else
- {
- b2 = Edge2.Bot.Y - (Edge2.Bot.X / Edge2.Dx);
- ip.Y = Round(ip.X / Edge2.Dx + b2);
- }
- }
- else if (Edge2.Dx == 0)
- {
- ip.X = Edge2.Bot.X;
- if (IsHorizontal(Edge1))
- ip.Y = Edge1.Bot.Y;
- else
- {
- b1 = Edge1.Bot.Y - (Edge1.Bot.X / Edge1.Dx);
- ip.Y = Round(ip.X / Edge1.Dx + b1);
- }
- }
- else
- {
- b1 = Edge1.Bot.X - Edge1.Bot.Y * Edge1.Dx;
- b2 = Edge2.Bot.X - Edge2.Bot.Y * Edge2.Dx;
- double q = (b2-b1) / (Edge1.Dx - Edge2.Dx);
- ip.Y = Round(q);
- if (std::fabs(Edge1.Dx) < std::fabs(Edge2.Dx))
- ip.X = Round(Edge1.Dx * q + b1);
- else
- ip.X = Round(Edge2.Dx * q + b2);
- }
-
- if (ip.Y < Edge1.Top.Y || ip.Y < Edge2.Top.Y)
- {
- if (Edge1.Top.Y > Edge2.Top.Y)
- ip.Y = Edge1.Top.Y;
- else
- ip.Y = Edge2.Top.Y;
- if (std::fabs(Edge1.Dx) < std::fabs(Edge2.Dx))
- ip.X = TopX(Edge1, ip.Y);
- else
- ip.X = TopX(Edge2, ip.Y);
- }
- //finally, don't allow 'ip' to be BELOW curr.Y (ie bottom of scanbeam) ...
- if (ip.Y > Edge1.Curr.Y)
- {
- ip.Y = Edge1.Curr.Y;
- //use the more vertical edge to derive X ...
- if (std::fabs(Edge1.Dx) > std::fabs(Edge2.Dx))
- ip.X = TopX(Edge2, ip.Y); else
- ip.X = TopX(Edge1, ip.Y);
- }
-}
-//------------------------------------------------------------------------------
-
-void ReversePolyPtLinks(OutPt *pp)
-{
- if (!pp) return;
- OutPt *pp1, *pp2;
- pp1 = pp;
- do {
- pp2 = pp1->Next;
- pp1->Next = pp1->Prev;
- pp1->Prev = pp2;
- pp1 = pp2;
- } while( pp1 != pp );
-}
-//------------------------------------------------------------------------------
-
-void DisposeOutPts(OutPt*& pp)
-{
- if (pp == 0) return;
- pp->Prev->Next = 0;
- while( pp )
- {
- OutPt *tmpPp = pp;
- pp = pp->Next;
- delete tmpPp;
- }
-}
-//------------------------------------------------------------------------------
-
-inline void InitEdge(TEdge* e, TEdge* eNext, TEdge* ePrev, const IntPoint& Pt)
-{
- std::memset(e, 0, sizeof(TEdge));
- e->Next = eNext;
- e->Prev = ePrev;
- e->Curr = Pt;
- e->OutIdx = Unassigned;
-}
-//------------------------------------------------------------------------------
-
-void InitEdge2(TEdge& e, PolyType Pt)
-{
- if (e.Curr.Y >= e.Next->Curr.Y)
- {
- e.Bot = e.Curr;
- e.Top = e.Next->Curr;
- } else
- {
- e.Top = e.Curr;
- e.Bot = e.Next->Curr;
- }
- SetDx(e);
- e.PolyTyp = Pt;
-}
-//------------------------------------------------------------------------------
-
-TEdge* RemoveEdge(TEdge* e)
-{
- //removes e from double_linked_list (but without removing from memory)
- e->Prev->Next = e->Next;
- e->Next->Prev = e->Prev;
- TEdge* result = e->Next;
- e->Prev = 0; //flag as removed (see ClipperBase.Clear)
- return result;
-}
-//------------------------------------------------------------------------------
-
-inline void ReverseHorizontal(TEdge &e)
-{
- //swap horizontal edges' Top and Bottom x's so they follow the natural
- //progression of the bounds - ie so their xbots will align with the
- //adjoining lower edge. [Helpful in the ProcessHorizontal() method.]
- std::swap(e.Top.X, e.Bot.X);
-#ifdef use_xyz
- std::swap(e.Top.Z, e.Bot.Z);
-#endif
-}
-//------------------------------------------------------------------------------
-
-void SwapPoints(IntPoint &pt1, IntPoint &pt2)
-{
- IntPoint tmp = pt1;
- pt1 = pt2;
- pt2 = tmp;
-}
-//------------------------------------------------------------------------------
-
-bool GetOverlapSegment(IntPoint pt1a, IntPoint pt1b, IntPoint pt2a,
- IntPoint pt2b, IntPoint &pt1, IntPoint &pt2)
-{
- //precondition: segments are Collinear.
- if (Abs(pt1a.X - pt1b.X) > Abs(pt1a.Y - pt1b.Y))
- {
- if (pt1a.X > pt1b.X) SwapPoints(pt1a, pt1b);
- if (pt2a.X > pt2b.X) SwapPoints(pt2a, pt2b);
- if (pt1a.X > pt2a.X) pt1 = pt1a; else pt1 = pt2a;
- if (pt1b.X < pt2b.X) pt2 = pt1b; else pt2 = pt2b;
- return pt1.X < pt2.X;
- } else
- {
- if (pt1a.Y < pt1b.Y) SwapPoints(pt1a, pt1b);
- if (pt2a.Y < pt2b.Y) SwapPoints(pt2a, pt2b);
- if (pt1a.Y < pt2a.Y) pt1 = pt1a; else pt1 = pt2a;
- if (pt1b.Y > pt2b.Y) pt2 = pt1b; else pt2 = pt2b;
- return pt1.Y > pt2.Y;
- }
-}
-//------------------------------------------------------------------------------
-
-bool FirstIsBottomPt(const OutPt* btmPt1, const OutPt* btmPt2)
-{
- OutPt *p = btmPt1->Prev;
- while ((p->Pt == btmPt1->Pt) && (p != btmPt1)) p = p->Prev;
- double dx1p = std::fabs(GetDx(btmPt1->Pt, p->Pt));
- p = btmPt1->Next;
- while ((p->Pt == btmPt1->Pt) && (p != btmPt1)) p = p->Next;
- double dx1n = std::fabs(GetDx(btmPt1->Pt, p->Pt));
-
- p = btmPt2->Prev;
- while ((p->Pt == btmPt2->Pt) && (p != btmPt2)) p = p->Prev;
- double dx2p = std::fabs(GetDx(btmPt2->Pt, p->Pt));
- p = btmPt2->Next;
- while ((p->Pt == btmPt2->Pt) && (p != btmPt2)) p = p->Next;
- double dx2n = std::fabs(GetDx(btmPt2->Pt, p->Pt));
-
- if (std::max(dx1p, dx1n) == std::max(dx2p, dx2n) &&
- std::min(dx1p, dx1n) == std::min(dx2p, dx2n))
- return Area(btmPt1) > 0; //if otherwise identical use orientation
- else
- return (dx1p >= dx2p && dx1p >= dx2n) || (dx1n >= dx2p && dx1n >= dx2n);
-}
-//------------------------------------------------------------------------------
-
-OutPt* GetBottomPt(OutPt *pp)
-{
- OutPt* dups = 0;
- OutPt* p = pp->Next;
- while (p != pp)
- {
- if (p->Pt.Y > pp->Pt.Y)
- {
- pp = p;
- dups = 0;
- }
- else if (p->Pt.Y == pp->Pt.Y && p->Pt.X <= pp->Pt.X)
- {
- if (p->Pt.X < pp->Pt.X)
- {
- dups = 0;
- pp = p;
- } else
- {
- if (p->Next != pp && p->Prev != pp) dups = p;
- }
- }
- p = p->Next;
- }
- if (dups)
- {
- //there appears to be at least 2 vertices at BottomPt so ...
- while (dups != p)
- {
- if (!FirstIsBottomPt(p, dups)) pp = dups;
- dups = dups->Next;
- while (dups->Pt != pp->Pt) dups = dups->Next;
- }
- }
- return pp;
-}
-//------------------------------------------------------------------------------
-
-bool Pt2IsBetweenPt1AndPt3(const IntPoint pt1,
- const IntPoint pt2, const IntPoint pt3)
-{
- if ((pt1 == pt3) || (pt1 == pt2) || (pt3 == pt2))
- return false;
- else if (pt1.X != pt3.X)
- return (pt2.X > pt1.X) == (pt2.X < pt3.X);
- else
- return (pt2.Y > pt1.Y) == (pt2.Y < pt3.Y);
-}
-//------------------------------------------------------------------------------
-
-bool HorzSegmentsOverlap(cInt seg1a, cInt seg1b, cInt seg2a, cInt seg2b)
-{
- if (seg1a > seg1b) std::swap(seg1a, seg1b);
- if (seg2a > seg2b) std::swap(seg2a, seg2b);
- return (seg1a < seg2b) && (seg2a < seg1b);
-}
-
-//------------------------------------------------------------------------------
-// ClipperBase class methods ...
-//------------------------------------------------------------------------------
-
-ClipperBase::ClipperBase() //constructor
-{
- m_CurrentLM = m_MinimaList.begin(); //begin() == end() here
- m_UseFullRange = false;
-}
-//------------------------------------------------------------------------------
-
-ClipperBase::~ClipperBase() //destructor
-{
- Clear();
-}
-//------------------------------------------------------------------------------
-
-void RangeTest(const IntPoint& Pt, bool& useFullRange)
-{
- if (useFullRange)
- {
- if (Pt.X > hiRange || Pt.Y > hiRange || -Pt.X > hiRange || -Pt.Y > hiRange)
- CLIPPER_THROW(clipperException("Coordinate outside allowed range"));
- }
- else if (Pt.X > loRange|| Pt.Y > loRange || -Pt.X > loRange || -Pt.Y > loRange)
- {
- useFullRange = true;
- RangeTest(Pt, useFullRange);
- }
-}
-//------------------------------------------------------------------------------
-
-TEdge* FindNextLocMin(TEdge* E)
-{
- for (;;)
- {
- while (E->Bot != E->Prev->Bot || E->Curr == E->Top) E = E->Next;
- if (!IsHorizontal(*E) && !IsHorizontal(*E->Prev)) break;
- while (IsHorizontal(*E->Prev)) E = E->Prev;
- TEdge* E2 = E;
- while (IsHorizontal(*E)) E = E->Next;
- if (E->Top.Y == E->Prev->Bot.Y) continue; //ie just an intermediate horz.
- if (E2->Prev->Bot.X < E->Bot.X) E = E2;
- break;
- }
- return E;
-}
-//------------------------------------------------------------------------------
-
-TEdge* ClipperBase::ProcessBound(TEdge* E, bool NextIsForward)
-{
- TEdge *Result = E;
- TEdge *Horz = 0;
-
- if (E->OutIdx == Skip)
- {
- //if edges still remain in the current bound beyond the skip edge then
- //create another LocMin and call ProcessBound once more
- if (NextIsForward)
- {
- while (E->Top.Y == E->Next->Bot.Y) E = E->Next;
- //don't include top horizontals when parsing a bound a second time,
- //they will be contained in the opposite bound ...
- while (E != Result && IsHorizontal(*E)) E = E->Prev;
- }
- else
- {
- while (E->Top.Y == E->Prev->Bot.Y) E = E->Prev;
- while (E != Result && IsHorizontal(*E)) E = E->Next;
- }
-
- if (E == Result)
- {
- if (NextIsForward) Result = E->Next;
- else Result = E->Prev;
- }
- else
- {
- //there are more edges in the bound beyond result starting with E
- if (NextIsForward)
- E = Result->Next;
- else
- E = Result->Prev;
- MinimaList::value_type locMin;
- locMin.Y = E->Bot.Y;
- locMin.LeftBound = 0;
- locMin.RightBound = E;
- E->WindDelta = 0;
- Result = ProcessBound(E, NextIsForward);
- m_MinimaList.push_back(locMin);
- }
- return Result;
- }
-
- TEdge *EStart;
-
- if (IsHorizontal(*E))
- {
- //We need to be careful with open paths because this may not be a
- //true local minima (ie E may be following a skip edge).
- //Also, consecutive horz. edges may start heading left before going right.
- if (NextIsForward)
- EStart = E->Prev;
- else
- EStart = E->Next;
- if (IsHorizontal(*EStart)) //ie an adjoining horizontal skip edge
- {
- if (EStart->Bot.X != E->Bot.X && EStart->Top.X != E->Bot.X)
- ReverseHorizontal(*E);
- }
- else if (EStart->Bot.X != E->Bot.X)
- ReverseHorizontal(*E);
- }
-
- EStart = E;
- if (NextIsForward)
- {
- while (Result->Top.Y == Result->Next->Bot.Y && Result->Next->OutIdx != Skip)
- Result = Result->Next;
- if (IsHorizontal(*Result) && Result->Next->OutIdx != Skip)
- {
- //nb: at the top of a bound, horizontals are added to the bound
- //only when the preceding edge attaches to the horizontal's left vertex
- //unless a Skip edge is encountered when that becomes the top divide
- Horz = Result;
- while (IsHorizontal(*Horz->Prev)) Horz = Horz->Prev;
- if (Horz->Prev->Top.X > Result->Next->Top.X) Result = Horz->Prev;
- }
- while (E != Result)
- {
- E->NextInLML = E->Next;
- if (IsHorizontal(*E) && E != EStart &&
- E->Bot.X != E->Prev->Top.X) ReverseHorizontal(*E);
- E = E->Next;
- }
- if (IsHorizontal(*E) && E != EStart && E->Bot.X != E->Prev->Top.X)
- ReverseHorizontal(*E);
- Result = Result->Next; //move to the edge just beyond current bound
- } else
- {
- while (Result->Top.Y == Result->Prev->Bot.Y && Result->Prev->OutIdx != Skip)
- Result = Result->Prev;
- if (IsHorizontal(*Result) && Result->Prev->OutIdx != Skip)
- {
- Horz = Result;
- while (IsHorizontal(*Horz->Next)) Horz = Horz->Next;
- if (Horz->Next->Top.X == Result->Prev->Top.X ||
- Horz->Next->Top.X > Result->Prev->Top.X) Result = Horz->Next;
- }
-
- while (E != Result)
- {
- E->NextInLML = E->Prev;
- if (IsHorizontal(*E) && E != EStart && E->Bot.X != E->Next->Top.X)
- ReverseHorizontal(*E);
- E = E->Prev;
- }
- if (IsHorizontal(*E) && E != EStart && E->Bot.X != E->Next->Top.X)
- ReverseHorizontal(*E);
- Result = Result->Prev; //move to the edge just beyond current bound
- }
-
- return Result;
-}
-//------------------------------------------------------------------------------
-
-bool ClipperBase::AddPath(const Path &pg, PolyType PolyTyp, bool Closed)
-{
-#ifdef use_lines
- if (!Closed && PolyTyp == ptClip)
- CLIPPER_THROW(clipperException("AddPath: Open paths must be subject."));
-#else
- if (!Closed)
- CLIPPER_THROW(clipperException("AddPath: Open paths have been disabled."));
-#endif
-
- int highI = (int)pg.size() -1;
- if (Closed) while (highI > 0 && (pg[highI] == pg[0])) --highI;
- while (highI > 0 && (pg[highI] == pg[highI -1])) --highI;
- if ((Closed && highI < 2) || (!Closed && highI < 1)) return false;
-
- //create a new edge array ...
- TEdge *edges = new TEdge [highI +1];
-
- bool IsFlat = true;
- //1. Basic (first) edge initialization ...
- CLIPPER_TRY
- {
- edges[1].Curr = pg[1];
- RangeTest(pg[0], m_UseFullRange);
- RangeTest(pg[highI], m_UseFullRange);
- InitEdge(&edges[0], &edges[1], &edges[highI], pg[0]);
- InitEdge(&edges[highI], &edges[0], &edges[highI-1], pg[highI]);
- for (int i = highI - 1; i >= 1; --i)
- {
- RangeTest(pg[i], m_UseFullRange);
- InitEdge(&edges[i], &edges[i+1], &edges[i-1], pg[i]);
- }
- }
- CLIPPER_CATCH(...)
- {
- delete [] edges;
- CLIPPER_THROW(); //range test fails
- }
- TEdge *eStart = &edges[0];
-
- //2. Remove duplicate vertices, and (when closed) collinear edges ...
- TEdge *E = eStart, *eLoopStop = eStart;
- for (;;)
- {
- //nb: allows matching start and end points when not Closed ...
- if (E->Curr == E->Next->Curr && (Closed || E->Next != eStart))
- {
- if (E == E->Next) break;
- if (E == eStart) eStart = E->Next;
- E = RemoveEdge(E);
- eLoopStop = E;
- continue;
- }
- if (E->Prev == E->Next)
- break; //only two vertices
- else if (Closed &&
- SlopesEqual(E->Prev->Curr, E->Curr, E->Next->Curr, m_UseFullRange) &&
- (!m_PreserveCollinear ||
- !Pt2IsBetweenPt1AndPt3(E->Prev->Curr, E->Curr, E->Next->Curr)))
- {
- //Collinear edges are allowed for open paths but in closed paths
- //the default is to merge adjacent collinear edges into a single edge.
- //However, if the PreserveCollinear property is enabled, only overlapping
- //collinear edges (ie spikes) will be removed from closed paths.
- if (E == eStart) eStart = E->Next;
- E = RemoveEdge(E);
- E = E->Prev;
- eLoopStop = E;
- continue;
- }
- E = E->Next;
- if ((E == eLoopStop) || (!Closed && E->Next == eStart)) break;
- }
-
- if ((!Closed && (E == E->Next)) || (Closed && (E->Prev == E->Next)))
- {
- delete [] edges;
- return false;
- }
-
- if (!Closed)
- {
- m_HasOpenPaths = true;
- eStart->Prev->OutIdx = Skip;
- }
-
- //3. Do second stage of edge initialization ...
- E = eStart;
- do
- {
- InitEdge2(*E, PolyTyp);
- E = E->Next;
- if (IsFlat && E->Curr.Y != eStart->Curr.Y) IsFlat = false;
- }
- while (E != eStart);
-
- //4. Finally, add edge bounds to LocalMinima list ...
-
- //Totally flat paths must be handled differently when adding them
- //to LocalMinima list to avoid endless loops etc ...
- if (IsFlat)
- {
- if (Closed)
- {
- delete [] edges;
- return false;
- }
- E->Prev->OutIdx = Skip;
- MinimaList::value_type locMin;
- locMin.Y = E->Bot.Y;
- locMin.LeftBound = 0;
- locMin.RightBound = E;
- locMin.RightBound->Side = esRight;
- locMin.RightBound->WindDelta = 0;
- for (;;)
- {
- if (E->Bot.X != E->Prev->Top.X) ReverseHorizontal(*E);
- if (E->Next->OutIdx == Skip) break;
- E->NextInLML = E->Next;
- E = E->Next;
- }
- m_MinimaList.push_back(locMin);
- m_edges.push_back(edges);
- return true;
- }
-
- m_edges.push_back(edges);
- bool leftBoundIsForward;
- TEdge* EMin = 0;
-
- //workaround to avoid an endless loop in the while loop below when
- //open paths have matching start and end points ...
- if (E->Prev->Bot == E->Prev->Top) E = E->Next;
-
- for (;;)
- {
- E = FindNextLocMin(E);
- if (E == EMin) break;
- else if (!EMin) EMin = E;
-
- //E and E.Prev now share a local minima (left aligned if horizontal).
- //Compare their slopes to find which starts which bound ...
- MinimaList::value_type locMin;
- locMin.Y = E->Bot.Y;
- if (E->Dx < E->Prev->Dx)
- {
- locMin.LeftBound = E->Prev;
- locMin.RightBound = E;
- leftBoundIsForward = false; //Q.nextInLML = Q.prev
- } else
- {
- locMin.LeftBound = E;
- locMin.RightBound = E->Prev;
- leftBoundIsForward = true; //Q.nextInLML = Q.next
- }
-
- if (!Closed) locMin.LeftBound->WindDelta = 0;
- else if (locMin.LeftBound->Next == locMin.RightBound)
- locMin.LeftBound->WindDelta = -1;
- else locMin.LeftBound->WindDelta = 1;
- locMin.RightBound->WindDelta = -locMin.LeftBound->WindDelta;
-
- E = ProcessBound(locMin.LeftBound, leftBoundIsForward);
- if (E->OutIdx == Skip) E = ProcessBound(E, leftBoundIsForward);
-
- TEdge* E2 = ProcessBound(locMin.RightBound, !leftBoundIsForward);
- if (E2->OutIdx == Skip) E2 = ProcessBound(E2, !leftBoundIsForward);
-
- if (locMin.LeftBound->OutIdx == Skip)
- locMin.LeftBound = 0;
- else if (locMin.RightBound->OutIdx == Skip)
- locMin.RightBound = 0;
- m_MinimaList.push_back(locMin);
- if (!leftBoundIsForward) E = E2;
- }
- return true;
-}
-//------------------------------------------------------------------------------
-
-bool ClipperBase::AddPaths(const Paths &ppg, PolyType PolyTyp, bool Closed)
-{
- bool result = false;
- for (Paths::size_type i = 0; i < ppg.size(); ++i)
- if (AddPath(ppg[i], PolyTyp, Closed)) result = true;
- return result;
-}
-//------------------------------------------------------------------------------
-
-void ClipperBase::Clear()
-{
- DisposeLocalMinimaList();
- for (EdgeList::size_type i = 0; i < m_edges.size(); ++i)
- {
- TEdge* edges = m_edges[i];
- delete [] edges;
- }
- m_edges.clear();
- m_UseFullRange = false;
- m_HasOpenPaths = false;
-}
-//------------------------------------------------------------------------------
-
-void ClipperBase::Reset()
-{
- m_CurrentLM = m_MinimaList.begin();
- if (m_CurrentLM == m_MinimaList.end()) return; //ie nothing to process
- std::sort(m_MinimaList.begin(), m_MinimaList.end(), LocMinSorter());
-
- m_Scanbeam = ScanbeamList(); //clears/resets priority_queue
- //reset all edges ...
- for (MinimaList::iterator lm = m_MinimaList.begin(); lm != m_MinimaList.end(); ++lm)
- {
- InsertScanbeam(lm->Y);
- TEdge* e = lm->LeftBound;
- if (e)
- {
- e->Curr = e->Bot;
- e->Side = esLeft;
- e->OutIdx = Unassigned;
- }
-
- e = lm->RightBound;
- if (e)
- {
- e->Curr = e->Bot;
- e->Side = esRight;
- e->OutIdx = Unassigned;
- }
- }
- m_ActiveEdges = 0;
- m_CurrentLM = m_MinimaList.begin();
-}
-//------------------------------------------------------------------------------
-
-void ClipperBase::DisposeLocalMinimaList()
-{
- m_MinimaList.clear();
- m_CurrentLM = m_MinimaList.begin();
-}
-//------------------------------------------------------------------------------
-
-bool ClipperBase::PopLocalMinima(cInt Y, const LocalMinimum *&locMin)
-{
- if (m_CurrentLM == m_MinimaList.end() || (*m_CurrentLM).Y != Y) return false;
- locMin = &(*m_CurrentLM);
- ++m_CurrentLM;
- return true;
-}
-//------------------------------------------------------------------------------
-
-IntRect ClipperBase::GetBounds()
-{
- IntRect result;
- MinimaList::iterator lm = m_MinimaList.begin();
- if (lm == m_MinimaList.end())
- {
- result.left = result.top = result.right = result.bottom = 0;
- return result;
- }
- result.left = lm->LeftBound->Bot.X;
- result.top = lm->LeftBound->Bot.Y;
- result.right = lm->LeftBound->Bot.X;
- result.bottom = lm->LeftBound->Bot.Y;
- while (lm != m_MinimaList.end())
- {
- //todo - needs fixing for open paths
- result.bottom = std::max(result.bottom, lm->LeftBound->Bot.Y);
- TEdge* e = lm->LeftBound;
- for (;;) {
- TEdge* bottomE = e;
- while (e->NextInLML)
- {
- if (e->Bot.X < result.left) result.left = e->Bot.X;
- if (e->Bot.X > result.right) result.right = e->Bot.X;
- e = e->NextInLML;
- }
- result.left = std::min(result.left, e->Bot.X);
- result.right = std::max(result.right, e->Bot.X);
- result.left = std::min(result.left, e->Top.X);
- result.right = std::max(result.right, e->Top.X);
- result.top = std::min(result.top, e->Top.Y);
- if (bottomE == lm->LeftBound) e = lm->RightBound;
- else break;
- }
- ++lm;
- }
- return result;
-}
-//------------------------------------------------------------------------------
-
-void ClipperBase::InsertScanbeam(const cInt Y)
-{
- m_Scanbeam.push(Y);
-}
-//------------------------------------------------------------------------------
-
-bool ClipperBase::PopScanbeam(cInt &Y)
-{
- if (m_Scanbeam.empty()) return false;
- Y = m_Scanbeam.top();
- m_Scanbeam.pop();
- while (!m_Scanbeam.empty() && Y == m_Scanbeam.top()) { m_Scanbeam.pop(); } // Pop duplicates.
- return true;
-}
-//------------------------------------------------------------------------------
-
-void ClipperBase::DisposeAllOutRecs(){
- for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i)
- DisposeOutRec(i);
- m_PolyOuts.clear();
-}
-//------------------------------------------------------------------------------
-
-void ClipperBase::DisposeOutRec(PolyOutList::size_type index)
-{
- OutRec *outRec = m_PolyOuts[index];
- if (outRec->Pts) DisposeOutPts(outRec->Pts);
- delete outRec;
- m_PolyOuts[index] = 0;
-}
-//------------------------------------------------------------------------------
-
-void ClipperBase::DeleteFromAEL(TEdge *e)
-{
- TEdge* AelPrev = e->PrevInAEL;
- TEdge* AelNext = e->NextInAEL;
- if (!AelPrev && !AelNext && (e != m_ActiveEdges)) return; //already deleted
- if (AelPrev) AelPrev->NextInAEL = AelNext;
- else m_ActiveEdges = AelNext;
- if (AelNext) AelNext->PrevInAEL = AelPrev;
- e->NextInAEL = 0;
- e->PrevInAEL = 0;
-}
-//------------------------------------------------------------------------------
-
-OutRec* ClipperBase::CreateOutRec()
-{
- OutRec* result = new OutRec;
- result->IsHole = false;
- result->IsOpen = false;
- result->FirstLeft = 0;
- result->Pts = 0;
- result->BottomPt = 0;
- result->PolyNd = 0;
- m_PolyOuts.push_back(result);
- result->Idx = (int)m_PolyOuts.size() - 1;
- return result;
-}
-//------------------------------------------------------------------------------
-
-void ClipperBase::SwapPositionsInAEL(TEdge *Edge1, TEdge *Edge2)
-{
- //check that one or other edge hasn't already been removed from AEL ...
- if (Edge1->NextInAEL == Edge1->PrevInAEL ||
- Edge2->NextInAEL == Edge2->PrevInAEL) return;
-
- if (Edge1->NextInAEL == Edge2)
- {
- TEdge* Next = Edge2->NextInAEL;
- if (Next) Next->PrevInAEL = Edge1;
- TEdge* Prev = Edge1->PrevInAEL;
- if (Prev) Prev->NextInAEL = Edge2;
- Edge2->PrevInAEL = Prev;
- Edge2->NextInAEL = Edge1;
- Edge1->PrevInAEL = Edge2;
- Edge1->NextInAEL = Next;
- }
- else if (Edge2->NextInAEL == Edge1)
- {
- TEdge* Next = Edge1->NextInAEL;
- if (Next) Next->PrevInAEL = Edge2;
- TEdge* Prev = Edge2->PrevInAEL;
- if (Prev) Prev->NextInAEL = Edge1;
- Edge1->PrevInAEL = Prev;
- Edge1->NextInAEL = Edge2;
- Edge2->PrevInAEL = Edge1;
- Edge2->NextInAEL = Next;
- }
- else
- {
- TEdge* Next = Edge1->NextInAEL;
- TEdge* Prev = Edge1->PrevInAEL;
- Edge1->NextInAEL = Edge2->NextInAEL;
- if (Edge1->NextInAEL) Edge1->NextInAEL->PrevInAEL = Edge1;
- Edge1->PrevInAEL = Edge2->PrevInAEL;
- if (Edge1->PrevInAEL) Edge1->PrevInAEL->NextInAEL = Edge1;
- Edge2->NextInAEL = Next;
- if (Edge2->NextInAEL) Edge2->NextInAEL->PrevInAEL = Edge2;
- Edge2->PrevInAEL = Prev;
- if (Edge2->PrevInAEL) Edge2->PrevInAEL->NextInAEL = Edge2;
- }
-
- if (!Edge1->PrevInAEL) m_ActiveEdges = Edge1;
- else if (!Edge2->PrevInAEL) m_ActiveEdges = Edge2;
-}
-//------------------------------------------------------------------------------
-
-void ClipperBase::UpdateEdgeIntoAEL(TEdge *&e)
-{
- if (!e->NextInLML)
- CLIPPER_THROW(clipperException("UpdateEdgeIntoAEL: invalid call"));
-
- e->NextInLML->OutIdx = e->OutIdx;
- TEdge* AelPrev = e->PrevInAEL;
- TEdge* AelNext = e->NextInAEL;
- if (AelPrev) AelPrev->NextInAEL = e->NextInLML;
- else m_ActiveEdges = e->NextInLML;
- if (AelNext) AelNext->PrevInAEL = e->NextInLML;
- e->NextInLML->Side = e->Side;
- e->NextInLML->WindDelta = e->WindDelta;
- e->NextInLML->WindCnt = e->WindCnt;
- e->NextInLML->WindCnt2 = e->WindCnt2;
- e = e->NextInLML;
- e->Curr = e->Bot;
- e->PrevInAEL = AelPrev;
- e->NextInAEL = AelNext;
- if (!IsHorizontal(*e)) InsertScanbeam(e->Top.Y);
-}
-//------------------------------------------------------------------------------
-
-bool ClipperBase::LocalMinimaPending()
-{
- return (m_CurrentLM != m_MinimaList.end());
-}
-
-//------------------------------------------------------------------------------
-// TClipper methods ...
-//------------------------------------------------------------------------------
-
-Clipper::Clipper(int initOptions) : ClipperBase() //constructor
-{
- m_ExecuteLocked = false;
- m_UseFullRange = false;
- m_ReverseOutput = ((initOptions & ioReverseSolution) != 0);
- m_StrictSimple = ((initOptions & ioStrictlySimple) != 0);
- m_PreserveCollinear = ((initOptions & ioPreserveCollinear) != 0);
- m_HasOpenPaths = false;
-#ifdef use_xyz
- m_ZFill = 0;
-#endif
-}
-//------------------------------------------------------------------------------
-
-#ifdef use_xyz
-void Clipper::ZFillFunction(ZFillCallback zFillFunc)
-{
- m_ZFill = zFillFunc;
-}
-//------------------------------------------------------------------------------
-#endif
-
-bool Clipper::Execute(ClipType clipType, Paths &solution, PolyFillType fillType)
-{
- return Execute(clipType, solution, fillType, fillType);
-}
-//------------------------------------------------------------------------------
-
-bool Clipper::Execute(ClipType clipType, PolyTree &polytree, PolyFillType fillType)
-{
- return Execute(clipType, polytree, fillType, fillType);
-}
-//------------------------------------------------------------------------------
-
-bool Clipper::Execute(ClipType clipType, Paths &solution,
- PolyFillType subjFillType, PolyFillType clipFillType)
-{
- if( m_ExecuteLocked ) return false;
- if (m_HasOpenPaths)
- CLIPPER_THROW(clipperException("Error: PolyTree struct is needed for open path clipping."));
- m_ExecuteLocked = true;
- solution.resize(0);
- m_SubjFillType = subjFillType;
- m_ClipFillType = clipFillType;
- m_ClipType = clipType;
- m_UsingPolyTree = false;
- bool succeeded = ExecuteInternal();
- if (succeeded) BuildResult(solution);
- DisposeAllOutRecs();
- m_ExecuteLocked = false;
- return succeeded;
-}
-//------------------------------------------------------------------------------
-
-bool Clipper::Execute(ClipType clipType, PolyTree& polytree,
- PolyFillType subjFillType, PolyFillType clipFillType)
-{
- if( m_ExecuteLocked ) return false;
- m_ExecuteLocked = true;
- m_SubjFillType = subjFillType;
- m_ClipFillType = clipFillType;
- m_ClipType = clipType;
- m_UsingPolyTree = true;
- bool succeeded = ExecuteInternal();
- if (succeeded) BuildResult2(polytree);
- DisposeAllOutRecs();
- m_ExecuteLocked = false;
- return succeeded;
-}
-//------------------------------------------------------------------------------
-
-void Clipper::FixHoleLinkage(OutRec &outrec)
-{
- //skip OutRecs that (a) contain outermost polygons or
- //(b) already have the correct owner/child linkage ...
- if (!outrec.FirstLeft ||
- (outrec.IsHole != outrec.FirstLeft->IsHole &&
- outrec.FirstLeft->Pts)) return;
-
- OutRec* orfl = outrec.FirstLeft;
- while (orfl && ((orfl->IsHole == outrec.IsHole) || !orfl->Pts))
- orfl = orfl->FirstLeft;
- outrec.FirstLeft = orfl;
-}
-//------------------------------------------------------------------------------
-
-bool Clipper::ExecuteInternal()
-{
- bool succeeded = true;
- CLIPPER_TRY {
- Reset();
- m_Maxima = MaximaList();
- m_SortedEdges = 0;
-
- succeeded = true;
- cInt botY, topY;
- if (!PopScanbeam(botY)) return false;
- InsertLocalMinimaIntoAEL(botY);
- while (PopScanbeam(topY) || LocalMinimaPending())
- {
- ProcessHorizontals();
- ClearGhostJoins();
- if (!ProcessIntersections(topY))
- {
- succeeded = false;
- break;
- }
- ProcessEdgesAtTopOfScanbeam(topY);
- botY = topY;
- InsertLocalMinimaIntoAEL(botY);
- }
- }
- CLIPPER_CATCH(...)
- {
- succeeded = false;
- }
-
- if (succeeded)
- {
- //fix orientations ...
- for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i)
- {
- OutRec *outRec = m_PolyOuts[i];
- if (!outRec->Pts || outRec->IsOpen) continue;
- if ((outRec->IsHole ^ m_ReverseOutput) == (Area(*outRec) > 0))
- ReversePolyPtLinks(outRec->Pts);
- }
-
- if (!m_Joins.empty()) JoinCommonEdges();
-
- //unfortunately FixupOutPolygon() must be done after JoinCommonEdges()
- for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i)
- {
- OutRec *outRec = m_PolyOuts[i];
- if (!outRec->Pts) continue;
- if (outRec->IsOpen)
- FixupOutPolyline(*outRec);
- else
- FixupOutPolygon(*outRec);
- }
-
- if (m_StrictSimple) DoSimplePolygons();
- }
-
- ClearJoins();
- ClearGhostJoins();
- return succeeded;
-}
-//------------------------------------------------------------------------------
-
-void Clipper::SetWindingCount(TEdge &edge)
-{
- TEdge *e = edge.PrevInAEL;
- //find the edge of the same polytype that immediately preceeds 'edge' in AEL
- while (e && ((e->PolyTyp != edge.PolyTyp) || (e->WindDelta == 0))) e = e->PrevInAEL;
- if (!e)
- {
- if (edge.WindDelta == 0)
- {
- PolyFillType pft = (edge.PolyTyp == ptSubject ? m_SubjFillType : m_ClipFillType);
- edge.WindCnt = (pft == pftNegative ? -1 : 1);
- }
- else
- edge.WindCnt = edge.WindDelta;
- edge.WindCnt2 = 0;
- e = m_ActiveEdges; //ie get ready to calc WindCnt2
- }
- else if (edge.WindDelta == 0 && m_ClipType != ctUnion)
- {
- edge.WindCnt = 1;
- edge.WindCnt2 = e->WindCnt2;
- e = e->NextInAEL; //ie get ready to calc WindCnt2
- }
- else if (IsEvenOddFillType(edge))
- {
- //EvenOdd filling ...
- if (edge.WindDelta == 0)
- {
- //are we inside a subj polygon ...
- bool Inside = true;
- TEdge *e2 = e->PrevInAEL;
- while (e2)
- {
- if (e2->PolyTyp == e->PolyTyp && e2->WindDelta != 0)
- Inside = !Inside;
- e2 = e2->PrevInAEL;
- }
- edge.WindCnt = (Inside ? 0 : 1);
- }
- else
- {
- edge.WindCnt = edge.WindDelta;
- }
- edge.WindCnt2 = e->WindCnt2;
- e = e->NextInAEL; //ie get ready to calc WindCnt2
- }
- else
- {
- //nonZero, Positive or Negative filling ...
- if (e->WindCnt * e->WindDelta < 0)
- {
- //prev edge is 'decreasing' WindCount (WC) toward zero
- //so we're outside the previous polygon ...
- if (Abs(e->WindCnt) > 1)
- {
- //outside prev poly but still inside another.
- //when reversing direction of prev poly use the same WC
- if (e->WindDelta * edge.WindDelta < 0) edge.WindCnt = e->WindCnt;
- //otherwise continue to 'decrease' WC ...
- else edge.WindCnt = e->WindCnt + edge.WindDelta;
- }
- else
- //now outside all polys of same polytype so set own WC ...
- edge.WindCnt = (edge.WindDelta == 0 ? 1 : edge.WindDelta);
- } else
- {
- //prev edge is 'increasing' WindCount (WC) away from zero
- //so we're inside the previous polygon ...
- if (edge.WindDelta == 0)
- edge.WindCnt = (e->WindCnt < 0 ? e->WindCnt - 1 : e->WindCnt + 1);
- //if wind direction is reversing prev then use same WC
- else if (e->WindDelta * edge.WindDelta < 0) edge.WindCnt = e->WindCnt;
- //otherwise add to WC ...
- else edge.WindCnt = e->WindCnt + edge.WindDelta;
- }
- edge.WindCnt2 = e->WindCnt2;
- e = e->NextInAEL; //ie get ready to calc WindCnt2
- }
-
- //update WindCnt2 ...
- if (IsEvenOddAltFillType(edge))
- {
- //EvenOdd filling ...
- while (e != &edge)
- {
- if (e->WindDelta != 0)
- edge.WindCnt2 = (edge.WindCnt2 == 0 ? 1 : 0);
- e = e->NextInAEL;
- }
- } else
- {
- //nonZero, Positive or Negative filling ...
- while ( e != &edge )
- {
- edge.WindCnt2 += e->WindDelta;
- e = e->NextInAEL;
- }
- }
-}
-//------------------------------------------------------------------------------
-
-bool Clipper::IsEvenOddFillType(const TEdge& edge) const
-{
- if (edge.PolyTyp == ptSubject)
- return m_SubjFillType == pftEvenOdd; else
- return m_ClipFillType == pftEvenOdd;
-}
-//------------------------------------------------------------------------------
-
-bool Clipper::IsEvenOddAltFillType(const TEdge& edge) const
-{
- if (edge.PolyTyp == ptSubject)
- return m_ClipFillType == pftEvenOdd; else
- return m_SubjFillType == pftEvenOdd;
-}
-//------------------------------------------------------------------------------
-
-bool Clipper::IsContributing(const TEdge& edge) const
-{
- PolyFillType pft, pft2;
- if (edge.PolyTyp == ptSubject)
- {
- pft = m_SubjFillType;
- pft2 = m_ClipFillType;
- } else
- {
- pft = m_ClipFillType;
- pft2 = m_SubjFillType;
- }
-
- switch(pft)
- {
- case pftEvenOdd:
- //return false if a subj line has been flagged as inside a subj polygon
- if (edge.WindDelta == 0 && edge.WindCnt != 1) return false;
- break;
- case pftNonZero:
- if (Abs(edge.WindCnt) != 1) return false;
- break;
- case pftPositive:
- if (edge.WindCnt != 1) return false;
- break;
- default: //pftNegative
- if (edge.WindCnt != -1) return false;
- }
-
- switch(m_ClipType)
- {
- case ctIntersection:
- switch(pft2)
- {
- case pftEvenOdd:
- case pftNonZero:
- return (edge.WindCnt2 != 0);
- case pftPositive:
- return (edge.WindCnt2 > 0);
- default:
- return (edge.WindCnt2 < 0);
- }
- break;
- case ctUnion:
- switch(pft2)
- {
- case pftEvenOdd:
- case pftNonZero:
- return (edge.WindCnt2 == 0);
- case pftPositive:
- return (edge.WindCnt2 <= 0);
- default:
- return (edge.WindCnt2 >= 0);
- }
- break;
- case ctDifference:
- if (edge.PolyTyp == ptSubject)
- switch(pft2)
- {
- case pftEvenOdd:
- case pftNonZero:
- return (edge.WindCnt2 == 0);
- case pftPositive:
- return (edge.WindCnt2 <= 0);
- default:
- return (edge.WindCnt2 >= 0);
- }
- else
- switch(pft2)
- {
- case pftEvenOdd:
- case pftNonZero:
- return (edge.WindCnt2 != 0);
- case pftPositive:
- return (edge.WindCnt2 > 0);
- default:
- return (edge.WindCnt2 < 0);
- }
- break;
- case ctXor:
- if (edge.WindDelta == 0) //XOr always contributing unless open
- switch(pft2)
- {
- case pftEvenOdd:
- case pftNonZero:
- return (edge.WindCnt2 == 0);
- case pftPositive:
- return (edge.WindCnt2 <= 0);
- default:
- return (edge.WindCnt2 >= 0);
- }
- else
- return true;
- break;
- default:
- return true;
- }
-}
-//------------------------------------------------------------------------------
-
-OutPt* Clipper::AddLocalMinPoly(TEdge *e1, TEdge *e2, const IntPoint &Pt)
-{
- OutPt* result;
- TEdge *e, *prevE;
- if (IsHorizontal(*e2) || ( e1->Dx > e2->Dx ))
- {
- result = AddOutPt(e1, Pt);
- e2->OutIdx = e1->OutIdx;
- e1->Side = esLeft;
- e2->Side = esRight;
- e = e1;
- if (e->PrevInAEL == e2)
- prevE = e2->PrevInAEL;
- else
- prevE = e->PrevInAEL;
- } else
- {
- result = AddOutPt(e2, Pt);
- e1->OutIdx = e2->OutIdx;
- e1->Side = esRight;
- e2->Side = esLeft;
- e = e2;
- if (e->PrevInAEL == e1)
- prevE = e1->PrevInAEL;
- else
- prevE = e->PrevInAEL;
- }
-
- if (prevE && prevE->OutIdx >= 0 && prevE->Top.Y < Pt.Y && e->Top.Y < Pt.Y)
- {
- cInt xPrev = TopX(*prevE, Pt.Y);
- cInt xE = TopX(*e, Pt.Y);
- if (xPrev == xE && (e->WindDelta != 0) && (prevE->WindDelta != 0) &&
- SlopesEqual(IntPoint(xPrev, Pt.Y), prevE->Top, IntPoint(xE, Pt.Y), e->Top, m_UseFullRange))
- {
- OutPt* outPt = AddOutPt(prevE, Pt);
- AddJoin(result, outPt, e->Top);
- }
- }
- return result;
-}
-//------------------------------------------------------------------------------
-
-void Clipper::AddLocalMaxPoly(TEdge *e1, TEdge *e2, const IntPoint &Pt)
-{
- AddOutPt( e1, Pt );
- if (e2->WindDelta == 0) AddOutPt(e2, Pt);
- if( e1->OutIdx == e2->OutIdx )
- {
- e1->OutIdx = Unassigned;
- e2->OutIdx = Unassigned;
- }
- else if (e1->OutIdx < e2->OutIdx)
- AppendPolygon(e1, e2);
- else
- AppendPolygon(e2, e1);
-}
-//------------------------------------------------------------------------------
-
-void Clipper::AddEdgeToSEL(TEdge *edge)
-{
- //SEL pointers in PEdge are reused to build a list of horizontal edges.
- //However, we don't need to worry about order with horizontal edge processing.
- if( !m_SortedEdges )
- {
- m_SortedEdges = edge;
- edge->PrevInSEL = 0;
- edge->NextInSEL = 0;
- }
- else
- {
- edge->NextInSEL = m_SortedEdges;
- edge->PrevInSEL = 0;
- m_SortedEdges->PrevInSEL = edge;
- m_SortedEdges = edge;
- }
-}
-//------------------------------------------------------------------------------
-
-bool Clipper::PopEdgeFromSEL(TEdge *&edge)
-{
- if (!m_SortedEdges) return false;
- edge = m_SortedEdges;
- DeleteFromSEL(m_SortedEdges);
- return true;
-}
-//------------------------------------------------------------------------------
-
-void Clipper::CopyAELToSEL()
-{
- TEdge* e = m_ActiveEdges;
- m_SortedEdges = e;
- while ( e )
- {
- e->PrevInSEL = e->PrevInAEL;
- e->NextInSEL = e->NextInAEL;
- e = e->NextInAEL;
- }
-}
-//------------------------------------------------------------------------------
-
-void Clipper::AddJoin(OutPt *op1, OutPt *op2, const IntPoint OffPt)
-{
- Join* j = new Join;
- j->OutPt1 = op1;
- j->OutPt2 = op2;
- j->OffPt = OffPt;
- m_Joins.push_back(j);
-}
-//------------------------------------------------------------------------------
-
-void Clipper::ClearJoins()
-{
- for (JoinList::size_type i = 0; i < m_Joins.size(); i++)
- delete m_Joins[i];
- m_Joins.resize(0);
-}
-//------------------------------------------------------------------------------
-
-void Clipper::ClearGhostJoins()
-{
- for (JoinList::size_type i = 0; i < m_GhostJoins.size(); i++)
- delete m_GhostJoins[i];
- m_GhostJoins.resize(0);
-}
-//------------------------------------------------------------------------------
-
-void Clipper::AddGhostJoin(OutPt *op, const IntPoint OffPt)
-{
- Join* j = new Join;
- j->OutPt1 = op;
- j->OutPt2 = 0;
- j->OffPt = OffPt;
- m_GhostJoins.push_back(j);
-}
-//------------------------------------------------------------------------------
-
-void Clipper::InsertLocalMinimaIntoAEL(const cInt botY)
-{
- const LocalMinimum *lm;
- while (PopLocalMinima(botY, lm))
- {
- TEdge* lb = lm->LeftBound;
- TEdge* rb = lm->RightBound;
-
- OutPt *Op1 = 0;
- if (!lb)
- {
- //nb: don't insert LB into either AEL or SEL
- InsertEdgeIntoAEL(rb, 0);
- SetWindingCount(*rb);
- if (IsContributing(*rb))
- Op1 = AddOutPt(rb, rb->Bot);
- }
- else if (!rb)
- {
- InsertEdgeIntoAEL(lb, 0);
- SetWindingCount(*lb);
- if (IsContributing(*lb))
- Op1 = AddOutPt(lb, lb->Bot);
- InsertScanbeam(lb->Top.Y);
- }
- else
- {
- InsertEdgeIntoAEL(lb, 0);
- InsertEdgeIntoAEL(rb, lb);
- SetWindingCount( *lb );
- rb->WindCnt = lb->WindCnt;
- rb->WindCnt2 = lb->WindCnt2;
- if (IsContributing(*lb))
- Op1 = AddLocalMinPoly(lb, rb, lb->Bot);
- InsertScanbeam(lb->Top.Y);
- }
-
- if (rb)
- {
- if (IsHorizontal(*rb))
- {
- AddEdgeToSEL(rb);
- if (rb->NextInLML)
- InsertScanbeam(rb->NextInLML->Top.Y);
- }
- else InsertScanbeam( rb->Top.Y );
- }
-
- if (!lb || !rb) continue;
-
- //if any output polygons share an edge, they'll need joining later ...
- if (Op1 && IsHorizontal(*rb) &&
- m_GhostJoins.size() > 0 && (rb->WindDelta != 0))
- {
- for (JoinList::size_type i = 0; i < m_GhostJoins.size(); ++i)
- {
- Join* jr = m_GhostJoins[i];
- //if the horizontal Rb and a 'ghost' horizontal overlap, then convert
- //the 'ghost' join to a real join ready for later ...
- if (HorzSegmentsOverlap(jr->OutPt1->Pt.X, jr->OffPt.X, rb->Bot.X, rb->Top.X))
- AddJoin(jr->OutPt1, Op1, jr->OffPt);
- }
- }
-
- if (lb->OutIdx >= 0 && lb->PrevInAEL &&
- lb->PrevInAEL->Curr.X == lb->Bot.X &&
- lb->PrevInAEL->OutIdx >= 0 &&
- SlopesEqual(lb->PrevInAEL->Bot, lb->PrevInAEL->Top, lb->Curr, lb->Top, m_UseFullRange) &&
- (lb->WindDelta != 0) && (lb->PrevInAEL->WindDelta != 0))
- {
- OutPt *Op2 = AddOutPt(lb->PrevInAEL, lb->Bot);
- AddJoin(Op1, Op2, lb->Top);
- }
-
- if(lb->NextInAEL != rb)
- {
-
- if (rb->OutIdx >= 0 && rb->PrevInAEL->OutIdx >= 0 &&
- SlopesEqual(rb->PrevInAEL->Curr, rb->PrevInAEL->Top, rb->Curr, rb->Top, m_UseFullRange) &&
- (rb->WindDelta != 0) && (rb->PrevInAEL->WindDelta != 0))
- {
- OutPt *Op2 = AddOutPt(rb->PrevInAEL, rb->Bot);
- AddJoin(Op1, Op2, rb->Top);
- }
-
- TEdge* e = lb->NextInAEL;
- if (e)
- {
- while( e != rb )
- {
- //nb: For calculating winding counts etc, IntersectEdges() assumes
- //that param1 will be to the Right of param2 ABOVE the intersection ...
- IntersectEdges(rb , e , lb->Curr); //order important here
- e = e->NextInAEL;
- }
- }
- }
-
- }
-}
-//------------------------------------------------------------------------------
-
-void Clipper::DeleteFromSEL(TEdge *e)
-{
- TEdge* SelPrev = e->PrevInSEL;
- TEdge* SelNext = e->NextInSEL;
- if( !SelPrev && !SelNext && (e != m_SortedEdges) ) return; //already deleted
- if( SelPrev ) SelPrev->NextInSEL = SelNext;
- else m_SortedEdges = SelNext;
- if( SelNext ) SelNext->PrevInSEL = SelPrev;
- e->NextInSEL = 0;
- e->PrevInSEL = 0;
-}
-//------------------------------------------------------------------------------
-
-#ifdef use_xyz
-void Clipper::SetZ(IntPoint& pt, TEdge& e1, TEdge& e2)
-{
- if (pt.Z != 0 || !m_ZFill) return;
- else if (pt == e1.Bot) pt.Z = e1.Bot.Z;
- else if (pt == e1.Top) pt.Z = e1.Top.Z;
- else if (pt == e2.Bot) pt.Z = e2.Bot.Z;
- else if (pt == e2.Top) pt.Z = e2.Top.Z;
- else (*m_ZFill)(e1.Bot, e1.Top, e2.Bot, e2.Top, pt);
-}
-//------------------------------------------------------------------------------
-#endif
-
-void Clipper::IntersectEdges(TEdge *e1, TEdge *e2, IntPoint &Pt)
-{
- bool e1Contributing = ( e1->OutIdx >= 0 );
- bool e2Contributing = ( e2->OutIdx >= 0 );
-
-#ifdef use_xyz
- SetZ(Pt, *e1, *e2);
-#endif
-
-#ifdef use_lines
- //if either edge is on an OPEN path ...
- if (e1->WindDelta == 0 || e2->WindDelta == 0)
- {
- //ignore subject-subject open path intersections UNLESS they
- //are both open paths, AND they are both 'contributing maximas' ...
- if (e1->WindDelta == 0 && e2->WindDelta == 0) return;
-
- //if intersecting a subj line with a subj poly ...
- else if (e1->PolyTyp == e2->PolyTyp &&
- e1->WindDelta != e2->WindDelta && m_ClipType == ctUnion)
- {
- if (e1->WindDelta == 0)
- {
- if (e2Contributing)
- {
- AddOutPt(e1, Pt);
- if (e1Contributing) e1->OutIdx = Unassigned;
- }
- }
- else
- {
- if (e1Contributing)
- {
- AddOutPt(e2, Pt);
- if (e2Contributing) e2->OutIdx = Unassigned;
- }
- }
- }
- else if (e1->PolyTyp != e2->PolyTyp)
- {
- //toggle subj open path OutIdx on/off when Abs(clip.WndCnt) == 1 ...
- if ((e1->WindDelta == 0) && abs(e2->WindCnt) == 1 &&
- (m_ClipType != ctUnion || e2->WindCnt2 == 0))
- {
- AddOutPt(e1, Pt);
- if (e1Contributing) e1->OutIdx = Unassigned;
- }
- else if ((e2->WindDelta == 0) && (abs(e1->WindCnt) == 1) &&
- (m_ClipType != ctUnion || e1->WindCnt2 == 0))
- {
- AddOutPt(e2, Pt);
- if (e2Contributing) e2->OutIdx = Unassigned;
- }
- }
- return;
- }
-#endif
-
- //update winding counts...
- //assumes that e1 will be to the Right of e2 ABOVE the intersection
- if ( e1->PolyTyp == e2->PolyTyp )
- {
- if ( IsEvenOddFillType( *e1) )
- {
- int oldE1WindCnt = e1->WindCnt;
- e1->WindCnt = e2->WindCnt;
- e2->WindCnt = oldE1WindCnt;
- } else
- {
- if (e1->WindCnt + e2->WindDelta == 0 ) e1->WindCnt = -e1->WindCnt;
- else e1->WindCnt += e2->WindDelta;
- if ( e2->WindCnt - e1->WindDelta == 0 ) e2->WindCnt = -e2->WindCnt;
- else e2->WindCnt -= e1->WindDelta;
- }
- } else
- {
- if (!IsEvenOddFillType(*e2)) e1->WindCnt2 += e2->WindDelta;
- else e1->WindCnt2 = ( e1->WindCnt2 == 0 ) ? 1 : 0;
- if (!IsEvenOddFillType(*e1)) e2->WindCnt2 -= e1->WindDelta;
- else e2->WindCnt2 = ( e2->WindCnt2 == 0 ) ? 1 : 0;
- }
-
- PolyFillType e1FillType, e2FillType, e1FillType2, e2FillType2;
- if (e1->PolyTyp == ptSubject)
- {
- e1FillType = m_SubjFillType;
- e1FillType2 = m_ClipFillType;
- } else
- {
- e1FillType = m_ClipFillType;
- e1FillType2 = m_SubjFillType;
- }
- if (e2->PolyTyp == ptSubject)
- {
- e2FillType = m_SubjFillType;
- e2FillType2 = m_ClipFillType;
- } else
- {
- e2FillType = m_ClipFillType;
- e2FillType2 = m_SubjFillType;
- }
-
- cInt e1Wc, e2Wc;
- switch (e1FillType)
- {
- case pftPositive: e1Wc = e1->WindCnt; break;
- case pftNegative: e1Wc = -e1->WindCnt; break;
- default: e1Wc = Abs(e1->WindCnt);
- }
- switch(e2FillType)
- {
- case pftPositive: e2Wc = e2->WindCnt; break;
- case pftNegative: e2Wc = -e2->WindCnt; break;
- default: e2Wc = Abs(e2->WindCnt);
- }
-
- if ( e1Contributing && e2Contributing )
- {
- if ((e1Wc != 0 && e1Wc != 1) || (e2Wc != 0 && e2Wc != 1) ||
- (e1->PolyTyp != e2->PolyTyp && m_ClipType != ctXor) )
- {
- AddLocalMaxPoly(e1, e2, Pt);
- }
- else
- {
- AddOutPt(e1, Pt);
- AddOutPt(e2, Pt);
- SwapSides( *e1 , *e2 );
- SwapPolyIndexes( *e1 , *e2 );
- }
- }
- else if ( e1Contributing )
- {
- if (e2Wc == 0 || e2Wc == 1)
- {
- AddOutPt(e1, Pt);
- SwapSides(*e1, *e2);
- SwapPolyIndexes(*e1, *e2);
- }
- }
- else if ( e2Contributing )
- {
- if (e1Wc == 0 || e1Wc == 1)
- {
- AddOutPt(e2, Pt);
- SwapSides(*e1, *e2);
- SwapPolyIndexes(*e1, *e2);
- }
- }
- else if ( (e1Wc == 0 || e1Wc == 1) && (e2Wc == 0 || e2Wc == 1))
- {
- //neither edge is currently contributing ...
-
- cInt e1Wc2, e2Wc2;
- switch (e1FillType2)
- {
- case pftPositive: e1Wc2 = e1->WindCnt2; break;
- case pftNegative : e1Wc2 = -e1->WindCnt2; break;
- default: e1Wc2 = Abs(e1->WindCnt2);
- }
- switch (e2FillType2)
- {
- case pftPositive: e2Wc2 = e2->WindCnt2; break;
- case pftNegative: e2Wc2 = -e2->WindCnt2; break;
- default: e2Wc2 = Abs(e2->WindCnt2);
- }
-
- if (e1->PolyTyp != e2->PolyTyp)
- {
- AddLocalMinPoly(e1, e2, Pt);
- }
- else if (e1Wc == 1 && e2Wc == 1)
- switch( m_ClipType ) {
- case ctIntersection:
- if (e1Wc2 > 0 && e2Wc2 > 0)
- AddLocalMinPoly(e1, e2, Pt);
- break;
- case ctUnion:
- if ( e1Wc2 <= 0 && e2Wc2 <= 0 )
- AddLocalMinPoly(e1, e2, Pt);
- break;
- case ctDifference:
- if (((e1->PolyTyp == ptClip) && (e1Wc2 > 0) && (e2Wc2 > 0)) ||
- ((e1->PolyTyp == ptSubject) && (e1Wc2 <= 0) && (e2Wc2 <= 0)))
- AddLocalMinPoly(e1, e2, Pt);
- break;
- case ctXor:
- AddLocalMinPoly(e1, e2, Pt);
- }
- else
- SwapSides( *e1, *e2 );
- }
-}
-//------------------------------------------------------------------------------
-
-void Clipper::SetHoleState(TEdge *e, OutRec *outrec)
-{
- TEdge *e2 = e->PrevInAEL;
- TEdge *eTmp = 0;
- while (e2)
- {
- if (e2->OutIdx >= 0 && e2->WindDelta != 0)
- {
- if (!eTmp) eTmp = e2;
- else if (eTmp->OutIdx == e2->OutIdx) eTmp = 0;
- }
- e2 = e2->PrevInAEL;
- }
- if (!eTmp)
- {
- outrec->FirstLeft = 0;
- outrec->IsHole = false;
- }
- else
- {
- outrec->FirstLeft = m_PolyOuts[eTmp->OutIdx];
- outrec->IsHole = !outrec->FirstLeft->IsHole;
- }
-}
-//------------------------------------------------------------------------------
-
-OutRec* GetLowermostRec(OutRec *outRec1, OutRec *outRec2)
-{
- //work out which polygon fragment has the correct hole state ...
- if (!outRec1->BottomPt)
- outRec1->BottomPt = GetBottomPt(outRec1->Pts);
- if (!outRec2->BottomPt)
- outRec2->BottomPt = GetBottomPt(outRec2->Pts);
- OutPt *OutPt1 = outRec1->BottomPt;
- OutPt *OutPt2 = outRec2->BottomPt;
- if (OutPt1->Pt.Y > OutPt2->Pt.Y) return outRec1;
- else if (OutPt1->Pt.Y < OutPt2->Pt.Y) return outRec2;
- else if (OutPt1->Pt.X < OutPt2->Pt.X) return outRec1;
- else if (OutPt1->Pt.X > OutPt2->Pt.X) return outRec2;
- else if (OutPt1->Next == OutPt1) return outRec2;
- else if (OutPt2->Next == OutPt2) return outRec1;
- else if (FirstIsBottomPt(OutPt1, OutPt2)) return outRec1;
- else return outRec2;
-}
-//------------------------------------------------------------------------------
-
-bool OutRec1RightOfOutRec2(OutRec* outRec1, OutRec* outRec2)
-{
- do
- {
- outRec1 = outRec1->FirstLeft;
- if (outRec1 == outRec2) return true;
- } while (outRec1);
- return false;
-}
-//------------------------------------------------------------------------------
-
-OutRec* Clipper::GetOutRec(int Idx)
-{
- OutRec* outrec = m_PolyOuts[Idx];
- while (outrec != m_PolyOuts[outrec->Idx])
- outrec = m_PolyOuts[outrec->Idx];
- return outrec;
-}
-//------------------------------------------------------------------------------
-
-void Clipper::AppendPolygon(TEdge *e1, TEdge *e2)
-{
- //get the start and ends of both output polygons ...
- OutRec *outRec1 = m_PolyOuts[e1->OutIdx];
- OutRec *outRec2 = m_PolyOuts[e2->OutIdx];
-
- OutRec *holeStateRec;
- if (OutRec1RightOfOutRec2(outRec1, outRec2))
- holeStateRec = outRec2;
- else if (OutRec1RightOfOutRec2(outRec2, outRec1))
- holeStateRec = outRec1;
- else
- holeStateRec = GetLowermostRec(outRec1, outRec2);
-
- //get the start and ends of both output polygons and
- //join e2 poly onto e1 poly and delete pointers to e2 ...
-
- OutPt* p1_lft = outRec1->Pts;
- OutPt* p1_rt = p1_lft->Prev;
- OutPt* p2_lft = outRec2->Pts;
- OutPt* p2_rt = p2_lft->Prev;
-
- //join e2 poly onto e1 poly and delete pointers to e2 ...
- if( e1->Side == esLeft )
- {
- if( e2->Side == esLeft )
- {
- //z y x a b c
- ReversePolyPtLinks(p2_lft);
- p2_lft->Next = p1_lft;
- p1_lft->Prev = p2_lft;
- p1_rt->Next = p2_rt;
- p2_rt->Prev = p1_rt;
- outRec1->Pts = p2_rt;
- } else
- {
- //x y z a b c
- p2_rt->Next = p1_lft;
- p1_lft->Prev = p2_rt;
- p2_lft->Prev = p1_rt;
- p1_rt->Next = p2_lft;
- outRec1->Pts = p2_lft;
- }
- } else
- {
- if( e2->Side == esRight )
- {
- //a b c z y x
- ReversePolyPtLinks(p2_lft);
- p1_rt->Next = p2_rt;
- p2_rt->Prev = p1_rt;
- p2_lft->Next = p1_lft;
- p1_lft->Prev = p2_lft;
- } else
- {
- //a b c x y z
- p1_rt->Next = p2_lft;
- p2_lft->Prev = p1_rt;
- p1_lft->Prev = p2_rt;
- p2_rt->Next = p1_lft;
- }
- }
-
- outRec1->BottomPt = 0;
- if (holeStateRec == outRec2)
- {
- if (outRec2->FirstLeft != outRec1)
- outRec1->FirstLeft = outRec2->FirstLeft;
- outRec1->IsHole = outRec2->IsHole;
- }
- outRec2->Pts = 0;
- outRec2->BottomPt = 0;
- outRec2->FirstLeft = outRec1;
-
- int OKIdx = e1->OutIdx;
- int ObsoleteIdx = e2->OutIdx;
-
- e1->OutIdx = Unassigned; //nb: safe because we only get here via AddLocalMaxPoly
- e2->OutIdx = Unassigned;
-
- TEdge* e = m_ActiveEdges;
- while( e )
- {
- if( e->OutIdx == ObsoleteIdx )
- {
- e->OutIdx = OKIdx;
- e->Side = e1->Side;
- break;
- }
- e = e->NextInAEL;
- }
-
- outRec2->Idx = outRec1->Idx;
-}
-//------------------------------------------------------------------------------
-
-OutPt* Clipper::AddOutPt(TEdge *e, const IntPoint &pt)
-{
- if( e->OutIdx < 0 )
- {
- OutRec *outRec = CreateOutRec();
- outRec->IsOpen = (e->WindDelta == 0);
- OutPt* newOp = new OutPt;
- outRec->Pts = newOp;
- newOp->Idx = outRec->Idx;
- newOp->Pt = pt;
- newOp->Next = newOp;
- newOp->Prev = newOp;
- if (!outRec->IsOpen)
- SetHoleState(e, outRec);
- e->OutIdx = outRec->Idx;
- return newOp;
- } else
- {
- OutRec *outRec = m_PolyOuts[e->OutIdx];
- //OutRec.Pts is the 'Left-most' point & OutRec.Pts.Prev is the 'Right-most'
- OutPt* op = outRec->Pts;
-
- bool ToFront = (e->Side == esLeft);
- if (ToFront && (pt == op->Pt)) return op;
- else if (!ToFront && (pt == op->Prev->Pt)) return op->Prev;
-
- OutPt* newOp = new OutPt;
- newOp->Idx = outRec->Idx;
- newOp->Pt = pt;
- newOp->Next = op;
- newOp->Prev = op->Prev;
- newOp->Prev->Next = newOp;
- op->Prev = newOp;
- if (ToFront) outRec->Pts = newOp;
- return newOp;
- }
-}
-//------------------------------------------------------------------------------
-
-OutPt* Clipper::GetLastOutPt(TEdge *e)
-{
- OutRec *outRec = m_PolyOuts[e->OutIdx];
- if (e->Side == esLeft)
- return outRec->Pts;
- else
- return outRec->Pts->Prev;
-}
-//------------------------------------------------------------------------------
-
-void Clipper::ProcessHorizontals()
-{
- TEdge* horzEdge;
- while (PopEdgeFromSEL(horzEdge))
- ProcessHorizontal(horzEdge);
-}
-//------------------------------------------------------------------------------
-
-inline bool IsMinima(TEdge *e)
-{
- return e && (e->Prev->NextInLML != e) && (e->Next->NextInLML != e);
-}
-//------------------------------------------------------------------------------
-
-inline bool IsMaxima(TEdge *e, const cInt Y)
-{
- return e && e->Top.Y == Y && !e->NextInLML;
-}
-//------------------------------------------------------------------------------
-
-inline bool IsIntermediate(TEdge *e, const cInt Y)
-{
- return e->Top.Y == Y && e->NextInLML;
-}
-//------------------------------------------------------------------------------
-
-TEdge *GetMaximaPair(TEdge *e)
-{
- if ((e->Next->Top == e->Top) && !e->Next->NextInLML)
- return e->Next;
- else if ((e->Prev->Top == e->Top) && !e->Prev->NextInLML)
- return e->Prev;
- else return 0;
-}
-//------------------------------------------------------------------------------
-
-TEdge *GetMaximaPairEx(TEdge *e)
-{
- //as GetMaximaPair() but returns 0 if MaxPair isn't in AEL (unless it's horizontal)
- TEdge* result = GetMaximaPair(e);
- if (result && (result->OutIdx == Skip ||
- (result->NextInAEL == result->PrevInAEL && !IsHorizontal(*result)))) return 0;
- return result;
-}
-//------------------------------------------------------------------------------
-
-void Clipper::SwapPositionsInSEL(TEdge *Edge1, TEdge *Edge2)
-{
- if( !( Edge1->NextInSEL ) && !( Edge1->PrevInSEL ) ) return;
- if( !( Edge2->NextInSEL ) && !( Edge2->PrevInSEL ) ) return;
-
- if( Edge1->NextInSEL == Edge2 )
- {
- TEdge* Next = Edge2->NextInSEL;
- if( Next ) Next->PrevInSEL = Edge1;
- TEdge* Prev = Edge1->PrevInSEL;
- if( Prev ) Prev->NextInSEL = Edge2;
- Edge2->PrevInSEL = Prev;
- Edge2->NextInSEL = Edge1;
- Edge1->PrevInSEL = Edge2;
- Edge1->NextInSEL = Next;
- }
- else if( Edge2->NextInSEL == Edge1 )
- {
- TEdge* Next = Edge1->NextInSEL;
- if( Next ) Next->PrevInSEL = Edge2;
- TEdge* Prev = Edge2->PrevInSEL;
- if( Prev ) Prev->NextInSEL = Edge1;
- Edge1->PrevInSEL = Prev;
- Edge1->NextInSEL = Edge2;
- Edge2->PrevInSEL = Edge1;
- Edge2->NextInSEL = Next;
- }
- else
- {
- TEdge* Next = Edge1->NextInSEL;
- TEdge* Prev = Edge1->PrevInSEL;
- Edge1->NextInSEL = Edge2->NextInSEL;
- if( Edge1->NextInSEL ) Edge1->NextInSEL->PrevInSEL = Edge1;
- Edge1->PrevInSEL = Edge2->PrevInSEL;
- if( Edge1->PrevInSEL ) Edge1->PrevInSEL->NextInSEL = Edge1;
- Edge2->NextInSEL = Next;
- if( Edge2->NextInSEL ) Edge2->NextInSEL->PrevInSEL = Edge2;
- Edge2->PrevInSEL = Prev;
- if( Edge2->PrevInSEL ) Edge2->PrevInSEL->NextInSEL = Edge2;
- }
-
- if( !Edge1->PrevInSEL ) m_SortedEdges = Edge1;
- else if( !Edge2->PrevInSEL ) m_SortedEdges = Edge2;
-}
-//------------------------------------------------------------------------------
-
-TEdge* GetNextInAEL(TEdge *e, Direction dir)
-{
- return dir == dLeftToRight ? e->NextInAEL : e->PrevInAEL;
-}
-//------------------------------------------------------------------------------
-
-void GetHorzDirection(TEdge& HorzEdge, Direction& Dir, cInt& Left, cInt& Right)
-{
- if (HorzEdge.Bot.X < HorzEdge.Top.X)
- {
- Left = HorzEdge.Bot.X;
- Right = HorzEdge.Top.X;
- Dir = dLeftToRight;
- } else
- {
- Left = HorzEdge.Top.X;
- Right = HorzEdge.Bot.X;
- Dir = dRightToLeft;
- }
-}
-//------------------------------------------------------------------------
-
-/*******************************************************************************
-* Notes: Horizontal edges (HEs) at scanline intersections (ie at the Top or *
-* Bottom of a scanbeam) are processed as if layered. The order in which HEs *
-* are processed doesn't matter. HEs intersect with other HE Bot.Xs only [#] *
-* (or they could intersect with Top.Xs only, ie EITHER Bot.Xs OR Top.Xs), *
-* and with other non-horizontal edges [*]. Once these intersections are *
-* processed, intermediate HEs then 'promote' the Edge above (NextInLML) into *
-* the AEL. These 'promoted' edges may in turn intersect [%] with other HEs. *
-*******************************************************************************/
-
-void Clipper::ProcessHorizontal(TEdge *horzEdge)
-{
- Direction dir;
- cInt horzLeft, horzRight;
- bool IsOpen = (horzEdge->WindDelta == 0);
-
- GetHorzDirection(*horzEdge, dir, horzLeft, horzRight);
-
- TEdge* eLastHorz = horzEdge, *eMaxPair = 0;
- while (eLastHorz->NextInLML && IsHorizontal(*eLastHorz->NextInLML))
- eLastHorz = eLastHorz->NextInLML;
- if (!eLastHorz->NextInLML)
- eMaxPair = GetMaximaPair(eLastHorz);
-
- MaximaList::const_iterator maxIt;
- MaximaList::const_reverse_iterator maxRit;
- if (m_Maxima.size() > 0)
- {
- //get the first maxima in range (X) ...
- if (dir == dLeftToRight)
- {
- maxIt = m_Maxima.begin();
- while (maxIt != m_Maxima.end() && *maxIt <= horzEdge->Bot.X) maxIt++;
- if (maxIt != m_Maxima.end() && *maxIt >= eLastHorz->Top.X)
- maxIt = m_Maxima.end();
- }
- else
- {
- maxRit = m_Maxima.rbegin();
- while (maxRit != m_Maxima.rend() && *maxRit > horzEdge->Bot.X) maxRit++;
- if (maxRit != m_Maxima.rend() && *maxRit <= eLastHorz->Top.X)
- maxRit = m_Maxima.rend();
- }
- }
-
- OutPt* op1 = 0;
-
- for (;;) //loop through consec. horizontal edges
- {
-
- bool IsLastHorz = (horzEdge == eLastHorz);
- TEdge* e = GetNextInAEL(horzEdge, dir);
- while(e)
- {
-
- //this code block inserts extra coords into horizontal edges (in output
- //polygons) whereever maxima touch these horizontal edges. This helps
- //'simplifying' polygons (ie if the Simplify property is set).
- if (m_Maxima.size() > 0)
- {
- if (dir == dLeftToRight)
- {
- while (maxIt != m_Maxima.end() && *maxIt < e->Curr.X)
- {
- if (horzEdge->OutIdx >= 0 && !IsOpen)
- AddOutPt(horzEdge, IntPoint(*maxIt, horzEdge->Bot.Y));
- maxIt++;
- }
- }
- else
- {
- while (maxRit != m_Maxima.rend() && *maxRit > e->Curr.X)
- {
- if (horzEdge->OutIdx >= 0 && !IsOpen)
- AddOutPt(horzEdge, IntPoint(*maxRit, horzEdge->Bot.Y));
- maxRit++;
- }
- }
- };
-
- if ((dir == dLeftToRight && e->Curr.X > horzRight) ||
- (dir == dRightToLeft && e->Curr.X < horzLeft)) break;
-
- //Also break if we've got to the end of an intermediate horizontal edge ...
- //nb: Smaller Dx's are to the right of larger Dx's ABOVE the horizontal.
- if (e->Curr.X == horzEdge->Top.X && horzEdge->NextInLML &&
- e->Dx < horzEdge->NextInLML->Dx) break;
-
- if (horzEdge->OutIdx >= 0 && !IsOpen) //note: may be done multiple times
- {
-#ifdef use_xyz
- if (dir == dLeftToRight) SetZ(e->Curr, *horzEdge, *e);
- else SetZ(e->Curr, *e, *horzEdge);
-#endif
- op1 = AddOutPt(horzEdge, e->Curr);
- TEdge* eNextHorz = m_SortedEdges;
- while (eNextHorz)
- {
- if (eNextHorz->OutIdx >= 0 &&
- HorzSegmentsOverlap(horzEdge->Bot.X,
- horzEdge->Top.X, eNextHorz->Bot.X, eNextHorz->Top.X))
- {
- OutPt* op2 = GetLastOutPt(eNextHorz);
- AddJoin(op2, op1, eNextHorz->Top);
- }
- eNextHorz = eNextHorz->NextInSEL;
- }
- AddGhostJoin(op1, horzEdge->Bot);
- }
-
- //OK, so far we're still in range of the horizontal Edge but make sure
- //we're at the last of consec. horizontals when matching with eMaxPair
- if(e == eMaxPair && IsLastHorz)
- {
- if (horzEdge->OutIdx >= 0)
- AddLocalMaxPoly(horzEdge, eMaxPair, horzEdge->Top);
- DeleteFromAEL(horzEdge);
- DeleteFromAEL(eMaxPair);
- return;
- }
-
- if(dir == dLeftToRight)
- {
- IntPoint Pt = IntPoint(e->Curr.X, horzEdge->Curr.Y);
- IntersectEdges(horzEdge, e, Pt);
- }
- else
- {
- IntPoint Pt = IntPoint(e->Curr.X, horzEdge->Curr.Y);
- IntersectEdges( e, horzEdge, Pt);
- }
- TEdge* eNext = GetNextInAEL(e, dir);
- SwapPositionsInAEL( horzEdge, e );
- e = eNext;
- } //end while(e)
-
- //Break out of loop if HorzEdge.NextInLML is not also horizontal ...
- if (!horzEdge->NextInLML || !IsHorizontal(*horzEdge->NextInLML)) break;
-
- UpdateEdgeIntoAEL(horzEdge);
- if (horzEdge->OutIdx >= 0) AddOutPt(horzEdge, horzEdge->Bot);
- GetHorzDirection(*horzEdge, dir, horzLeft, horzRight);
-
- } //end for (;;)
-
- if (horzEdge->OutIdx >= 0 && !op1)
- {
- op1 = GetLastOutPt(horzEdge);
- TEdge* eNextHorz = m_SortedEdges;
- while (eNextHorz)
- {
- if (eNextHorz->OutIdx >= 0 &&
- HorzSegmentsOverlap(horzEdge->Bot.X,
- horzEdge->Top.X, eNextHorz->Bot.X, eNextHorz->Top.X))
- {
- OutPt* op2 = GetLastOutPt(eNextHorz);
- AddJoin(op2, op1, eNextHorz->Top);
- }
- eNextHorz = eNextHorz->NextInSEL;
- }
- AddGhostJoin(op1, horzEdge->Top);
- }
-
- if (horzEdge->NextInLML)
- {
- if(horzEdge->OutIdx >= 0)
- {
- op1 = AddOutPt( horzEdge, horzEdge->Top);
- UpdateEdgeIntoAEL(horzEdge);
- if (horzEdge->WindDelta == 0) return;
- //nb: HorzEdge is no longer horizontal here
- TEdge* ePrev = horzEdge->PrevInAEL;
- TEdge* eNext = horzEdge->NextInAEL;
- if (ePrev && ePrev->Curr.X == horzEdge->Bot.X &&
- ePrev->Curr.Y == horzEdge->Bot.Y && ePrev->WindDelta != 0 &&
- (ePrev->OutIdx >= 0 && ePrev->Curr.Y > ePrev->Top.Y &&
- SlopesEqual(*horzEdge, *ePrev, m_UseFullRange)))
- {
- OutPt* op2 = AddOutPt(ePrev, horzEdge->Bot);
- AddJoin(op1, op2, horzEdge->Top);
- }
- else if (eNext && eNext->Curr.X == horzEdge->Bot.X &&
- eNext->Curr.Y == horzEdge->Bot.Y && eNext->WindDelta != 0 &&
- eNext->OutIdx >= 0 && eNext->Curr.Y > eNext->Top.Y &&
- SlopesEqual(*horzEdge, *eNext, m_UseFullRange))
- {
- OutPt* op2 = AddOutPt(eNext, horzEdge->Bot);
- AddJoin(op1, op2, horzEdge->Top);
- }
- }
- else
- UpdateEdgeIntoAEL(horzEdge);
- }
- else
- {
- if (horzEdge->OutIdx >= 0) AddOutPt(horzEdge, horzEdge->Top);
- DeleteFromAEL(horzEdge);
- }
-}
-//------------------------------------------------------------------------------
-
-bool Clipper::ProcessIntersections(const cInt topY)
-{
- if( !m_ActiveEdges ) return true;
- CLIPPER_TRY {
- BuildIntersectList(topY);
- size_t IlSize = m_IntersectList.size();
- if (IlSize == 0) return true;
- if (IlSize == 1 || FixupIntersectionOrder()) ProcessIntersectList();
- else return false;
- }
- CLIPPER_CATCH(...)
- {
- m_SortedEdges = 0;
- DisposeIntersectNodes();
- CLIPPER_THROW(clipperException("ProcessIntersections error"));
- }
- m_SortedEdges = 0;
- return true;
-}
-//------------------------------------------------------------------------------
-
-void Clipper::DisposeIntersectNodes()
-{
- for (size_t i = 0; i < m_IntersectList.size(); ++i )
- delete m_IntersectList[i];
- m_IntersectList.clear();
-}
-//------------------------------------------------------------------------------
-
-void Clipper::BuildIntersectList(const cInt topY)
-{
- if ( !m_ActiveEdges ) return;
-
- //prepare for sorting ...
- TEdge* e = m_ActiveEdges;
- m_SortedEdges = e;
- while( e )
- {
- e->PrevInSEL = e->PrevInAEL;
- e->NextInSEL = e->NextInAEL;
- e->Curr.X = TopX( *e, topY );
- e = e->NextInAEL;
- }
-
- //bubblesort ...
- bool isModified;
- do
- {
- isModified = false;
- e = m_SortedEdges;
- while( e->NextInSEL )
- {
- TEdge *eNext = e->NextInSEL;
- IntPoint Pt;
- if(e->Curr.X > eNext->Curr.X)
- {
- IntersectPoint(*e, *eNext, Pt);
- if (Pt.Y < topY) Pt = IntPoint(TopX(*e, topY), topY);
- IntersectNode * newNode = new IntersectNode;
- newNode->Edge1 = e;
- newNode->Edge2 = eNext;
- newNode->Pt = Pt;
- m_IntersectList.push_back(newNode);
-
- SwapPositionsInSEL(e, eNext);
- isModified = true;
- }
- else
- e = eNext;
- }
- if( e->PrevInSEL ) e->PrevInSEL->NextInSEL = 0;
- else break;
- }
- while ( isModified );
- m_SortedEdges = 0; //important
-}
-//------------------------------------------------------------------------------
-
-
-void Clipper::ProcessIntersectList()
-{
- for (size_t i = 0; i < m_IntersectList.size(); ++i)
- {
- IntersectNode* iNode = m_IntersectList[i];
- {
- IntersectEdges( iNode->Edge1, iNode->Edge2, iNode->Pt);
- SwapPositionsInAEL( iNode->Edge1 , iNode->Edge2 );
- }
- delete iNode;
- }
- m_IntersectList.clear();
-}
-//------------------------------------------------------------------------------
-
-bool IntersectListSort(IntersectNode* node1, IntersectNode* node2)
-{
- return node2->Pt.Y < node1->Pt.Y;
-}
-//------------------------------------------------------------------------------
-
-inline bool EdgesAdjacent(const IntersectNode &inode)
-{
- return (inode.Edge1->NextInSEL == inode.Edge2) ||
- (inode.Edge1->PrevInSEL == inode.Edge2);
-}
-//------------------------------------------------------------------------------
-
-bool Clipper::FixupIntersectionOrder()
-{
- //pre-condition: intersections are sorted Bottom-most first.
- //Now it's crucial that intersections are made only between adjacent edges,
- //so to ensure this the order of intersections may need adjusting ...
- CopyAELToSEL();
- std::sort(m_IntersectList.begin(), m_IntersectList.end(), IntersectListSort);
- size_t cnt = m_IntersectList.size();
- for (size_t i = 0; i < cnt; ++i)
- {
- if (!EdgesAdjacent(*m_IntersectList[i]))
- {
- size_t j = i + 1;
- while (j < cnt && !EdgesAdjacent(*m_IntersectList[j])) j++;
- if (j == cnt) return false;
- std::swap(m_IntersectList[i], m_IntersectList[j]);
- }
- SwapPositionsInSEL(m_IntersectList[i]->Edge1, m_IntersectList[i]->Edge2);
- }
- return true;
-}
-//------------------------------------------------------------------------------
-
-void Clipper::DoMaxima(TEdge *e)
-{
- TEdge* eMaxPair = GetMaximaPairEx(e);
- if (!eMaxPair)
- {
- if (e->OutIdx >= 0)
- AddOutPt(e, e->Top);
- DeleteFromAEL(e);
- return;
- }
-
- TEdge* eNext = e->NextInAEL;
- while(eNext && eNext != eMaxPair)
- {
- IntersectEdges(e, eNext, e->Top);
- SwapPositionsInAEL(e, eNext);
- eNext = e->NextInAEL;
- }
-
- if(e->OutIdx == Unassigned && eMaxPair->OutIdx == Unassigned)
- {
- DeleteFromAEL(e);
- DeleteFromAEL(eMaxPair);
- }
- else if( e->OutIdx >= 0 && eMaxPair->OutIdx >= 0 )
- {
- if (e->OutIdx >= 0) AddLocalMaxPoly(e, eMaxPair, e->Top);
- DeleteFromAEL(e);
- DeleteFromAEL(eMaxPair);
- }
-#ifdef use_lines
- else if (e->WindDelta == 0)
- {
- if (e->OutIdx >= 0)
- {
- AddOutPt(e, e->Top);
- e->OutIdx = Unassigned;
- }
- DeleteFromAEL(e);
-
- if (eMaxPair->OutIdx >= 0)
- {
- AddOutPt(eMaxPair, e->Top);
- eMaxPair->OutIdx = Unassigned;
- }
- DeleteFromAEL(eMaxPair);
- }
-#endif
- else CLIPPER_THROW(clipperException("DoMaxima error"));
-}
-//------------------------------------------------------------------------------
-
-void Clipper::ProcessEdgesAtTopOfScanbeam(const cInt topY)
-{
- TEdge* e = m_ActiveEdges;
- while( e )
- {
- //1. process maxima, treating them as if they're 'bent' horizontal edges,
- // but exclude maxima with horizontal edges. nb: e can't be a horizontal.
- bool IsMaximaEdge = IsMaxima(e, topY);
-
- if(IsMaximaEdge)
- {
- TEdge* eMaxPair = GetMaximaPairEx(e);
- IsMaximaEdge = (!eMaxPair || !IsHorizontal(*eMaxPair));
- }
-
- if(IsMaximaEdge)
- {
- if (m_StrictSimple) m_Maxima.push_back(e->Top.X);
- TEdge* ePrev = e->PrevInAEL;
- DoMaxima(e);
- if( !ePrev ) e = m_ActiveEdges;
- else e = ePrev->NextInAEL;
- }
- else
- {
- //2. promote horizontal edges, otherwise update Curr.X and Curr.Y ...
- if (IsIntermediate(e, topY) && IsHorizontal(*e->NextInLML))
- {
- UpdateEdgeIntoAEL(e);
- if (e->OutIdx >= 0)
- AddOutPt(e, e->Bot);
- AddEdgeToSEL(e);
- }
- else
- {
- e->Curr.X = TopX( *e, topY );
- e->Curr.Y = topY;
-#ifdef use_xyz
- e->Curr.Z = topY == e->Top.Y ? e->Top.Z : (topY == e->Bot.Y ? e->Bot.Z : 0);
-#endif
- }
-
- //When StrictlySimple and 'e' is being touched by another edge, then
- //make sure both edges have a vertex here ...
- if (m_StrictSimple)
- {
- TEdge* ePrev = e->PrevInAEL;
- if ((e->OutIdx >= 0) && (e->WindDelta != 0) && ePrev && (ePrev->OutIdx >= 0) &&
- (ePrev->Curr.X == e->Curr.X) && (ePrev->WindDelta != 0))
- {
- IntPoint pt = e->Curr;
-#ifdef use_xyz
- SetZ(pt, *ePrev, *e);
-#endif
- OutPt* op = AddOutPt(ePrev, pt);
- OutPt* op2 = AddOutPt(e, pt);
- AddJoin(op, op2, pt); //StrictlySimple (type-3) join
- }
- }
-
- e = e->NextInAEL;
- }
- }
-
- //3. Process horizontals at the Top of the scanbeam ...
- m_Maxima.sort();
- ProcessHorizontals();
- m_Maxima.clear();
-
- //4. Promote intermediate vertices ...
- e = m_ActiveEdges;
- while(e)
- {
- if(IsIntermediate(e, topY))
- {
- OutPt* op = 0;
- if( e->OutIdx >= 0 )
- op = AddOutPt(e, e->Top);
- UpdateEdgeIntoAEL(e);
-
- //if output polygons share an edge, they'll need joining later ...
- TEdge* ePrev = e->PrevInAEL;
- TEdge* eNext = e->NextInAEL;
- if (ePrev && ePrev->Curr.X == e->Bot.X &&
- ePrev->Curr.Y == e->Bot.Y && op &&
- ePrev->OutIdx >= 0 && ePrev->Curr.Y > ePrev->Top.Y &&
- SlopesEqual(e->Curr, e->Top, ePrev->Curr, ePrev->Top, m_UseFullRange) &&
- (e->WindDelta != 0) && (ePrev->WindDelta != 0))
- {
- OutPt* op2 = AddOutPt(ePrev, e->Bot);
- AddJoin(op, op2, e->Top);
- }
- else if (eNext && eNext->Curr.X == e->Bot.X &&
- eNext->Curr.Y == e->Bot.Y && op &&
- eNext->OutIdx >= 0 && eNext->Curr.Y > eNext->Top.Y &&
- SlopesEqual(e->Curr, e->Top, eNext->Curr, eNext->Top, m_UseFullRange) &&
- (e->WindDelta != 0) && (eNext->WindDelta != 0))
- {
- OutPt* op2 = AddOutPt(eNext, e->Bot);
- AddJoin(op, op2, e->Top);
- }
- }
- e = e->NextInAEL;
- }
-}
-//------------------------------------------------------------------------------
-
-void Clipper::FixupOutPolyline(OutRec &outrec)
-{
- OutPt *pp = outrec.Pts;
- OutPt *lastPP = pp->Prev;
- while (pp != lastPP)
- {
- pp = pp->Next;
- if (pp->Pt == pp->Prev->Pt)
- {
- if (pp == lastPP) lastPP = pp->Prev;
- OutPt *tmpPP = pp->Prev;
- tmpPP->Next = pp->Next;
- pp->Next->Prev = tmpPP;
- delete pp;
- pp = tmpPP;
- }
- }
-
- if (pp == pp->Prev)
- {
- DisposeOutPts(pp);
- outrec.Pts = 0;
- return;
- }
-}
-//------------------------------------------------------------------------------
-
-void Clipper::FixupOutPolygon(OutRec &outrec)
-{
- //FixupOutPolygon() - removes duplicate points and simplifies consecutive
- //parallel edges by removing the middle vertex.
- OutPt *lastOK = 0;
- outrec.BottomPt = 0;
- OutPt *pp = outrec.Pts;
- bool preserveCol = m_PreserveCollinear || m_StrictSimple;
-
- for (;;)
- {
- if (pp->Prev == pp || pp->Prev == pp->Next)
- {
- DisposeOutPts(pp);
- outrec.Pts = 0;
- return;
- }
-
- //test for duplicate points and collinear edges ...
- if ((pp->Pt == pp->Next->Pt) || (pp->Pt == pp->Prev->Pt) ||
- (SlopesEqual(pp->Prev->Pt, pp->Pt, pp->Next->Pt, m_UseFullRange) &&
- (!preserveCol || !Pt2IsBetweenPt1AndPt3(pp->Prev->Pt, pp->Pt, pp->Next->Pt))))
- {
- lastOK = 0;
- OutPt *tmp = pp;
- pp->Prev->Next = pp->Next;
- pp->Next->Prev = pp->Prev;
- pp = pp->Prev;
- delete tmp;
- }
- else if (pp == lastOK) break;
- else
- {
- if (!lastOK) lastOK = pp;
- pp = pp->Next;
- }
- }
- outrec.Pts = pp;
-}
-//------------------------------------------------------------------------------
-
-int PointCount(OutPt *Pts)
-{
- if (!Pts) return 0;
- int result = 0;
- OutPt* p = Pts;
- do
- {
- result++;
- p = p->Next;
- }
- while (p != Pts);
- return result;
-}
-//------------------------------------------------------------------------------
-
-void Clipper::BuildResult(Paths &polys)
-{
- polys.reserve(m_PolyOuts.size());
- for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i)
- {
- if (!m_PolyOuts[i]->Pts) continue;
- Path pg;
- OutPt* p = m_PolyOuts[i]->Pts->Prev;
- int cnt = PointCount(p);
- if (cnt < 2) continue;
- pg.reserve(cnt);
- for (int i = 0; i < cnt; ++i)
- {
- pg.push_back(p->Pt);
- p = p->Prev;
- }
- polys.push_back(pg);
- }
-}
-//------------------------------------------------------------------------------
-
-void Clipper::BuildResult2(PolyTree& polytree)
-{
- polytree.Clear();
- polytree.AllNodes.reserve(m_PolyOuts.size());
- //add each output polygon/contour to polytree ...
- for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); i++)
- {
- OutRec* outRec = m_PolyOuts[i];
- int cnt = PointCount(outRec->Pts);
- if ((outRec->IsOpen && cnt < 2) || (!outRec->IsOpen && cnt < 3)) continue;
- FixHoleLinkage(*outRec);
- PolyNode* pn = new PolyNode();
- //nb: polytree takes ownership of all the PolyNodes
- polytree.AllNodes.push_back(pn);
- outRec->PolyNd = pn;
- pn->Parent = 0;
- pn->Index = 0;
- pn->Contour.reserve(cnt);
- OutPt *op = outRec->Pts->Prev;
- for (int j = 0; j < cnt; j++)
- {
- pn->Contour.push_back(op->Pt);
- op = op->Prev;
- }
- }
-
- //fixup PolyNode links etc ...
- polytree.Childs.reserve(m_PolyOuts.size());
- for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); i++)
- {
- OutRec* outRec = m_PolyOuts[i];
- if (!outRec->PolyNd) continue;
- if (outRec->IsOpen)
- {
- outRec->PolyNd->m_IsOpen = true;
- polytree.AddChild(*outRec->PolyNd);
- }
- else if (outRec->FirstLeft && outRec->FirstLeft->PolyNd)
- outRec->FirstLeft->PolyNd->AddChild(*outRec->PolyNd);
- else
- polytree.AddChild(*outRec->PolyNd);
- }
-}
-//------------------------------------------------------------------------------
-
-void SwapIntersectNodes(IntersectNode &int1, IntersectNode &int2)
-{
- //just swap the contents (because fIntersectNodes is a single-linked-list)
- IntersectNode inode = int1; //gets a copy of Int1
- int1.Edge1 = int2.Edge1;
- int1.Edge2 = int2.Edge2;
- int1.Pt = int2.Pt;
- int2.Edge1 = inode.Edge1;
- int2.Edge2 = inode.Edge2;
- int2.Pt = inode.Pt;
-}
-//------------------------------------------------------------------------------
-
-inline bool E2InsertsBeforeE1(TEdge &e1, TEdge &e2)
-{
- if (e2.Curr.X == e1.Curr.X)
- {
- if (e2.Top.Y > e1.Top.Y)
- return e2.Top.X < TopX(e1, e2.Top.Y);
- else return e1.Top.X > TopX(e2, e1.Top.Y);
- }
- else return e2.Curr.X < e1.Curr.X;
-}
-//------------------------------------------------------------------------------
-
-bool GetOverlap(const cInt a1, const cInt a2, const cInt b1, const cInt b2,
- cInt& Left, cInt& Right)
-{
- if (a1 < a2)
- {
- if (b1 < b2) {Left = std::max(a1,b1); Right = std::min(a2,b2);}
- else {Left = std::max(a1,b2); Right = std::min(a2,b1);}
- }
- else
- {
- if (b1 < b2) {Left = std::max(a2,b1); Right = std::min(a1,b2);}
- else {Left = std::max(a2,b2); Right = std::min(a1,b1);}
- }
- return Left < Right;
-}
-//------------------------------------------------------------------------------
-
-inline void UpdateOutPtIdxs(OutRec& outrec)
-{
- OutPt* op = outrec.Pts;
- do
- {
- op->Idx = outrec.Idx;
- op = op->Prev;
- }
- while(op != outrec.Pts);
-}
-//------------------------------------------------------------------------------
-
-void Clipper::InsertEdgeIntoAEL(TEdge *edge, TEdge* startEdge)
-{
- if(!m_ActiveEdges)
- {
- edge->PrevInAEL = 0;
- edge->NextInAEL = 0;
- m_ActiveEdges = edge;
- }
- else if(!startEdge && E2InsertsBeforeE1(*m_ActiveEdges, *edge))
- {
- edge->PrevInAEL = 0;
- edge->NextInAEL = m_ActiveEdges;
- m_ActiveEdges->PrevInAEL = edge;
- m_ActiveEdges = edge;
- }
- else
- {
- if(!startEdge) startEdge = m_ActiveEdges;
- while(startEdge->NextInAEL &&
- !E2InsertsBeforeE1(*startEdge->NextInAEL , *edge))
- startEdge = startEdge->NextInAEL;
- edge->NextInAEL = startEdge->NextInAEL;
- if(startEdge->NextInAEL) startEdge->NextInAEL->PrevInAEL = edge;
- edge->PrevInAEL = startEdge;
- startEdge->NextInAEL = edge;
- }
-}
-//----------------------------------------------------------------------
-
-OutPt* DupOutPt(OutPt* outPt, bool InsertAfter)
-{
- OutPt* result = new OutPt;
- result->Pt = outPt->Pt;
- result->Idx = outPt->Idx;
- if (InsertAfter)
- {
- result->Next = outPt->Next;
- result->Prev = outPt;
- outPt->Next->Prev = result;
- outPt->Next = result;
- }
- else
- {
- result->Prev = outPt->Prev;
- result->Next = outPt;
- outPt->Prev->Next = result;
- outPt->Prev = result;
- }
- return result;
-}
-//------------------------------------------------------------------------------
-
-bool JoinHorz(OutPt* op1, OutPt* op1b, OutPt* op2, OutPt* op2b,
- const IntPoint Pt, bool DiscardLeft)
-{
- Direction Dir1 = (op1->Pt.X > op1b->Pt.X ? dRightToLeft : dLeftToRight);
- Direction Dir2 = (op2->Pt.X > op2b->Pt.X ? dRightToLeft : dLeftToRight);
- if (Dir1 == Dir2) return false;
-
- //When DiscardLeft, we want Op1b to be on the Left of Op1, otherwise we
- //want Op1b to be on the Right. (And likewise with Op2 and Op2b.)
- //So, to facilitate this while inserting Op1b and Op2b ...
- //when DiscardLeft, make sure we're AT or RIGHT of Pt before adding Op1b,
- //otherwise make sure we're AT or LEFT of Pt. (Likewise with Op2b.)
- if (Dir1 == dLeftToRight)
- {
- while (op1->Next->Pt.X <= Pt.X &&
- op1->Next->Pt.X >= op1->Pt.X && op1->Next->Pt.Y == Pt.Y)
- op1 = op1->Next;
- if (DiscardLeft && (op1->Pt.X != Pt.X)) op1 = op1->Next;
- op1b = DupOutPt(op1, !DiscardLeft);
- if (op1b->Pt != Pt)
- {
- op1 = op1b;
- op1->Pt = Pt;
- op1b = DupOutPt(op1, !DiscardLeft);
- }
- }
- else
- {
- while (op1->Next->Pt.X >= Pt.X &&
- op1->Next->Pt.X <= op1->Pt.X && op1->Next->Pt.Y == Pt.Y)
- op1 = op1->Next;
- if (!DiscardLeft && (op1->Pt.X != Pt.X)) op1 = op1->Next;
- op1b = DupOutPt(op1, DiscardLeft);
- if (op1b->Pt != Pt)
- {
- op1 = op1b;
- op1->Pt = Pt;
- op1b = DupOutPt(op1, DiscardLeft);
- }
- }
-
- if (Dir2 == dLeftToRight)
- {
- while (op2->Next->Pt.X <= Pt.X &&
- op2->Next->Pt.X >= op2->Pt.X && op2->Next->Pt.Y == Pt.Y)
- op2 = op2->Next;
- if (DiscardLeft && (op2->Pt.X != Pt.X)) op2 = op2->Next;
- op2b = DupOutPt(op2, !DiscardLeft);
- if (op2b->Pt != Pt)
- {
- op2 = op2b;
- op2->Pt = Pt;
- op2b = DupOutPt(op2, !DiscardLeft);
- };
- } else
- {
- while (op2->Next->Pt.X >= Pt.X &&
- op2->Next->Pt.X <= op2->Pt.X && op2->Next->Pt.Y == Pt.Y)
- op2 = op2->Next;
- if (!DiscardLeft && (op2->Pt.X != Pt.X)) op2 = op2->Next;
- op2b = DupOutPt(op2, DiscardLeft);
- if (op2b->Pt != Pt)
- {
- op2 = op2b;
- op2->Pt = Pt;
- op2b = DupOutPt(op2, DiscardLeft);
- };
- };
-
- if ((Dir1 == dLeftToRight) == DiscardLeft)
- {
- op1->Prev = op2;
- op2->Next = op1;
- op1b->Next = op2b;
- op2b->Prev = op1b;
- }
- else
- {
- op1->Next = op2;
- op2->Prev = op1;
- op1b->Prev = op2b;
- op2b->Next = op1b;
- }
- return true;
-}
-//------------------------------------------------------------------------------
-
-bool Clipper::JoinPoints(Join *j, OutRec* outRec1, OutRec* outRec2)
-{
- OutPt *op1 = j->OutPt1, *op1b;
- OutPt *op2 = j->OutPt2, *op2b;
-
- //There are 3 kinds of joins for output polygons ...
- //1. Horizontal joins where Join.OutPt1 & Join.OutPt2 are vertices anywhere
- //along (horizontal) collinear edges (& Join.OffPt is on the same horizontal).
- //2. Non-horizontal joins where Join.OutPt1 & Join.OutPt2 are at the same
- //location at the Bottom of the overlapping segment (& Join.OffPt is above).
- //3. StrictSimple joins where edges touch but are not collinear and where
- //Join.OutPt1, Join.OutPt2 & Join.OffPt all share the same point.
- bool isHorizontal = (j->OutPt1->Pt.Y == j->OffPt.Y);
-
- if (isHorizontal && (j->OffPt == j->OutPt1->Pt) &&
- (j->OffPt == j->OutPt2->Pt))
- {
- //Strictly Simple join ...
- if (outRec1 != outRec2) return false;
- op1b = j->OutPt1->Next;
- while (op1b != op1 && (op1b->Pt == j->OffPt))
- op1b = op1b->Next;
- bool reverse1 = (op1b->Pt.Y > j->OffPt.Y);
- op2b = j->OutPt2->Next;
- while (op2b != op2 && (op2b->Pt == j->OffPt))
- op2b = op2b->Next;
- bool reverse2 = (op2b->Pt.Y > j->OffPt.Y);
- if (reverse1 == reverse2) return false;
- if (reverse1)
- {
- op1b = DupOutPt(op1, false);
- op2b = DupOutPt(op2, true);
- op1->Prev = op2;
- op2->Next = op1;
- op1b->Next = op2b;
- op2b->Prev = op1b;
- j->OutPt1 = op1;
- j->OutPt2 = op1b;
- return true;
- } else
- {
- op1b = DupOutPt(op1, true);
- op2b = DupOutPt(op2, false);
- op1->Next = op2;
- op2->Prev = op1;
- op1b->Prev = op2b;
- op2b->Next = op1b;
- j->OutPt1 = op1;
- j->OutPt2 = op1b;
- return true;
- }
- }
- else if (isHorizontal)
- {
- //treat horizontal joins differently to non-horizontal joins since with
- //them we're not yet sure where the overlapping is. OutPt1.Pt & OutPt2.Pt
- //may be anywhere along the horizontal edge.
- op1b = op1;
- while (op1->Prev->Pt.Y == op1->Pt.Y && op1->Prev != op1b && op1->Prev != op2)
- op1 = op1->Prev;
- while (op1b->Next->Pt.Y == op1b->Pt.Y && op1b->Next != op1 && op1b->Next != op2)
- op1b = op1b->Next;
- if (op1b->Next == op1 || op1b->Next == op2) return false; //a flat 'polygon'
-
- op2b = op2;
- while (op2->Prev->Pt.Y == op2->Pt.Y && op2->Prev != op2b && op2->Prev != op1b)
- op2 = op2->Prev;
- while (op2b->Next->Pt.Y == op2b->Pt.Y && op2b->Next != op2 && op2b->Next != op1)
- op2b = op2b->Next;
- if (op2b->Next == op2 || op2b->Next == op1) return false; //a flat 'polygon'
-
- cInt Left, Right;
- //Op1 --> Op1b & Op2 --> Op2b are the extremites of the horizontal edges
- if (!GetOverlap(op1->Pt.X, op1b->Pt.X, op2->Pt.X, op2b->Pt.X, Left, Right))
- return false;
-
- //DiscardLeftSide: when overlapping edges are joined, a spike will created
- //which needs to be cleaned up. However, we don't want Op1 or Op2 caught up
- //on the discard Side as either may still be needed for other joins ...
- IntPoint Pt;
- bool DiscardLeftSide;
- if (op1->Pt.X >= Left && op1->Pt.X <= Right)
- {
- Pt = op1->Pt; DiscardLeftSide = (op1->Pt.X > op1b->Pt.X);
- }
- else if (op2->Pt.X >= Left&& op2->Pt.X <= Right)
- {
- Pt = op2->Pt; DiscardLeftSide = (op2->Pt.X > op2b->Pt.X);
- }
- else if (op1b->Pt.X >= Left && op1b->Pt.X <= Right)
- {
- Pt = op1b->Pt; DiscardLeftSide = op1b->Pt.X > op1->Pt.X;
- }
- else
- {
- Pt = op2b->Pt; DiscardLeftSide = (op2b->Pt.X > op2->Pt.X);
- }
- j->OutPt1 = op1; j->OutPt2 = op2;
- return JoinHorz(op1, op1b, op2, op2b, Pt, DiscardLeftSide);
- } else
- {
- //nb: For non-horizontal joins ...
- // 1. Jr.OutPt1.Pt.Y == Jr.OutPt2.Pt.Y
- // 2. Jr.OutPt1.Pt > Jr.OffPt.Y
-
- //make sure the polygons are correctly oriented ...
- op1b = op1->Next;
- while ((op1b->Pt == op1->Pt) && (op1b != op1)) op1b = op1b->Next;
- bool Reverse1 = ((op1b->Pt.Y > op1->Pt.Y) ||
- !SlopesEqual(op1->Pt, op1b->Pt, j->OffPt, m_UseFullRange));
- if (Reverse1)
- {
- op1b = op1->Prev;
- while ((op1b->Pt == op1->Pt) && (op1b != op1)) op1b = op1b->Prev;
- if ((op1b->Pt.Y > op1->Pt.Y) ||
- !SlopesEqual(op1->Pt, op1b->Pt, j->OffPt, m_UseFullRange)) return false;
- };
- op2b = op2->Next;
- while ((op2b->Pt == op2->Pt) && (op2b != op2))op2b = op2b->Next;
- bool Reverse2 = ((op2b->Pt.Y > op2->Pt.Y) ||
- !SlopesEqual(op2->Pt, op2b->Pt, j->OffPt, m_UseFullRange));
- if (Reverse2)
- {
- op2b = op2->Prev;
- while ((op2b->Pt == op2->Pt) && (op2b != op2)) op2b = op2b->Prev;
- if ((op2b->Pt.Y > op2->Pt.Y) ||
- !SlopesEqual(op2->Pt, op2b->Pt, j->OffPt, m_UseFullRange)) return false;
- }
-
- if ((op1b == op1) || (op2b == op2) || (op1b == op2b) ||
- ((outRec1 == outRec2) && (Reverse1 == Reverse2))) return false;
-
- if (Reverse1)
- {
- op1b = DupOutPt(op1, false);
- op2b = DupOutPt(op2, true);
- op1->Prev = op2;
- op2->Next = op1;
- op1b->Next = op2b;
- op2b->Prev = op1b;
- j->OutPt1 = op1;
- j->OutPt2 = op1b;
- return true;
- } else
- {
- op1b = DupOutPt(op1, true);
- op2b = DupOutPt(op2, false);
- op1->Next = op2;
- op2->Prev = op1;
- op1b->Prev = op2b;
- op2b->Next = op1b;
- j->OutPt1 = op1;
- j->OutPt2 = op1b;
- return true;
- }
- }
-}
-//----------------------------------------------------------------------
-
-static OutRec* ParseFirstLeft(OutRec* FirstLeft)
-{
- while (FirstLeft && !FirstLeft->Pts)
- FirstLeft = FirstLeft->FirstLeft;
- return FirstLeft;
-}
-//------------------------------------------------------------------------------
-
-void Clipper::FixupFirstLefts1(OutRec* OldOutRec, OutRec* NewOutRec)
-{
- //tests if NewOutRec contains the polygon before reassigning FirstLeft
- for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i)
- {
- OutRec* outRec = m_PolyOuts[i];
- OutRec* firstLeft = ParseFirstLeft(outRec->FirstLeft);
- if (outRec->Pts && firstLeft == OldOutRec)
- {
- if (Poly2ContainsPoly1(outRec->Pts, NewOutRec->Pts))
- outRec->FirstLeft = NewOutRec;
- }
- }
-}
-//----------------------------------------------------------------------
-
-void Clipper::FixupFirstLefts2(OutRec* InnerOutRec, OutRec* OuterOutRec)
-{
- //A polygon has split into two such that one is now the inner of the other.
- //It's possible that these polygons now wrap around other polygons, so check
- //every polygon that's also contained by OuterOutRec's FirstLeft container
- //(including 0) to see if they've become inner to the new inner polygon ...
- OutRec* orfl = OuterOutRec->FirstLeft;
- for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i)
- {
- OutRec* outRec = m_PolyOuts[i];
-
- if (!outRec->Pts || outRec == OuterOutRec || outRec == InnerOutRec)
- continue;
- OutRec* firstLeft = ParseFirstLeft(outRec->FirstLeft);
- if (firstLeft != orfl && firstLeft != InnerOutRec && firstLeft != OuterOutRec)
- continue;
- if (Poly2ContainsPoly1(outRec->Pts, InnerOutRec->Pts))
- outRec->FirstLeft = InnerOutRec;
- else if (Poly2ContainsPoly1(outRec->Pts, OuterOutRec->Pts))
- outRec->FirstLeft = OuterOutRec;
- else if (outRec->FirstLeft == InnerOutRec || outRec->FirstLeft == OuterOutRec)
- outRec->FirstLeft = orfl;
- }
-}
-//----------------------------------------------------------------------
-void Clipper::FixupFirstLefts3(OutRec* OldOutRec, OutRec* NewOutRec)
-{
- //reassigns FirstLeft WITHOUT testing if NewOutRec contains the polygon
- for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i)
- {
- OutRec* outRec = m_PolyOuts[i];
- OutRec* firstLeft = ParseFirstLeft(outRec->FirstLeft);
- if (outRec->Pts && firstLeft == OldOutRec)
- outRec->FirstLeft = NewOutRec;
- }
-}
-//----------------------------------------------------------------------
-
-void Clipper::JoinCommonEdges()
-{
- for (JoinList::size_type i = 0; i < m_Joins.size(); i++)
- {
- Join* join = m_Joins[i];
-
- OutRec *outRec1 = GetOutRec(join->OutPt1->Idx);
- OutRec *outRec2 = GetOutRec(join->OutPt2->Idx);
-
- if (!outRec1->Pts || !outRec2->Pts) continue;
- if (outRec1->IsOpen || outRec2->IsOpen) continue;
-
- //get the polygon fragment with the correct hole state (FirstLeft)
- //before calling JoinPoints() ...
- OutRec *holeStateRec;
- if (outRec1 == outRec2) holeStateRec = outRec1;
- else if (OutRec1RightOfOutRec2(outRec1, outRec2)) holeStateRec = outRec2;
- else if (OutRec1RightOfOutRec2(outRec2, outRec1)) holeStateRec = outRec1;
- else holeStateRec = GetLowermostRec(outRec1, outRec2);
-
- if (!JoinPoints(join, outRec1, outRec2)) continue;
-
- if (outRec1 == outRec2)
- {
- //instead of joining two polygons, we've just created a new one by
- //splitting one polygon into two.
- outRec1->Pts = join->OutPt1;
- outRec1->BottomPt = 0;
- outRec2 = CreateOutRec();
- outRec2->Pts = join->OutPt2;
-
- //update all OutRec2.Pts Idx's ...
- UpdateOutPtIdxs(*outRec2);
-
- if (Poly2ContainsPoly1(outRec2->Pts, outRec1->Pts))
- {
- //outRec1 contains outRec2 ...
- outRec2->IsHole = !outRec1->IsHole;
- outRec2->FirstLeft = outRec1;
-
- if (m_UsingPolyTree) FixupFirstLefts2(outRec2, outRec1);
-
- if ((outRec2->IsHole ^ m_ReverseOutput) == (Area(*outRec2) > 0))
- ReversePolyPtLinks(outRec2->Pts);
-
- } else if (Poly2ContainsPoly1(outRec1->Pts, outRec2->Pts))
- {
- //outRec2 contains outRec1 ...
- outRec2->IsHole = outRec1->IsHole;
- outRec1->IsHole = !outRec2->IsHole;
- outRec2->FirstLeft = outRec1->FirstLeft;
- outRec1->FirstLeft = outRec2;
-
- if (m_UsingPolyTree) FixupFirstLefts2(outRec1, outRec2);
-
- if ((outRec1->IsHole ^ m_ReverseOutput) == (Area(*outRec1) > 0))
- ReversePolyPtLinks(outRec1->Pts);
- }
- else
- {
- //the 2 polygons are completely separate ...
- outRec2->IsHole = outRec1->IsHole;
- outRec2->FirstLeft = outRec1->FirstLeft;
-
- //fixup FirstLeft pointers that may need reassigning to OutRec2
- if (m_UsingPolyTree) FixupFirstLefts1(outRec1, outRec2);
- }
-
- } else
- {
- //joined 2 polygons together ...
-
- outRec2->Pts = 0;
- outRec2->BottomPt = 0;
- outRec2->Idx = outRec1->Idx;
-
- outRec1->IsHole = holeStateRec->IsHole;
- if (holeStateRec == outRec2)
- outRec1->FirstLeft = outRec2->FirstLeft;
- outRec2->FirstLeft = outRec1;
-
- if (m_UsingPolyTree) FixupFirstLefts3(outRec2, outRec1);
- }
- }
-}
-
-//------------------------------------------------------------------------------
-// ClipperOffset support functions ...
-//------------------------------------------------------------------------------
-
-DoublePoint GetUnitNormal(const IntPoint &pt1, const IntPoint &pt2)
-{
- if(pt2.X == pt1.X && pt2.Y == pt1.Y)
- return DoublePoint(0, 0);
-
- double Dx = (double)(pt2.X - pt1.X);
- double dy = (double)(pt2.Y - pt1.Y);
- double f = 1 *1.0/ std::sqrt( Dx*Dx + dy*dy );
- Dx *= f;
- dy *= f;
- return DoublePoint(dy, -Dx);
-}
-
-//------------------------------------------------------------------------------
-// ClipperOffset class
-//------------------------------------------------------------------------------
-
-ClipperOffset::ClipperOffset(double miterLimit, double arcTolerance)
-{
- this->MiterLimit = miterLimit;
- this->ArcTolerance = arcTolerance;
- m_lowest.X = -1;
-}
-//------------------------------------------------------------------------------
-
-ClipperOffset::~ClipperOffset()
-{
- Clear();
-}
-//------------------------------------------------------------------------------
-
-void ClipperOffset::Clear()
-{
- for (int i = 0; i < m_polyNodes.ChildCount(); ++i)
- delete m_polyNodes.Childs[i];
- m_polyNodes.Childs.clear();
- m_lowest.X = -1;
-}
-//------------------------------------------------------------------------------
-
-void ClipperOffset::AddPath(const Path& path, JoinType joinType, EndType endType)
-{
- int highI = (int)path.size() - 1;
- if (highI < 0) return;
- PolyNode* newNode = new PolyNode();
- newNode->m_jointype = joinType;
- newNode->m_endtype = endType;
-
- //strip duplicate points from path and also get index to the lowest point ...
- if (endType == etClosedLine || endType == etClosedPolygon)
- while (highI > 0 && path[0] == path[highI]) highI--;
- newNode->Contour.reserve(highI + 1);
- newNode->Contour.push_back(path[0]);
- int j = 0, k = 0;
- for (int i = 1; i <= highI; i++)
- if (newNode->Contour[j] != path[i])
- {
- j++;
- newNode->Contour.push_back(path[i]);
- if (path[i].Y > newNode->Contour[k].Y ||
- (path[i].Y == newNode->Contour[k].Y &&
- path[i].X < newNode->Contour[k].X)) k = j;
- }
- if (endType == etClosedPolygon && j < 2)
- {
- delete newNode;
- return;
- }
- m_polyNodes.AddChild(*newNode);
-
- //if this path's lowest pt is lower than all the others then update m_lowest
- if (endType != etClosedPolygon) return;
- if (m_lowest.X < 0)
- m_lowest = IntPoint(m_polyNodes.ChildCount() - 1, k);
- else
- {
- IntPoint ip = m_polyNodes.Childs[(int)m_lowest.X]->Contour[(int)m_lowest.Y];
- if (newNode->Contour[k].Y > ip.Y ||
- (newNode->Contour[k].Y == ip.Y &&
- newNode->Contour[k].X < ip.X))
- m_lowest = IntPoint(m_polyNodes.ChildCount() - 1, k);
- }
-}
-//------------------------------------------------------------------------------
-
-void ClipperOffset::AddPaths(const Paths& paths, JoinType joinType, EndType endType)
-{
- for (Paths::size_type i = 0; i < paths.size(); ++i)
- AddPath(paths[i], joinType, endType);
-}
-//------------------------------------------------------------------------------
-
-void ClipperOffset::FixOrientations()
-{
- //fixup orientations of all closed paths if the orientation of the
- //closed path with the lowermost vertex is wrong ...
- if (m_lowest.X >= 0 &&
- !Orientation(m_polyNodes.Childs[(int)m_lowest.X]->Contour))
- {
- for (int i = 0; i < m_polyNodes.ChildCount(); ++i)
- {
- PolyNode& node = *m_polyNodes.Childs[i];
- if (node.m_endtype == etClosedPolygon ||
- (node.m_endtype == etClosedLine && Orientation(node.Contour)))
- ReversePath(node.Contour);
- }
- } else
- {
- for (int i = 0; i < m_polyNodes.ChildCount(); ++i)
- {
- PolyNode& node = *m_polyNodes.Childs[i];
- if (node.m_endtype == etClosedLine && !Orientation(node.Contour))
- ReversePath(node.Contour);
- }
- }
-}
-//------------------------------------------------------------------------------
-
-void ClipperOffset::Execute(Paths& solution, double delta)
-{
- solution.clear();
- FixOrientations();
- DoOffset(delta);
-
- //now clean up 'corners' ...
- Clipper clpr;
- clpr.AddPaths(m_destPolys, ptSubject, true);
- if (delta > 0)
- {
- clpr.Execute(ctUnion, solution, pftPositive, pftPositive);
- }
- else
- {
- IntRect r = clpr.GetBounds();
- Path outer(4);
- outer[0] = IntPoint(r.left - 10, r.bottom + 10);
- outer[1] = IntPoint(r.right + 10, r.bottom + 10);
- outer[2] = IntPoint(r.right + 10, r.top - 10);
- outer[3] = IntPoint(r.left - 10, r.top - 10);
-
- clpr.AddPath(outer, ptSubject, true);
- clpr.ReverseSolution(true);
- clpr.Execute(ctUnion, solution, pftNegative, pftNegative);
- if (solution.size() > 0) solution.erase(solution.begin());
- }
-}
-//------------------------------------------------------------------------------
-
-void ClipperOffset::Execute(PolyTree& solution, double delta)
-{
- solution.Clear();
- FixOrientations();
- DoOffset(delta);
-
- //now clean up 'corners' ...
- Clipper clpr;
- clpr.AddPaths(m_destPolys, ptSubject, true);
- if (delta > 0)
- {
- clpr.Execute(ctUnion, solution, pftPositive, pftPositive);
- }
- else
- {
- IntRect r = clpr.GetBounds();
- Path outer(4);
- outer[0] = IntPoint(r.left - 10, r.bottom + 10);
- outer[1] = IntPoint(r.right + 10, r.bottom + 10);
- outer[2] = IntPoint(r.right + 10, r.top - 10);
- outer[3] = IntPoint(r.left - 10, r.top - 10);
-
- clpr.AddPath(outer, ptSubject, true);
- clpr.ReverseSolution(true);
- clpr.Execute(ctUnion, solution, pftNegative, pftNegative);
- //remove the outer PolyNode rectangle ...
- if (solution.ChildCount() == 1 && solution.Childs[0]->ChildCount() > 0)
- {
- PolyNode* outerNode = solution.Childs[0];
- solution.Childs.reserve(outerNode->ChildCount());
- solution.Childs[0] = outerNode->Childs[0];
- solution.Childs[0]->Parent = outerNode->Parent;
- for (int i = 1; i < outerNode->ChildCount(); ++i)
- solution.AddChild(*outerNode->Childs[i]);
- }
- else
- solution.Clear();
- }
-}
-//------------------------------------------------------------------------------
-
-void ClipperOffset::DoOffset(double delta)
-{
- m_destPolys.clear();
- m_delta = delta;
-
- //if Zero offset, just copy any CLOSED polygons to m_p and return ...
- if (NEAR_ZERO(delta))
- {
- m_destPolys.reserve(m_polyNodes.ChildCount());
- for (int i = 0; i < m_polyNodes.ChildCount(); i++)
- {
- PolyNode& node = *m_polyNodes.Childs[i];
- if (node.m_endtype == etClosedPolygon)
- m_destPolys.push_back(node.Contour);
- }
- return;
- }
-
- //see offset_triginometry3.svg in the documentation folder ...
- if (MiterLimit > 2) m_miterLim = 2/(MiterLimit * MiterLimit);
- else m_miterLim = 0.5;
-
- double y;
- if (ArcTolerance <= 0.0) y = def_arc_tolerance;
- else if (ArcTolerance > std::fabs(delta) * def_arc_tolerance)
- y = std::fabs(delta) * def_arc_tolerance;
- else y = ArcTolerance;
- //see offset_triginometry2.svg in the documentation folder ...
- double steps = pi / std::acos(1 - y / std::fabs(delta));
- if (steps > std::fabs(delta) * pi)
- steps = std::fabs(delta) * pi; //ie excessive precision check
- m_sin = std::sin(two_pi / steps);
- m_cos = std::cos(two_pi / steps);
- m_StepsPerRad = steps / two_pi;
- if (delta < 0.0) m_sin = -m_sin;
-
- m_destPolys.reserve(m_polyNodes.ChildCount() * 2);
- for (int i = 0; i < m_polyNodes.ChildCount(); i++)
- {
- PolyNode& node = *m_polyNodes.Childs[i];
- m_srcPoly = node.Contour;
-
- int len = (int)m_srcPoly.size();
- if (len == 0 || (delta <= 0 && (len < 3 || node.m_endtype != etClosedPolygon)))
- continue;
-
- m_destPoly.clear();
- if (len == 1)
- {
- if (node.m_jointype == jtRound)
- {
- double X = 1.0, Y = 0.0;
- for (cInt j = 1; j <= steps; j++)
- {
- m_destPoly.push_back(IntPoint(
- Round(m_srcPoly[0].X + X * delta),
- Round(m_srcPoly[0].Y + Y * delta)));
- double X2 = X;
- X = X * m_cos - m_sin * Y;
- Y = X2 * m_sin + Y * m_cos;
- }
- }
- else
- {
- double X = -1.0, Y = -1.0;
- for (int j = 0; j < 4; ++j)
- {
- m_destPoly.push_back(IntPoint(
- Round(m_srcPoly[0].X + X * delta),
- Round(m_srcPoly[0].Y + Y * delta)));
- if (X < 0) X = 1;
- else if (Y < 0) Y = 1;
- else X = -1;
- }
- }
- m_destPolys.push_back(m_destPoly);
- continue;
- }
- //build m_normals ...
- m_normals.clear();
- m_normals.reserve(len);
- for (int j = 0; j < len - 1; ++j)
- m_normals.push_back(GetUnitNormal(m_srcPoly[j], m_srcPoly[j + 1]));
- if (node.m_endtype == etClosedLine || node.m_endtype == etClosedPolygon)
- m_normals.push_back(GetUnitNormal(m_srcPoly[len - 1], m_srcPoly[0]));
- else
- m_normals.push_back(DoublePoint(m_normals[len - 2]));
-
- if (node.m_endtype == etClosedPolygon)
- {
- int k = len - 1;
- for (int j = 0; j < len; ++j)
- OffsetPoint(j, k, node.m_jointype);
- m_destPolys.push_back(m_destPoly);
- }
- else if (node.m_endtype == etClosedLine)
- {
- int k = len - 1;
- for (int j = 0; j < len; ++j)
- OffsetPoint(j, k, node.m_jointype);
- m_destPolys.push_back(m_destPoly);
- m_destPoly.clear();
- //re-build m_normals ...
- DoublePoint n = m_normals[len -1];
- for (int j = len - 1; j > 0; j--)
- m_normals[j] = DoublePoint(-m_normals[j - 1].X, -m_normals[j - 1].Y);
- m_normals[0] = DoublePoint(-n.X, -n.Y);
- k = 0;
- for (int j = len - 1; j >= 0; j--)
- OffsetPoint(j, k, node.m_jointype);
- m_destPolys.push_back(m_destPoly);
- }
- else
- {
- int k = 0;
- for (int j = 1; j < len - 1; ++j)
- OffsetPoint(j, k, node.m_jointype);
-
- IntPoint pt1;
- if (node.m_endtype == etOpenButt)
- {
- int j = len - 1;
- pt1 = IntPoint((cInt)Round(m_srcPoly[j].X + m_normals[j].X *
- delta), (cInt)Round(m_srcPoly[j].Y + m_normals[j].Y * delta));
- m_destPoly.push_back(pt1);
- pt1 = IntPoint((cInt)Round(m_srcPoly[j].X - m_normals[j].X *
- delta), (cInt)Round(m_srcPoly[j].Y - m_normals[j].Y * delta));
- m_destPoly.push_back(pt1);
- }
- else
- {
- int j = len - 1;
- k = len - 2;
- m_sinA = 0;
- m_normals[j] = DoublePoint(-m_normals[j].X, -m_normals[j].Y);
- if (node.m_endtype == etOpenSquare)
- DoSquare(j, k);
- else
- DoRound(j, k);
- }
-
- //re-build m_normals ...
- for (int j = len - 1; j > 0; j--)
- m_normals[j] = DoublePoint(-m_normals[j - 1].X, -m_normals[j - 1].Y);
- m_normals[0] = DoublePoint(-m_normals[1].X, -m_normals[1].Y);
-
- k = len - 1;
- for (int j = k - 1; j > 0; --j) OffsetPoint(j, k, node.m_jointype);
-
- if (node.m_endtype == etOpenButt)
- {
- pt1 = IntPoint((cInt)Round(m_srcPoly[0].X - m_normals[0].X * delta),
- (cInt)Round(m_srcPoly[0].Y - m_normals[0].Y * delta));
- m_destPoly.push_back(pt1);
- pt1 = IntPoint((cInt)Round(m_srcPoly[0].X + m_normals[0].X * delta),
- (cInt)Round(m_srcPoly[0].Y + m_normals[0].Y * delta));
- m_destPoly.push_back(pt1);
- }
- else
- {
- k = 1;
- m_sinA = 0;
- if (node.m_endtype == etOpenSquare)
- DoSquare(0, 1);
- else
- DoRound(0, 1);
- }
- m_destPolys.push_back(m_destPoly);
- }
- }
-}
-//------------------------------------------------------------------------------
-
-void ClipperOffset::OffsetPoint(int j, int& k, JoinType jointype)
-{
- //cross product ...
- m_sinA = (m_normals[k].X * m_normals[j].Y - m_normals[j].X * m_normals[k].Y);
- if (std::fabs(m_sinA * m_delta) < 1.0)
- {
- //dot product ...
- double cosA = (m_normals[k].X * m_normals[j].X + m_normals[j].Y * m_normals[k].Y );
- if (cosA > 0) // angle => 0 degrees
- {
- m_destPoly.push_back(IntPoint(Round(m_srcPoly[j].X + m_normals[k].X * m_delta),
- Round(m_srcPoly[j].Y + m_normals[k].Y * m_delta)));
- return;
- }
- //else angle => 180 degrees
- }
- else if (m_sinA > 1.0) m_sinA = 1.0;
- else if (m_sinA < -1.0) m_sinA = -1.0;
-
- if (m_sinA * m_delta < 0)
- {
- m_destPoly.push_back(IntPoint(Round(m_srcPoly[j].X + m_normals[k].X * m_delta),
- Round(m_srcPoly[j].Y + m_normals[k].Y * m_delta)));
- m_destPoly.push_back(m_srcPoly[j]);
- m_destPoly.push_back(IntPoint(Round(m_srcPoly[j].X + m_normals[j].X * m_delta),
- Round(m_srcPoly[j].Y + m_normals[j].Y * m_delta)));
- }
- else
- switch (jointype)
- {
- case jtMiter:
- {
- double r = 1 + (m_normals[j].X * m_normals[k].X +
- m_normals[j].Y * m_normals[k].Y);
- if (r >= m_miterLim) DoMiter(j, k, r); else DoSquare(j, k);
- break;
- }
- case jtSquare: DoSquare(j, k); break;
- case jtRound: DoRound(j, k); break;
- }
- k = j;
-}
-//------------------------------------------------------------------------------
-
-void ClipperOffset::DoSquare(int j, int k)
-{
- double dx = std::tan(std::atan2(m_sinA,
- m_normals[k].X * m_normals[j].X + m_normals[k].Y * m_normals[j].Y) / 4);
- m_destPoly.push_back(IntPoint(
- Round(m_srcPoly[j].X + m_delta * (m_normals[k].X - m_normals[k].Y * dx)),
- Round(m_srcPoly[j].Y + m_delta * (m_normals[k].Y + m_normals[k].X * dx))));
- m_destPoly.push_back(IntPoint(
- Round(m_srcPoly[j].X + m_delta * (m_normals[j].X + m_normals[j].Y * dx)),
- Round(m_srcPoly[j].Y + m_delta * (m_normals[j].Y - m_normals[j].X * dx))));
-}
-//------------------------------------------------------------------------------
-
-void ClipperOffset::DoMiter(int j, int k, double r)
-{
- double q = m_delta / r;
- m_destPoly.push_back(IntPoint(Round(m_srcPoly[j].X + (m_normals[k].X + m_normals[j].X) * q),
- Round(m_srcPoly[j].Y + (m_normals[k].Y + m_normals[j].Y) * q)));
-}
-//------------------------------------------------------------------------------
-
-void ClipperOffset::DoRound(int j, int k)
-{
- double a = std::atan2(m_sinA,
- m_normals[k].X * m_normals[j].X + m_normals[k].Y * m_normals[j].Y);
- int steps = std::max((int)Round(m_StepsPerRad * std::fabs(a)), 1);
-
- double X = m_normals[k].X, Y = m_normals[k].Y, X2;
- for (int i = 0; i < steps; ++i)
- {
- m_destPoly.push_back(IntPoint(
- Round(m_srcPoly[j].X + X * m_delta),
- Round(m_srcPoly[j].Y + Y * m_delta)));
- X2 = X;
- X = X * m_cos - m_sin * Y;
- Y = X2 * m_sin + Y * m_cos;
- }
- m_destPoly.push_back(IntPoint(
- Round(m_srcPoly[j].X + m_normals[j].X * m_delta),
- Round(m_srcPoly[j].Y + m_normals[j].Y * m_delta)));
-}
-
-//------------------------------------------------------------------------------
-// Miscellaneous public functions
-//------------------------------------------------------------------------------
-
-void Clipper::DoSimplePolygons()
-{
- PolyOutList::size_type i = 0;
- while (i < m_PolyOuts.size())
- {
- OutRec* outrec = m_PolyOuts[i++];
- OutPt* op = outrec->Pts;
- if (!op || outrec->IsOpen) continue;
- do //for each Pt in Polygon until duplicate found do ...
- {
- OutPt* op2 = op->Next;
- while (op2 != outrec->Pts)
- {
- if ((op->Pt == op2->Pt) && op2->Next != op && op2->Prev != op)
- {
- //split the polygon into two ...
- OutPt* op3 = op->Prev;
- OutPt* op4 = op2->Prev;
- op->Prev = op4;
- op4->Next = op;
- op2->Prev = op3;
- op3->Next = op2;
-
- outrec->Pts = op;
- OutRec* outrec2 = CreateOutRec();
- outrec2->Pts = op2;
- UpdateOutPtIdxs(*outrec2);
- if (Poly2ContainsPoly1(outrec2->Pts, outrec->Pts))
- {
- //OutRec2 is contained by OutRec1 ...
- outrec2->IsHole = !outrec->IsHole;
- outrec2->FirstLeft = outrec;
- if (m_UsingPolyTree) FixupFirstLefts2(outrec2, outrec);
- }
- else
- if (Poly2ContainsPoly1(outrec->Pts, outrec2->Pts))
- {
- //OutRec1 is contained by OutRec2 ...
- outrec2->IsHole = outrec->IsHole;
- outrec->IsHole = !outrec2->IsHole;
- outrec2->FirstLeft = outrec->FirstLeft;
- outrec->FirstLeft = outrec2;
- if (m_UsingPolyTree) FixupFirstLefts2(outrec, outrec2);
- }
- else
- {
- //the 2 polygons are separate ...
- outrec2->IsHole = outrec->IsHole;
- outrec2->FirstLeft = outrec->FirstLeft;
- if (m_UsingPolyTree) FixupFirstLefts1(outrec, outrec2);
- }
- op2 = op; //ie get ready for the Next iteration
- }
- op2 = op2->Next;
- }
- op = op->Next;
- }
- while (op != outrec->Pts);
- }
-}
-//------------------------------------------------------------------------------
-
-void ReversePath(Path& p)
-{
- std::reverse(p.begin(), p.end());
-}
-//------------------------------------------------------------------------------
-
-void ReversePaths(Paths& p)
-{
- for (Paths::size_type i = 0; i < p.size(); ++i)
- ReversePath(p[i]);
-}
-//------------------------------------------------------------------------------
-
-void SimplifyPolygon(const Path &in_poly, Paths &out_polys, PolyFillType fillType)
-{
- Clipper c;
- c.StrictlySimple(true);
- c.AddPath(in_poly, ptSubject, true);
- c.Execute(ctUnion, out_polys, fillType, fillType);
-}
-//------------------------------------------------------------------------------
-
-void SimplifyPolygons(const Paths &in_polys, Paths &out_polys, PolyFillType fillType)
-{
- Clipper c;
- c.StrictlySimple(true);
- c.AddPaths(in_polys, ptSubject, true);
- c.Execute(ctUnion, out_polys, fillType, fillType);
-}
-//------------------------------------------------------------------------------
-
-void SimplifyPolygons(Paths &polys, PolyFillType fillType)
-{
- SimplifyPolygons(polys, polys, fillType);
-}
-//------------------------------------------------------------------------------
-
-inline double DistanceSqrd(const IntPoint& pt1, const IntPoint& pt2)
-{
- double Dx = ((double)pt1.X - pt2.X);
- double dy = ((double)pt1.Y - pt2.Y);
- return (Dx*Dx + dy*dy);
-}
-//------------------------------------------------------------------------------
-
-double DistanceFromLineSqrd(
- const IntPoint& pt, const IntPoint& ln1, const IntPoint& ln2)
-{
- //The equation of a line in general form (Ax + By + C = 0)
- //given 2 points (x¹,y¹) & (x²,y²) is ...
- //(y¹ - y²)x + (x² - x¹)y + (y² - y¹)x¹ - (x² - x¹)y¹ = 0
- //A = (y¹ - y²); B = (x² - x¹); C = (y² - y¹)x¹ - (x² - x¹)y¹
- //perpendicular distance of point (x³,y³) = (Ax³ + By³ + C)/Sqrt(A² + B²)
- //see http://en.wikipedia.org/wiki/Perpendicular_distance
- double A = double(ln1.Y - ln2.Y);
- double B = double(ln2.X - ln1.X);
- double C = A * ln1.X + B * ln1.Y;
- C = A * pt.X + B * pt.Y - C;
- return (C * C) / (A * A + B * B);
-}
-//---------------------------------------------------------------------------
-
-bool SlopesNearCollinear(const IntPoint& pt1,
- const IntPoint& pt2, const IntPoint& pt3, double distSqrd)
-{
- //this function is more accurate when the point that's geometrically
- //between the other 2 points is the one that's tested for distance.
- //ie makes it more likely to pick up 'spikes' ...
- if (Abs(pt1.X - pt2.X) > Abs(pt1.Y - pt2.Y))
- {
- if ((pt1.X > pt2.X) == (pt1.X < pt3.X))
- return DistanceFromLineSqrd(pt1, pt2, pt3) < distSqrd;
- else if ((pt2.X > pt1.X) == (pt2.X < pt3.X))
- return DistanceFromLineSqrd(pt2, pt1, pt3) < distSqrd;
- else
- return DistanceFromLineSqrd(pt3, pt1, pt2) < distSqrd;
- }
- else
- {
- if ((pt1.Y > pt2.Y) == (pt1.Y < pt3.Y))
- return DistanceFromLineSqrd(pt1, pt2, pt3) < distSqrd;
- else if ((pt2.Y > pt1.Y) == (pt2.Y < pt3.Y))
- return DistanceFromLineSqrd(pt2, pt1, pt3) < distSqrd;
- else
- return DistanceFromLineSqrd(pt3, pt1, pt2) < distSqrd;
- }
-}
-//------------------------------------------------------------------------------
-
-bool PointsAreClose(IntPoint pt1, IntPoint pt2, double distSqrd)
-{
- double Dx = (double)pt1.X - pt2.X;
- double dy = (double)pt1.Y - pt2.Y;
- return ((Dx * Dx) + (dy * dy) <= distSqrd);
-}
-//------------------------------------------------------------------------------
-
-OutPt* ExcludeOp(OutPt* op)
-{
- OutPt* result = op->Prev;
- result->Next = op->Next;
- op->Next->Prev = result;
- result->Idx = 0;
- return result;
-}
-//------------------------------------------------------------------------------
-
-void CleanPolygon(const Path& in_poly, Path& out_poly, double distance)
-{
- //distance = proximity in units/pixels below which vertices
- //will be stripped. Default ~= sqrt(2).
-
- size_t size = in_poly.size();
-
- if (size == 0)
- {
- out_poly.clear();
- return;
- }
-
- OutPt* outPts = new OutPt[size];
- for (size_t i = 0; i < size; ++i)
- {
- outPts[i].Pt = in_poly[i];
- outPts[i].Next = &outPts[(i + 1) % size];
- outPts[i].Next->Prev = &outPts[i];
- outPts[i].Idx = 0;
- }
-
- double distSqrd = distance * distance;
- OutPt* op = &outPts[0];
- while (op->Idx == 0 && op->Next != op->Prev)
- {
- if (PointsAreClose(op->Pt, op->Prev->Pt, distSqrd))
- {
- op = ExcludeOp(op);
- size--;
- }
- else if (PointsAreClose(op->Prev->Pt, op->Next->Pt, distSqrd))
- {
- ExcludeOp(op->Next);
- op = ExcludeOp(op);
- size -= 2;
- }
- else if (SlopesNearCollinear(op->Prev->Pt, op->Pt, op->Next->Pt, distSqrd))
- {
- op = ExcludeOp(op);
- size--;
- }
- else
- {
- op->Idx = 1;
- op = op->Next;
- }
- }
-
- if (size < 3) size = 0;
- out_poly.resize(size);
- for (size_t i = 0; i < size; ++i)
- {
- out_poly[i] = op->Pt;
- op = op->Next;
- }
- delete [] outPts;
-}
-//------------------------------------------------------------------------------
-
-void CleanPolygon(Path& poly, double distance)
-{
- CleanPolygon(poly, poly, distance);
-}
-//------------------------------------------------------------------------------
-
-void CleanPolygons(const Paths& in_polys, Paths& out_polys, double distance)
-{
- out_polys.resize(in_polys.size());
- for (Paths::size_type i = 0; i < in_polys.size(); ++i)
- CleanPolygon(in_polys[i], out_polys[i], distance);
-}
-//------------------------------------------------------------------------------
-
-void CleanPolygons(Paths& polys, double distance)
-{
- CleanPolygons(polys, polys, distance);
-}
-//------------------------------------------------------------------------------
-
-void Minkowski(const Path& poly, const Path& path,
- Paths& solution, bool isSum, bool isClosed)
-{
- int delta = (isClosed ? 1 : 0);
- size_t polyCnt = poly.size();
- size_t pathCnt = path.size();
- Paths pp;
- pp.reserve(pathCnt);
- if (isSum)
- for (size_t i = 0; i < pathCnt; ++i)
- {
- Path p;
- p.reserve(polyCnt);
- for (size_t j = 0; j < poly.size(); ++j)
- p.push_back(IntPoint(path[i].X + poly[j].X, path[i].Y + poly[j].Y));
- pp.push_back(p);
- }
- else
- for (size_t i = 0; i < pathCnt; ++i)
- {
- Path p;
- p.reserve(polyCnt);
- for (size_t j = 0; j < poly.size(); ++j)
- p.push_back(IntPoint(path[i].X - poly[j].X, path[i].Y - poly[j].Y));
- pp.push_back(p);
- }
-
- solution.clear();
- solution.reserve((pathCnt + delta) * (polyCnt + 1));
- for (size_t i = 0; i < pathCnt - 1 + delta; ++i)
- for (size_t j = 0; j < polyCnt; ++j)
- {
- Path quad;
- quad.reserve(4);
- quad.push_back(pp[i % pathCnt][j % polyCnt]);
- quad.push_back(pp[(i + 1) % pathCnt][j % polyCnt]);
- quad.push_back(pp[(i + 1) % pathCnt][(j + 1) % polyCnt]);
- quad.push_back(pp[i % pathCnt][(j + 1) % polyCnt]);
- if (!Orientation(quad)) ReversePath(quad);
- solution.push_back(quad);
- }
-}
-//------------------------------------------------------------------------------
-
-void MinkowskiSum(const Path& pattern, const Path& path, Paths& solution, bool pathIsClosed)
-{
- Minkowski(pattern, path, solution, true, pathIsClosed);
- Clipper c;
- c.AddPaths(solution, ptSubject, true);
- c.Execute(ctUnion, solution, pftNonZero, pftNonZero);
-}
-//------------------------------------------------------------------------------
-
-void TranslatePath(const Path& input, Path& output, const IntPoint delta)
-{
- //precondition: input != output
- output.resize(input.size());
- for (size_t i = 0; i < input.size(); ++i)
- output[i] = IntPoint(input[i].X + delta.X, input[i].Y + delta.Y);
-}
-//------------------------------------------------------------------------------
-
-void MinkowskiSum(const Path& pattern, const Paths& paths, Paths& solution, bool pathIsClosed)
-{
- Clipper c;
- for (size_t i = 0; i < paths.size(); ++i)
- {
- Paths tmp;
- Minkowski(pattern, paths[i], tmp, true, pathIsClosed);
- c.AddPaths(tmp, ptSubject, true);
- if (pathIsClosed)
- {
- Path tmp2;
- TranslatePath(paths[i], tmp2, pattern[0]);
- c.AddPath(tmp2, ptClip, true);
- }
- }
- c.Execute(ctUnion, solution, pftNonZero, pftNonZero);
-}
-//------------------------------------------------------------------------------
-
-void MinkowskiDiff(const Path& poly1, const Path& poly2, Paths& solution)
-{
- Minkowski(poly1, poly2, solution, false, true);
- Clipper c;
- c.AddPaths(solution, ptSubject, true);
- c.Execute(ctUnion, solution, pftNonZero, pftNonZero);
-}
-//------------------------------------------------------------------------------
-
-enum NodeType {ntAny, ntOpen, ntClosed};
-
-void AddPolyNodeToPaths(const PolyNode& polynode, NodeType nodetype, Paths& paths)
-{
- bool match = true;
- if (nodetype == ntClosed) match = !polynode.IsOpen();
- else if (nodetype == ntOpen) return;
-
- if (!polynode.Contour.empty() && match)
- paths.push_back(polynode.Contour);
- for (int i = 0; i < polynode.ChildCount(); ++i)
- AddPolyNodeToPaths(*polynode.Childs[i], nodetype, paths);
-}
-//------------------------------------------------------------------------------
-
-void PolyTreeToPaths(const PolyTree& polytree, Paths& paths)
-{
- paths.resize(0);
- paths.reserve(polytree.Total());
- AddPolyNodeToPaths(polytree, ntAny, paths);
-}
-//------------------------------------------------------------------------------
-
-void ClosedPathsFromPolyTree(const PolyTree& polytree, Paths& paths)
-{
- paths.resize(0);
- paths.reserve(polytree.Total());
- AddPolyNodeToPaths(polytree, ntClosed, paths);
-}
-//------------------------------------------------------------------------------
-
-void OpenPathsFromPolyTree(PolyTree& polytree, Paths& paths)
-{
- paths.resize(0);
- paths.reserve(polytree.Total());
- //Open paths are top level only, so ...
- for (int i = 0; i < polytree.ChildCount(); ++i)
- if (polytree.Childs[i]->IsOpen())
- paths.push_back(polytree.Childs[i]->Contour);
-}
-//------------------------------------------------------------------------------
-
-std::ostream& operator <<(std::ostream &s, const IntPoint &p)
-{
- s << "(" << p.X << "," << p.Y << ")";
- return s;
-}
-//------------------------------------------------------------------------------
-
-std::ostream& operator <<(std::ostream &s, const Path &p)
-{
- if (p.empty()) return s;
- Path::size_type last = p.size() -1;
- for (Path::size_type i = 0; i < last; i++)
- s << "(" << p[i].X << "," << p[i].Y << "), ";
- s << "(" << p[last].X << "," << p[last].Y << ")\n";
- return s;
-}
-//------------------------------------------------------------------------------
-
-std::ostream& operator <<(std::ostream &s, const Paths &p)
-{
- for (Paths::size_type i = 0; i < p.size(); i++)
- s << p[i];
- s << "\n";
- return s;
-}
-//------------------------------------------------------------------------------
-
-} //ClipperLib namespace
diff --git a/thirdparty/misc/clipper.hpp b/thirdparty/misc/clipper.hpp
deleted file mode 100644
index 5a19617bb4..0000000000
--- a/thirdparty/misc/clipper.hpp
+++ /dev/null
@@ -1,406 +0,0 @@
-/*******************************************************************************
-* *
-* Author : Angus Johnson *
-* Version : 6.4.2 *
-* Date : 27 February 2017 *
-* Website : http://www.angusj.com *
-* Copyright : Angus Johnson 2010-2017 *
-* *
-* License: *
-* Use, modification & distribution is subject to Boost Software License Ver 1. *
-* http://www.boost.org/LICENSE_1_0.txt *
-* *
-* Attributions: *
-* The code in this library is an extension of Bala Vatti's clipping algorithm: *
-* "A generic solution to polygon clipping" *
-* Communications of the ACM, Vol 35, Issue 7 (July 1992) pp 56-63. *
-* http://portal.acm.org/citation.cfm?id=129906 *
-* *
-* Computer graphics and geometric modeling: implementation and algorithms *
-* By Max K. Agoston *
-* Springer; 1 edition (January 4, 2005) *
-* http://books.google.com/books?q=vatti+clipping+agoston *
-* *
-* See also: *
-* "Polygon Offsetting by Computing Winding Numbers" *
-* Paper no. DETC2005-85513 pp. 565-575 *
-* ASME 2005 International Design Engineering Technical Conferences *
-* and Computers and Information in Engineering Conference (IDETC/CIE2005) *
-* September 24-28, 2005 , Long Beach, California, USA *
-* http://www.me.berkeley.edu/~mcmains/pubs/DAC05OffsetPolygon.pdf *
-* *
-*******************************************************************************/
-
-#ifndef clipper_hpp
-#define clipper_hpp
-
-#define CLIPPER_VERSION "6.4.2"
-
-//use_int32: When enabled 32bit ints are used instead of 64bit ints. This
-//improve performance but coordinate values are limited to the range +/- 46340
-//#define use_int32
-
-//use_xyz: adds a Z member to IntPoint. Adds a minor cost to perfomance.
-//#define use_xyz
-
-//use_lines: Enables line clipping. Adds a very minor cost to performance.
-#define use_lines
-
-//use_deprecated: Enables temporary support for the obsolete functions
-//#define use_deprecated
-
-#include <vector>
-#include <list>
-#include <set>
-#include <stdexcept>
-#include <cstring>
-#include <cstdlib>
-#include <ostream>
-#include <functional>
-#include <queue>
-
-namespace ClipperLib {
-
-enum ClipType { ctIntersection, ctUnion, ctDifference, ctXor };
-enum PolyType { ptSubject, ptClip };
-//By far the most widely used winding rules for polygon filling are
-//EvenOdd & NonZero (GDI, GDI+, XLib, OpenGL, Cairo, AGG, Quartz, SVG, Gr32)
-//Others rules include Positive, Negative and ABS_GTR_EQ_TWO (only in OpenGL)
-//see http://glprogramming.com/red/chapter11.html
-enum PolyFillType { pftEvenOdd, pftNonZero, pftPositive, pftNegative };
-
-#ifdef use_int32
- typedef int cInt;
- static cInt const loRange = 0x7FFF;
- static cInt const hiRange = 0x7FFF;
-#else
- typedef signed long long cInt;
- static cInt const loRange = 0x3FFFFFFF;
- static cInt const hiRange = 0x3FFFFFFFFFFFFFFFLL;
- typedef signed long long long64; //used by Int128 class
- typedef unsigned long long ulong64;
-
-#endif
-
-struct IntPoint {
- cInt X;
- cInt Y;
-#ifdef use_xyz
- cInt Z;
- IntPoint(cInt x = 0, cInt y = 0, cInt z = 0): X(x), Y(y), Z(z) {};
-#else
- IntPoint(cInt x = 0, cInt y = 0): X(x), Y(y) {};
-#endif
-
- friend inline bool operator== (const IntPoint& a, const IntPoint& b)
- {
- return a.X == b.X && a.Y == b.Y;
- }
- friend inline bool operator!= (const IntPoint& a, const IntPoint& b)
- {
- return a.X != b.X || a.Y != b.Y;
- }
-};
-//------------------------------------------------------------------------------
-
-typedef std::vector< IntPoint > Path;
-typedef std::vector< Path > Paths;
-
-inline Path& operator <<(Path& poly, const IntPoint& p) {poly.push_back(p); return poly;}
-inline Paths& operator <<(Paths& polys, const Path& p) {polys.push_back(p); return polys;}
-
-std::ostream& operator <<(std::ostream &s, const IntPoint &p);
-std::ostream& operator <<(std::ostream &s, const Path &p);
-std::ostream& operator <<(std::ostream &s, const Paths &p);
-
-struct DoublePoint
-{
- double X;
- double Y;
- DoublePoint(double x = 0, double y = 0) : X(x), Y(y) {}
- DoublePoint(IntPoint ip) : X((double)ip.X), Y((double)ip.Y) {}
-};
-//------------------------------------------------------------------------------
-
-#ifdef use_xyz
-typedef void (*ZFillCallback)(IntPoint& e1bot, IntPoint& e1top, IntPoint& e2bot, IntPoint& e2top, IntPoint& pt);
-#endif
-
-enum InitOptions {ioReverseSolution = 1, ioStrictlySimple = 2, ioPreserveCollinear = 4};
-enum JoinType {jtSquare, jtRound, jtMiter};
-enum EndType {etClosedPolygon, etClosedLine, etOpenButt, etOpenSquare, etOpenRound};
-
-class PolyNode;
-typedef std::vector< PolyNode* > PolyNodes;
-
-class PolyNode
-{
-public:
- PolyNode();
- virtual ~PolyNode(){};
- Path Contour;
- PolyNodes Childs;
- PolyNode* Parent;
- PolyNode* GetNext() const;
- bool IsHole() const;
- bool IsOpen() const;
- int ChildCount() const;
-private:
- //PolyNode& operator =(PolyNode& other);
- unsigned Index; //node index in Parent.Childs
- bool m_IsOpen;
- JoinType m_jointype;
- EndType m_endtype;
- PolyNode* GetNextSiblingUp() const;
- void AddChild(PolyNode& child);
- friend class Clipper; //to access Index
- friend class ClipperOffset;
-};
-
-class PolyTree: public PolyNode
-{
-public:
- ~PolyTree(){ Clear(); };
- PolyNode* GetFirst() const;
- void Clear();
- int Total() const;
-private:
- //PolyTree& operator =(PolyTree& other);
- PolyNodes AllNodes;
- friend class Clipper; //to access AllNodes
-};
-
-bool Orientation(const Path &poly);
-double Area(const Path &poly);
-int PointInPolygon(const IntPoint &pt, const Path &path);
-
-void SimplifyPolygon(const Path &in_poly, Paths &out_polys, PolyFillType fillType = pftEvenOdd);
-void SimplifyPolygons(const Paths &in_polys, Paths &out_polys, PolyFillType fillType = pftEvenOdd);
-void SimplifyPolygons(Paths &polys, PolyFillType fillType = pftEvenOdd);
-
-void CleanPolygon(const Path& in_poly, Path& out_poly, double distance = 1.415);
-void CleanPolygon(Path& poly, double distance = 1.415);
-void CleanPolygons(const Paths& in_polys, Paths& out_polys, double distance = 1.415);
-void CleanPolygons(Paths& polys, double distance = 1.415);
-
-void MinkowskiSum(const Path& pattern, const Path& path, Paths& solution, bool pathIsClosed);
-void MinkowskiSum(const Path& pattern, const Paths& paths, Paths& solution, bool pathIsClosed);
-void MinkowskiDiff(const Path& poly1, const Path& poly2, Paths& solution);
-
-void PolyTreeToPaths(const PolyTree& polytree, Paths& paths);
-void ClosedPathsFromPolyTree(const PolyTree& polytree, Paths& paths);
-void OpenPathsFromPolyTree(PolyTree& polytree, Paths& paths);
-
-void ReversePath(Path& p);
-void ReversePaths(Paths& p);
-
-struct IntRect { cInt left; cInt top; cInt right; cInt bottom; };
-
-//enums that are used internally ...
-enum EdgeSide { esLeft = 1, esRight = 2};
-
-//forward declarations (for stuff used internally) ...
-struct TEdge;
-struct IntersectNode;
-struct LocalMinimum;
-struct OutPt;
-struct OutRec;
-struct Join;
-
-typedef std::vector < OutRec* > PolyOutList;
-typedef std::vector < TEdge* > EdgeList;
-typedef std::vector < Join* > JoinList;
-typedef std::vector < IntersectNode* > IntersectList;
-
-//------------------------------------------------------------------------------
-
-//ClipperBase is the ancestor to the Clipper class. It should not be
-//instantiated directly. This class simply abstracts the conversion of sets of
-//polygon coordinates into edge objects that are stored in a LocalMinima list.
-class ClipperBase
-{
-public:
- ClipperBase();
- virtual ~ClipperBase();
- virtual bool AddPath(const Path &pg, PolyType PolyTyp, bool Closed);
- bool AddPaths(const Paths &ppg, PolyType PolyTyp, bool Closed);
- virtual void Clear();
- IntRect GetBounds();
- bool PreserveCollinear() {return m_PreserveCollinear;};
- void PreserveCollinear(bool value) {m_PreserveCollinear = value;};
-protected:
- void DisposeLocalMinimaList();
- TEdge* AddBoundsToLML(TEdge *e, bool IsClosed);
- virtual void Reset();
- TEdge* ProcessBound(TEdge* E, bool IsClockwise);
- void InsertScanbeam(const cInt Y);
- bool PopScanbeam(cInt &Y);
- bool LocalMinimaPending();
- bool PopLocalMinima(cInt Y, const LocalMinimum *&locMin);
- OutRec* CreateOutRec();
- void DisposeAllOutRecs();
- void DisposeOutRec(PolyOutList::size_type index);
- void SwapPositionsInAEL(TEdge *edge1, TEdge *edge2);
- void DeleteFromAEL(TEdge *e);
- void UpdateEdgeIntoAEL(TEdge *&e);
-
- typedef std::vector<LocalMinimum> MinimaList;
- MinimaList::iterator m_CurrentLM;
- MinimaList m_MinimaList;
-
- bool m_UseFullRange;
- EdgeList m_edges;
- bool m_PreserveCollinear;
- bool m_HasOpenPaths;
- PolyOutList m_PolyOuts;
- TEdge *m_ActiveEdges;
-
- typedef std::priority_queue<cInt> ScanbeamList;
- ScanbeamList m_Scanbeam;
-};
-//------------------------------------------------------------------------------
-
-class Clipper : public virtual ClipperBase
-{
-public:
- Clipper(int initOptions = 0);
- bool Execute(ClipType clipType,
- Paths &solution,
- PolyFillType fillType = pftEvenOdd);
- bool Execute(ClipType clipType,
- Paths &solution,
- PolyFillType subjFillType,
- PolyFillType clipFillType);
- bool Execute(ClipType clipType,
- PolyTree &polytree,
- PolyFillType fillType = pftEvenOdd);
- bool Execute(ClipType clipType,
- PolyTree &polytree,
- PolyFillType subjFillType,
- PolyFillType clipFillType);
- bool ReverseSolution() { return m_ReverseOutput; };
- void ReverseSolution(bool value) {m_ReverseOutput = value;};
- bool StrictlySimple() {return m_StrictSimple;};
- void StrictlySimple(bool value) {m_StrictSimple = value;};
- //set the callback function for z value filling on intersections (otherwise Z is 0)
-#ifdef use_xyz
- void ZFillFunction(ZFillCallback zFillFunc);
-#endif
-protected:
- virtual bool ExecuteInternal();
-private:
- JoinList m_Joins;
- JoinList m_GhostJoins;
- IntersectList m_IntersectList;
- ClipType m_ClipType;
- typedef std::list<cInt> MaximaList;
- MaximaList m_Maxima;
- TEdge *m_SortedEdges;
- bool m_ExecuteLocked;
- PolyFillType m_ClipFillType;
- PolyFillType m_SubjFillType;
- bool m_ReverseOutput;
- bool m_UsingPolyTree;
- bool m_StrictSimple;
-#ifdef use_xyz
- ZFillCallback m_ZFill; //custom callback
-#endif
- void SetWindingCount(TEdge& edge);
- bool IsEvenOddFillType(const TEdge& edge) const;
- bool IsEvenOddAltFillType(const TEdge& edge) const;
- void InsertLocalMinimaIntoAEL(const cInt botY);
- void InsertEdgeIntoAEL(TEdge *edge, TEdge* startEdge);
- void AddEdgeToSEL(TEdge *edge);
- bool PopEdgeFromSEL(TEdge *&edge);
- void CopyAELToSEL();
- void DeleteFromSEL(TEdge *e);
- void SwapPositionsInSEL(TEdge *edge1, TEdge *edge2);
- bool IsContributing(const TEdge& edge) const;
- bool IsTopHorz(const cInt XPos);
- void DoMaxima(TEdge *e);
- void ProcessHorizontals();
- void ProcessHorizontal(TEdge *horzEdge);
- void AddLocalMaxPoly(TEdge *e1, TEdge *e2, const IntPoint &pt);
- OutPt* AddLocalMinPoly(TEdge *e1, TEdge *e2, const IntPoint &pt);
- OutRec* GetOutRec(int idx);
- void AppendPolygon(TEdge *e1, TEdge *e2);
- void IntersectEdges(TEdge *e1, TEdge *e2, IntPoint &pt);
- OutPt* AddOutPt(TEdge *e, const IntPoint &pt);
- OutPt* GetLastOutPt(TEdge *e);
- bool ProcessIntersections(const cInt topY);
- void BuildIntersectList(const cInt topY);
- void ProcessIntersectList();
- void ProcessEdgesAtTopOfScanbeam(const cInt topY);
- void BuildResult(Paths& polys);
- void BuildResult2(PolyTree& polytree);
- void SetHoleState(TEdge *e, OutRec *outrec);
- void DisposeIntersectNodes();
- bool FixupIntersectionOrder();
- void FixupOutPolygon(OutRec &outrec);
- void FixupOutPolyline(OutRec &outrec);
- bool IsHole(TEdge *e);
- bool FindOwnerFromSplitRecs(OutRec &outRec, OutRec *&currOrfl);
- void FixHoleLinkage(OutRec &outrec);
- void AddJoin(OutPt *op1, OutPt *op2, const IntPoint offPt);
- void ClearJoins();
- void ClearGhostJoins();
- void AddGhostJoin(OutPt *op, const IntPoint offPt);
- bool JoinPoints(Join *j, OutRec* outRec1, OutRec* outRec2);
- void JoinCommonEdges();
- void DoSimplePolygons();
- void FixupFirstLefts1(OutRec* OldOutRec, OutRec* NewOutRec);
- void FixupFirstLefts2(OutRec* InnerOutRec, OutRec* OuterOutRec);
- void FixupFirstLefts3(OutRec* OldOutRec, OutRec* NewOutRec);
-#ifdef use_xyz
- void SetZ(IntPoint& pt, TEdge& e1, TEdge& e2);
-#endif
-};
-//------------------------------------------------------------------------------
-
-class ClipperOffset
-{
-public:
- ClipperOffset(double miterLimit = 2.0, double roundPrecision = 0.25);
- ~ClipperOffset();
- void AddPath(const Path& path, JoinType joinType, EndType endType);
- void AddPaths(const Paths& paths, JoinType joinType, EndType endType);
- void Execute(Paths& solution, double delta);
- void Execute(PolyTree& solution, double delta);
- void Clear();
- double MiterLimit;
- double ArcTolerance;
-private:
- Paths m_destPolys;
- Path m_srcPoly;
- Path m_destPoly;
- std::vector<DoublePoint> m_normals;
- double m_delta, m_sinA, m_sin, m_cos;
- double m_miterLim, m_StepsPerRad;
- IntPoint m_lowest;
- PolyNode m_polyNodes;
-
- void FixOrientations();
- void DoOffset(double delta);
- void OffsetPoint(int j, int& k, JoinType jointype);
- void DoSquare(int j, int k);
- void DoMiter(int j, int k, double r);
- void DoRound(int j, int k);
-};
-//------------------------------------------------------------------------------
-
-class clipperException : public std::exception
-{
- public:
- clipperException(const char* description): m_descr(description) {}
- virtual ~clipperException() throw() {}
- virtual const char* what() const throw() {return m_descr.c_str();}
- private:
- std::string m_descr;
-};
-//------------------------------------------------------------------------------
-
-} //ClipperLib namespace
-
-#endif //clipper_hpp
-
-
diff --git a/thirdparty/misc/patches/clipper-exceptions.patch b/thirdparty/misc/patches/clipper-exceptions.patch
deleted file mode 100644
index 537afd59b3..0000000000
--- a/thirdparty/misc/patches/clipper-exceptions.patch
+++ /dev/null
@@ -1,154 +0,0 @@
-diff --git a/thirdparty/misc/clipper.cpp b/thirdparty/misc/clipper.cpp
-index 8c3a59c4ca..c67045d113 100644
---- a/thirdparty/misc/clipper.cpp
-+++ b/thirdparty/misc/clipper.cpp
-@@ -48,6 +48,38 @@
- #include <ostream>
- #include <functional>
-
-+//Explicitly disables exceptions handling for target platform
-+//#define CLIPPER_NOEXCEPTION
-+
-+#define CLIPPER_THROW(exception) std::abort()
-+#define CLIPPER_TRY if(true)
-+#define CLIPPER_CATCH(exception) if(false)
-+
-+#if defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)
-+ #ifndef CLIPPER_NOEXCEPTION
-+ #undef CLIPPER_THROW
-+ #define CLIPPER_THROW(exception) throw exception
-+ #undef CLIPPER_TRY
-+ #define CLIPPER_TRY try
-+ #undef CLIPPER_CATCH
-+ #define CLIPPER_CATCH(exception) catch(exception)
-+ #endif
-+#endif
-+
-+//Optionally allows to override exception macros
-+#if defined(CLIPPER_THROW_USER)
-+ #undef CLIPPER_THROW
-+ #define CLIPPER_THROW CLIPPER_THROW_USER
-+#endif
-+#if defined(CLIPPER_TRY_USER)
-+ #undef CLIPPER_TRY
-+ #define CLIPPER_TRY CLIPPER_TRY_USER
-+#endif
-+#if defined(CLIPPER_CATCH_USER)
-+ #undef CLIPPER_CATCH
-+ #define CLIPPER_CATCH CLIPPER_CATCH_USER
-+#endif
-+
- namespace ClipperLib {
-
- static double const pi = 3.141592653589793238;
-@@ -898,7 +930,7 @@ void RangeTest(const IntPoint& Pt, bool& useFullRange)
- if (useFullRange)
- {
- if (Pt.X > hiRange || Pt.Y > hiRange || -Pt.X > hiRange || -Pt.Y > hiRange)
-- throw clipperException("Coordinate outside allowed range");
-+ CLIPPER_THROW(clipperException("Coordinate outside allowed range"));
- }
- else if (Pt.X > loRange|| Pt.Y > loRange || -Pt.X > loRange || -Pt.Y > loRange)
- {
-@@ -1046,10 +1078,10 @@ bool ClipperBase::AddPath(const Path &pg, PolyType PolyTyp, bool Closed)
- {
- #ifdef use_lines
- if (!Closed && PolyTyp == ptClip)
-- throw clipperException("AddPath: Open paths must be subject.");
-+ CLIPPER_THROW(clipperException("AddPath: Open paths must be subject."));
- #else
- if (!Closed)
-- throw clipperException("AddPath: Open paths have been disabled.");
-+ CLIPPER_THROW(clipperException("AddPath: Open paths have been disabled."));
- #endif
-
- int highI = (int)pg.size() -1;
-@@ -1062,7 +1094,7 @@ bool ClipperBase::AddPath(const Path &pg, PolyType PolyTyp, bool Closed)
-
- bool IsFlat = true;
- //1. Basic (first) edge initialization ...
-- try
-+ CLIPPER_TRY
- {
- edges[1].Curr = pg[1];
- RangeTest(pg[0], m_UseFullRange);
-@@ -1075,10 +1107,10 @@ bool ClipperBase::AddPath(const Path &pg, PolyType PolyTyp, bool Closed)
- InitEdge(&edges[i], &edges[i+1], &edges[i-1], pg[i]);
- }
- }
-- catch(...)
-+ CLIPPER_CATCH(...)
- {
- delete [] edges;
-- throw; //range test fails
-+ CLIPPER_THROW(); //range test fails
- }
- TEdge *eStart = &edges[0];
-
-@@ -1442,7 +1474,7 @@ void ClipperBase::SwapPositionsInAEL(TEdge *Edge1, TEdge *Edge2)
- void ClipperBase::UpdateEdgeIntoAEL(TEdge *&e)
- {
- if (!e->NextInLML)
-- throw clipperException("UpdateEdgeIntoAEL: invalid call");
-+ CLIPPER_THROW(clipperException("UpdateEdgeIntoAEL: invalid call"));
-
- e->NextInLML->OutIdx = e->OutIdx;
- TEdge* AelPrev = e->PrevInAEL;
-@@ -1510,7 +1542,7 @@ bool Clipper::Execute(ClipType clipType, Paths &solution,
- {
- if( m_ExecuteLocked ) return false;
- if (m_HasOpenPaths)
-- throw clipperException("Error: PolyTree struct is needed for open path clipping.");
-+ CLIPPER_THROW(clipperException("Error: PolyTree struct is needed for open path clipping."));
- m_ExecuteLocked = true;
- solution.resize(0);
- m_SubjFillType = subjFillType;
-@@ -1560,7 +1592,7 @@ void Clipper::FixHoleLinkage(OutRec &outrec)
- bool Clipper::ExecuteInternal()
- {
- bool succeeded = true;
-- try {
-+ CLIPPER_TRY {
- Reset();
- m_Maxima = MaximaList();
- m_SortedEdges = 0;
-@@ -1583,7 +1615,7 @@ bool Clipper::ExecuteInternal()
- InsertLocalMinimaIntoAEL(botY);
- }
- }
-- catch(...)
-+ CLIPPER_CATCH(...)
- {
- succeeded = false;
- }
-@@ -2827,18 +2859,18 @@ void Clipper::ProcessHorizontal(TEdge *horzEdge)
- bool Clipper::ProcessIntersections(const cInt topY)
- {
- if( !m_ActiveEdges ) return true;
-- try {
-+ CLIPPER_TRY {
- BuildIntersectList(topY);
- size_t IlSize = m_IntersectList.size();
- if (IlSize == 0) return true;
- if (IlSize == 1 || FixupIntersectionOrder()) ProcessIntersectList();
- else return false;
- }
-- catch(...)
-+ CLIPPER_CATCH(...)
- {
- m_SortedEdges = 0;
- DisposeIntersectNodes();
-- throw clipperException("ProcessIntersections error");
-+ CLIPPER_THROW(clipperException("ProcessIntersections error"));
- }
- m_SortedEdges = 0;
- return true;
-@@ -3002,7 +3034,7 @@ void Clipper::DoMaxima(TEdge *e)
- DeleteFromAEL(eMaxPair);
- }
- #endif
-- else throw clipperException("DoMaxima error");
-+ else CLIPPER_THROW(clipperException("DoMaxima error"));
- }
- //------------------------------------------------------------------------------
-