summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/classes/Environment.xml5
-rw-r--r--editor/plugins/asset_library_editor_plugin.cpp32
-rw-r--r--main/main.cpp2
-rw-r--r--modules/arkit/arkit_interface.h6
-rw-r--r--modules/arkit/arkit_interface.mm6
-rw-r--r--modules/arkit/arkit_session_delegate.h6
-rw-r--r--modules/arkit/arkit_session_delegate.mm6
-rw-r--r--modules/gdnative/nativescript/nativescript.cpp2
-rw-r--r--modules/gdnative/pluginscript/pluginscript_script.cpp2
-rw-r--r--modules/gdscript/SCsub1
-rw-r--r--modules/gdscript/gdscript.cpp2
-rw-r--r--modules/gdscript/gdscript_function.cpp4
-rw-r--r--modules/gdscript/gdscript_parser.cpp10
-rw-r--r--modules/gdscript/gdscript_parser.h1
-rw-r--r--modules/gdscript/language_server/gdscript_extend_parser.cpp759
-rw-r--r--modules/gdscript/language_server/gdscript_extend_parser.h103
-rw-r--r--modules/gdscript/language_server/gdscript_language_protocol.cpp211
-rw-r--r--modules/gdscript/language_server/gdscript_language_protocol.h93
-rw-r--r--modules/gdscript/language_server/gdscript_language_server.cpp88
-rw-r--r--modules/gdscript/language_server/gdscript_language_server.h60
-rw-r--r--modules/gdscript/language_server/gdscript_text_document.cpp391
-rw-r--r--modules/gdscript/language_server/gdscript_text_document.h73
-rw-r--r--modules/gdscript/language_server/gdscript_workspace.cpp504
-rw-r--r--modules/gdscript/language_server/gdscript_workspace.h91
-rw-r--r--modules/gdscript/language_server/lsp.hpp1506
-rw-r--r--modules/gdscript/register_types.cpp7
-rw-r--r--modules/jsonrpc/SCsub7
-rw-r--r--modules/jsonrpc/config.py5
-rw-r--r--modules/jsonrpc/jsonrpc.cpp171
-rw-r--r--modules/jsonrpc/jsonrpc.h70
-rw-r--r--modules/jsonrpc/register_types.cpp40
-rw-r--r--modules/jsonrpc/register_types.h32
-rw-r--r--modules/mbedtls/ssl_context_mbedtls.cpp2
-rw-r--r--modules/mbedtls/ssl_context_mbedtls.h2
-rwxr-xr-xmodules/mbedtls/stream_peer_mbedtls.cpp2
-rwxr-xr-xmodules/mbedtls/stream_peer_mbedtls.h2
-rw-r--r--modules/mono/class_db_api_json.cpp4
-rw-r--r--modules/mono/class_db_api_json.h4
-rw-r--r--modules/mono/csharp_script.cpp2
-rw-r--r--modules/mono/mono_gd/android_mono_config.h30
-rw-r--r--modules/recast/navigation_mesh_generator.cpp19
-rw-r--r--modules/websocket/wsl_peer.cpp2
-rw-r--r--modules/xatlas_unwrap/register_types.cpp6
-rw-r--r--scene/gui/control.cpp6
-rw-r--r--scene/resources/environment.cpp2
-rw-r--r--servers/arvr/arvr_interface.h2
-rw-r--r--servers/visual/shader_language.cpp59
-rw-r--r--servers/visual/shader_language.h3
-rw-r--r--thirdparty/README.md2
-rw-r--r--thirdparty/xatlas/avoid-failing-on-bad-geometry.patch157
-rw-r--r--thirdparty/xatlas/build-fix-limits.patch14
-rw-r--r--thirdparty/xatlas/xatlas.cpp2891
-rw-r--r--thirdparty/xatlas/xatlas.h36
53 files changed, 5960 insertions, 1583 deletions
diff --git a/doc/classes/Environment.xml b/doc/classes/Environment.xml
index 86c1002666..fcbd8a2193 100644
--- a/doc/classes/Environment.xml
+++ b/doc/classes/Environment.xml
@@ -146,10 +146,11 @@
<member name="fog_depth_enabled" type="bool" setter="set_fog_depth_enabled" getter="is_fog_depth_enabled" default="true">
Enables the fog depth.
</member>
- <member name="fog_depth_end" type="float" setter="set_fog_depth_end" getter="get_fog_depth_end" default="0.0">
+ <member name="fog_depth_end" type="float" setter="set_fog_depth_end" getter="get_fog_depth_end" default="100.0">
+ Fog's depth end distance from the camera. If this value is set to 0, it will be equal to the current camera's [member Camera.far] value.
</member>
<member name="fog_enabled" type="bool" setter="set_fog_enabled" getter="is_fog_enabled" default="false">
- Enables the fog. Needs fog_height_enabled and/or for_depth_enabled to actually display fog.
+ Enables the fog. Needs [member fog_height_enabled] and/or [member fog_depth_enabled] to actually display fog.
</member>
<member name="fog_height_curve" type="float" setter="set_fog_height_curve" getter="get_fog_height_curve" default="1.0">
Value defining the fog height intensity.
diff --git a/editor/plugins/asset_library_editor_plugin.cpp b/editor/plugins/asset_library_editor_plugin.cpp
index 0263be50ce..fecab89d23 100644
--- a/editor/plugins/asset_library_editor_plugin.cpp
+++ b/editor/plugins/asset_library_editor_plugin.cpp
@@ -101,7 +101,7 @@ EditorAssetLibraryItem::EditorAssetLibraryItem() {
add_style_override("panel", border);
HBoxContainer *hb = memnew(HBoxContainer);
- // Add some spacing to visually separate the icon from the asset details
+ // Add some spacing to visually separate the icon from the asset details.
hb->add_constant_override("separation", 15 * EDSCALE);
add_child(hb);
@@ -118,25 +118,21 @@ EditorAssetLibraryItem::EditorAssetLibraryItem() {
vb->set_h_size_flags(SIZE_EXPAND_FILL);
title = memnew(LinkButton);
- title->set_text("My Awesome Addon");
title->set_underline_mode(LinkButton::UNDERLINE_MODE_ON_HOVER);
title->connect("pressed", this, "_asset_clicked");
vb->add_child(title);
category = memnew(LinkButton);
- category->set_text("Editor Tools");
category->set_underline_mode(LinkButton::UNDERLINE_MODE_ON_HOVER);
category->connect("pressed", this, "_category_clicked");
vb->add_child(category);
author = memnew(LinkButton);
- author->set_text("Johny Tolengo");
author->set_underline_mode(LinkButton::UNDERLINE_MODE_ON_HOVER);
author->connect("pressed", this, "_author_clicked");
vb->add_child(author);
price = memnew(Label);
- price->set_text(TTR("Free"));
vb->add_child(price);
set_custom_minimum_size(Size2(250, 100) * EDSCALE);
@@ -278,12 +274,8 @@ void EditorAssetLibraryItemDescription::add_preview(int p_id, bool p_video, cons
EditorAssetLibraryItemDescription::EditorAssetLibraryItemDescription() {
- VBoxContainer *vbox = memnew(VBoxContainer);
- add_child(vbox);
-
HBoxContainer *hbox = memnew(HBoxContainer);
- vbox->add_child(hbox);
- vbox->add_constant_override("separation", 15 * EDSCALE);
+ add_child(hbox);
VBoxContainer *desc_vbox = memnew(VBoxContainer);
hbox->add_child(desc_vbox);
hbox->add_constant_override("separation", 15 * EDSCALE);
@@ -298,17 +290,20 @@ EditorAssetLibraryItemDescription::EditorAssetLibraryItemDescription() {
desc_bg->set_v_size_flags(SIZE_EXPAND_FILL);
description = memnew(RichTextLabel);
+ desc_bg->add_child(description);
description->connect("meta_clicked", this, "_link_click");
description->set_custom_minimum_size(Size2(440 * EDSCALE, 300 * EDSCALE));
- desc_bg->add_child(description);
VBoxContainer *previews_vbox = memnew(VBoxContainer);
hbox->add_child(previews_vbox);
previews_vbox->add_constant_override("separation", 15 * EDSCALE);
+ previews_vbox->set_v_size_flags(SIZE_EXPAND_FILL);
preview = memnew(TextureRect);
- preview->set_custom_minimum_size(Size2(640 * EDSCALE, 345 * EDSCALE));
previews_vbox->add_child(preview);
+ preview->set_expand(true);
+ preview->set_stretch_mode(TextureRect::STRETCH_KEEP_ASPECT_CENTERED);
+ preview->set_custom_minimum_size(Size2(640 * EDSCALE, 345 * EDSCALE));
previews_bg = memnew(PanelContainer);
previews_vbox->add_child(previews_bg);
@@ -392,7 +387,7 @@ void EditorAssetLibraryItemDownload::_http_download_completed(int p_status, int
install->set_disabled(false);
status->set_text(TTR("Success!"));
- // Make the progress bar invisible but don't reflow other Controls around it
+ // Make the progress bar invisible but don't reflow other Controls around it.
progress->set_modulate(Color(0, 0, 0, 0));
set_process(false);
@@ -661,7 +656,7 @@ const char *EditorAssetLibrary::sort_key[SORT_MAX] = {
const char *EditorAssetLibrary::sort_text[SORT_MAX] = {
"Downloads",
"Name",
- "License", // "cost" stores the SPDX license name in the Godot Asset Library
+ "License", // "cost" stores the SPDX license name in the Godot Asset Library.
"Updated"
};
@@ -673,7 +668,7 @@ const char *EditorAssetLibrary::support_key[SUPPORT_MAX] = {
void EditorAssetLibrary::_select_author(int p_id) {
- // Open author window
+ // Open author window.
}
void EditorAssetLibrary::_select_category(int p_id) {
@@ -1345,7 +1340,6 @@ EditorAssetLibrary::EditorAssetLibrary(bool p_templates_only) {
library_main->add_child(search_hb);
library_main->add_constant_override("separation", 10 * EDSCALE);
- search_hb->add_child(memnew(Label(TTR("Search:") + " ")));
filter = memnew(LineEdit);
search_hb->add_child(filter);
filter->set_h_size_flags(SIZE_EXPAND_FILL);
@@ -1358,12 +1352,12 @@ EditorAssetLibrary::EditorAssetLibrary(bool p_templates_only) {
search_hb->add_child(memnew(VSeparator));
Button *open_asset = memnew(Button);
- open_asset->set_text(TTR("Import"));
+ open_asset->set_text(TTR("Import..."));
search_hb->add_child(open_asset);
open_asset->connect("pressed", this, "_asset_open");
Button *plugins = memnew(Button);
- plugins->set_text(TTR("Plugins"));
+ plugins->set_text(TTR("Plugins..."));
search_hb->add_child(plugins);
plugins->connect("pressed", this, "_manage_plugins");
@@ -1420,7 +1414,7 @@ EditorAssetLibrary::EditorAssetLibrary(bool p_templates_only) {
support = memnew(MenuButton);
search_hb2->add_child(support);
- support->set_text(TTR("Support..."));
+ support->set_text(TTR("Support"));
support->get_popup()->add_check_item(TTR("Official"), SUPPORT_OFFICIAL);
support->get_popup()->add_check_item(TTR("Community"), SUPPORT_COMMUNITY);
support->get_popup()->add_check_item(TTR("Testing"), SUPPORT_TESTING);
diff --git a/main/main.cpp b/main/main.cpp
index 8def4bd1ec..f4665c4ad4 100644
--- a/main/main.cpp
+++ b/main/main.cpp
@@ -944,7 +944,7 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
OS::get_singleton()->_allow_hidpi = GLOBAL_DEF("display/window/dpi/allow_hidpi", false);
}
- video_mode.use_vsync = GLOBAL_DEF("display/window/vsync/use_vsync", true);
+ video_mode.use_vsync = GLOBAL_DEF_RST("display/window/vsync/use_vsync", true);
OS::get_singleton()->_use_vsync = video_mode.use_vsync;
OS::get_singleton()->_allow_layered = GLOBAL_DEF("display/window/per_pixel_transparency/allowed", false);
diff --git a/modules/arkit/arkit_interface.h b/modules/arkit/arkit_interface.h
index 8129611287..e1dbca1488 100644
--- a/modules/arkit/arkit_interface.h
+++ b/modules/arkit/arkit_interface.h
@@ -3,10 +3,10 @@
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
-/* http://www.godotengine.org */
+/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */
+/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
diff --git a/modules/arkit/arkit_interface.mm b/modules/arkit/arkit_interface.mm
index 68844c54c2..9614f775a5 100644
--- a/modules/arkit/arkit_interface.mm
+++ b/modules/arkit/arkit_interface.mm
@@ -3,10 +3,10 @@
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
-/* http://www.godotengine.org */
+/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */
+/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
diff --git a/modules/arkit/arkit_session_delegate.h b/modules/arkit/arkit_session_delegate.h
index afe093656b..9303552ca6 100644
--- a/modules/arkit/arkit_session_delegate.h
+++ b/modules/arkit/arkit_session_delegate.h
@@ -3,10 +3,10 @@
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
-/* http://www.godotengine.org */
+/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */
+/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
diff --git a/modules/arkit/arkit_session_delegate.mm b/modules/arkit/arkit_session_delegate.mm
index 56485c987c..d4072fc391 100644
--- a/modules/arkit/arkit_session_delegate.mm
+++ b/modules/arkit/arkit_session_delegate.mm
@@ -3,10 +3,10 @@
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
-/* http://www.godotengine.org */
+/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */
+/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
diff --git a/modules/gdnative/nativescript/nativescript.cpp b/modules/gdnative/nativescript/nativescript.cpp
index 9f7c3880ec..7c313c983f 100644
--- a/modules/gdnative/nativescript/nativescript.cpp
+++ b/modules/gdnative/nativescript/nativescript.cpp
@@ -79,7 +79,7 @@ void NativeScript::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::STRING, "script_class_name"), "set_script_class_name", "get_script_class_name");
ADD_PROPERTY(PropertyInfo(Variant::STRING, "script_class_icon_path", PROPERTY_HINT_FILE), "set_script_class_icon_path", "get_script_class_icon_path");
- ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "new", &NativeScript::_new, MethodInfo(Variant::OBJECT, "new"));
+ ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "new", &NativeScript::_new, MethodInfo("new"));
}
#define NSL NativeScriptLanguage::get_singleton()
diff --git a/modules/gdnative/pluginscript/pluginscript_script.cpp b/modules/gdnative/pluginscript/pluginscript_script.cpp
index b82823ab64..94d38e1be1 100644
--- a/modules/gdnative/pluginscript/pluginscript_script.cpp
+++ b/modules/gdnative/pluginscript/pluginscript_script.cpp
@@ -50,7 +50,7 @@
#endif
void PluginScript::_bind_methods() {
- ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "new", &PluginScript::_new, MethodInfo(Variant::OBJECT, "new"));
+ ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "new", &PluginScript::_new, MethodInfo("new"));
}
PluginScriptInstance *PluginScript::_create_instance(const Variant **p_args, int p_argcount, Object *p_owner, Variant::CallError &r_error) {
diff --git a/modules/gdscript/SCsub b/modules/gdscript/SCsub
index 6904154953..6285e6bb54 100644
--- a/modules/gdscript/SCsub
+++ b/modules/gdscript/SCsub
@@ -9,3 +9,4 @@ env_gdscript.add_source_files(env.modules_sources, "*.cpp")
if env['tools']:
env_gdscript.add_source_files(env.modules_sources, "./editor/*.cpp")
+ env_gdscript.add_source_files(env.modules_sources, "./language_server/*.cpp")
diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp
index d929bdb3e5..5dab063061 100644
--- a/modules/gdscript/gdscript.cpp
+++ b/modules/gdscript/gdscript.cpp
@@ -710,7 +710,7 @@ void GDScript::_get_property_list(List<PropertyInfo> *p_properties) const {
void GDScript::_bind_methods() {
- ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "new", &GDScript::_new, MethodInfo(Variant::OBJECT, "new"));
+ ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "new", &GDScript::_new, MethodInfo("new"));
ClassDB::bind_method(D_METHOD("get_as_byte_code"), &GDScript::get_as_byte_code);
}
diff --git a/modules/gdscript/gdscript_function.cpp b/modules/gdscript/gdscript_function.cpp
index dc0e64fd03..68f2a9473e 100644
--- a/modules/gdscript/gdscript_function.cpp
+++ b/modules/gdscript/gdscript_function.cpp
@@ -431,6 +431,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
profile.frame_call_count++;
}
bool exit_ok = false;
+ bool yielded = false;
#endif
#ifdef DEBUG_ENABLED
@@ -1323,6 +1324,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
#ifdef DEBUG_ENABLED
exit_ok = true;
+ yielded = true;
#endif
OPCODE_BREAK;
}
@@ -1589,8 +1591,6 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
GDScriptLanguage::get_singleton()->script_frame_time += time_taken - function_call_time;
}
- bool yielded = retvalue.is_ref() && Object::cast_to<GDScriptFunctionState>(retvalue);
-
// Check if this is the last time the function is resuming from yield
// Will be true if never yielded as well
// When it's the last resume it will postpone the exit from stack,
diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp
index a07b575c6e..a9f22225a0 100644
--- a/modules/gdscript/gdscript_parser.cpp
+++ b/modules/gdscript/gdscript_parser.cpp
@@ -2767,12 +2767,12 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) {
_mark_line_as_safe(tokenizer->get_token_line());
tokenizer->advance();
if (tokenizer->get_token() == GDScriptTokenizer::TK_SEMICOLON) {
- // Ignore semicolon after 'pass'
+ // Ignore semicolon after 'pass'.
tokenizer->advance();
}
} break;
case GDScriptTokenizer::TK_PR_VAR: {
- //variale declaration and (eventual) initialization
+ // Variable declaration and (eventual) initialization.
tokenizer->advance();
int var_line = tokenizer->get_token_line();
@@ -3171,6 +3171,7 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) {
} break;
case GDScriptTokenizer::TK_CF_CONTINUE: {
+ _mark_line_as_safe(tokenizer->get_token_line());
tokenizer->advance();
ControlFlowNode *cf_continue = alloc_node<ControlFlowNode>();
cf_continue->cf_type = ControlFlowNode::CF_CONTINUE;
@@ -3182,6 +3183,7 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) {
} break;
case GDScriptTokenizer::TK_CF_BREAK: {
+ _mark_line_as_safe(tokenizer->get_token_line());
tokenizer->advance();
ControlFlowNode *cf_break = alloc_node<ControlFlowNode>();
cf_break->cf_type = ControlFlowNode::CF_BREAK;
@@ -8255,6 +8257,10 @@ int GDScriptParser::get_error_column() const {
return error_column;
}
+bool GDScriptParser::has_error() const {
+ return error_set;
+}
+
Error GDScriptParser::_parse(const String &p_base_path) {
base_path = p_base_path;
diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h
index 62d7bdb393..72aa819a8c 100644
--- a/modules/gdscript/gdscript_parser.h
+++ b/modules/gdscript/gdscript_parser.h
@@ -632,6 +632,7 @@ private:
Error _parse(const String &p_base_path);
public:
+ bool has_error() const;
String get_error() const;
int get_error_line() const;
int get_error_column() const;
diff --git a/modules/gdscript/language_server/gdscript_extend_parser.cpp b/modules/gdscript/language_server/gdscript_extend_parser.cpp
new file mode 100644
index 0000000000..45f9ec9c6a
--- /dev/null
+++ b/modules/gdscript/language_server/gdscript_extend_parser.cpp
@@ -0,0 +1,759 @@
+/*************************************************************************/
+/* gdscript_extend_parser.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "gdscript_extend_parser.h"
+#include "../gdscript.h"
+#include "core/io/json.h"
+#include "gdscript_language_protocol.h"
+#include "gdscript_workspace.h"
+
+void ExtendGDScriptParser::update_diagnostics() {
+
+ diagnostics.clear();
+
+ if (has_error()) {
+ lsp::Diagnostic diagnostic;
+ diagnostic.severity = lsp::DiagnosticSeverity::Error;
+ diagnostic.message = get_error();
+ diagnostic.source = "gdscript";
+ diagnostic.code = -1;
+ lsp::Range range;
+ lsp::Position pos;
+ int line = LINE_NUMBER_TO_INDEX(get_error_line());
+ const String &line_text = get_lines()[line];
+ pos.line = line;
+ pos.character = line_text.length() - line_text.strip_edges(true, false).length();
+ range.start = pos;
+ range.end = range.start;
+ range.end.character = line_text.strip_edges(false).length();
+ diagnostic.range = range;
+ diagnostics.push_back(diagnostic);
+ }
+
+ const List<GDScriptWarning> &warnings = get_warnings();
+ for (const List<GDScriptWarning>::Element *E = warnings.front(); E; E = E->next()) {
+ const GDScriptWarning &warning = E->get();
+ lsp::Diagnostic diagnostic;
+ diagnostic.severity = lsp::DiagnosticSeverity::Warning;
+ diagnostic.message = warning.get_message();
+ diagnostic.source = "gdscript";
+ diagnostic.code = warning.code;
+ lsp::Range range;
+ lsp::Position pos;
+ int line = LINE_NUMBER_TO_INDEX(warning.line);
+ const String &line_text = get_lines()[line];
+ pos.line = line;
+ pos.character = line_text.length() - line_text.strip_edges(true, false).length();
+ range.start = pos;
+ range.end = pos;
+ range.end.character = line_text.strip_edges(false).length();
+ diagnostic.range = range;
+ diagnostics.push_back(diagnostic);
+ }
+}
+
+void ExtendGDScriptParser::update_symbols() {
+
+ members.clear();
+
+ const GDScriptParser::Node *head = get_parse_tree();
+ if (const GDScriptParser::ClassNode *gdclass = dynamic_cast<const GDScriptParser::ClassNode *>(head)) {
+
+ parse_class_symbol(gdclass, class_symbol);
+
+ for (int i = 0; i < class_symbol.children.size(); i++) {
+ const lsp::DocumentSymbol &symbol = class_symbol.children[i];
+ members.set(symbol.name, &symbol);
+
+ // cache level one inner classes
+ if (symbol.kind == lsp::SymbolKind::Class) {
+ ClassMembers inner_class;
+ for (int j = 0; j < symbol.children.size(); j++) {
+ const lsp::DocumentSymbol &s = symbol.children[j];
+ inner_class.set(s.name, &s);
+ }
+ inner_classes.set(symbol.name, inner_class);
+ }
+ }
+ }
+}
+
+void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p_class, lsp::DocumentSymbol &r_symbol) {
+
+ const String uri = get_uri();
+
+ r_symbol.uri = uri;
+ r_symbol.script_path = path;
+ r_symbol.children.clear();
+ r_symbol.name = p_class->name;
+ if (r_symbol.name.empty())
+ r_symbol.name = path.get_file();
+ r_symbol.kind = lsp::SymbolKind::Class;
+ r_symbol.deprecated = false;
+ r_symbol.range.start.line = LINE_NUMBER_TO_INDEX(p_class->line);
+ r_symbol.range.start.character = p_class->column;
+ r_symbol.range.end.line = LINE_NUMBER_TO_INDEX(p_class->end_line);
+ r_symbol.selectionRange.start.line = r_symbol.range.start.line;
+ r_symbol.detail = "class " + r_symbol.name;
+ bool is_root_class = &r_symbol == &class_symbol;
+ r_symbol.documentation = parse_documentation_as_markdown(is_root_class ? 0 : LINE_NUMBER_TO_INDEX(p_class->line), is_root_class);
+
+ for (int i = 0; i < p_class->variables.size(); ++i) {
+
+ const GDScriptParser::ClassNode::Member &m = p_class->variables[i];
+
+ lsp::DocumentSymbol symbol;
+ symbol.name = m.identifier;
+ symbol.kind = lsp::SymbolKind::Variable;
+ symbol.deprecated = false;
+ const int line = LINE_NUMBER_TO_INDEX(m.line);
+ symbol.range.start.line = line;
+ symbol.range.start.character = lines[line].length() - lines[line].strip_edges(true, false).length();
+ symbol.range.end.line = line;
+ symbol.range.end.character = lines[line].length();
+ symbol.selectionRange.start.line = symbol.range.start.line;
+ if (m._export.type != Variant::NIL) {
+ symbol.detail += "export ";
+ }
+ symbol.detail += "var " + m.identifier;
+ if (m.data_type.kind != GDScriptParser::DataType::UNRESOLVED) {
+ symbol.detail += ": " + m.data_type.to_string();
+ }
+ if (m.default_value.get_type() != Variant::NIL) {
+ symbol.detail += " = " + JSON::print(m.default_value);
+ }
+
+ symbol.documentation = parse_documentation_as_markdown(line);
+ symbol.uri = uri;
+ symbol.script_path = path;
+
+ r_symbol.children.push_back(symbol);
+ }
+
+ for (int i = 0; i < p_class->_signals.size(); ++i) {
+ const GDScriptParser::ClassNode::Signal &signal = p_class->_signals[i];
+
+ lsp::DocumentSymbol symbol;
+ symbol.name = signal.name;
+ symbol.kind = lsp::SymbolKind::Event;
+ symbol.deprecated = false;
+ const int line = LINE_NUMBER_TO_INDEX(signal.line);
+ symbol.range.start.line = line;
+ symbol.range.start.character = lines[line].length() - lines[line].strip_edges(true, false).length();
+ symbol.range.end.line = symbol.range.start.line;
+ symbol.range.end.character = lines[line].length();
+ symbol.selectionRange.start.line = symbol.range.start.line;
+ symbol.documentation = parse_documentation_as_markdown(line);
+ symbol.uri = uri;
+ symbol.script_path = path;
+ symbol.detail = "signal " + signal.name + "(";
+ for (int j = 0; j < signal.arguments.size(); j++) {
+ if (j > 0) {
+ symbol.detail += ", ";
+ }
+ symbol.detail += signal.arguments[j];
+ }
+ symbol.detail += ")";
+
+ r_symbol.children.push_back(symbol);
+ }
+
+ for (Map<StringName, GDScriptParser::ClassNode::Constant>::Element *E = p_class->constant_expressions.front(); E; E = E->next()) {
+ lsp::DocumentSymbol symbol;
+ const GDScriptParser::ClassNode::Constant &c = E->value();
+ const GDScriptParser::ConstantNode *node = dynamic_cast<const GDScriptParser::ConstantNode *>(c.expression);
+ symbol.name = E->key();
+ symbol.kind = lsp::SymbolKind::Constant;
+ symbol.deprecated = false;
+ const int line = LINE_NUMBER_TO_INDEX(E->get().expression->line);
+ symbol.range.start.line = line;
+ symbol.range.start.character = E->get().expression->column;
+ symbol.range.end.line = symbol.range.start.line;
+ symbol.range.end.character = lines[line].length();
+ symbol.selectionRange.start.line = symbol.range.start.line;
+ symbol.documentation = parse_documentation_as_markdown(line);
+ symbol.uri = uri;
+ symbol.script_path = path;
+
+ symbol.detail = "const " + symbol.name;
+ if (c.type.kind != GDScriptParser::DataType::UNRESOLVED) {
+ symbol.detail += ": " + c.type.to_string();
+ }
+
+ String value_text;
+ if (node->value.get_type() == Variant::OBJECT) {
+ RES res = node->value;
+ if (res.is_valid() && !res->get_path().empty()) {
+ value_text = "preload(\"" + res->get_path() + "\")";
+ if (symbol.documentation.empty()) {
+ if (Map<String, ExtendGDScriptParser *>::Element *S = GDScriptLanguageProtocol::get_singleton()->get_workspace()->scripts.find(res->get_path())) {
+ symbol.documentation = S->get()->class_symbol.documentation;
+ }
+ }
+ } else {
+ value_text = JSON::print(node->value);
+ }
+ } else {
+ value_text = JSON::print(node->value);
+ }
+ if (!value_text.empty()) {
+ symbol.detail += " = " + value_text;
+ }
+
+ r_symbol.children.push_back(symbol);
+ }
+
+ for (int i = 0; i < p_class->functions.size(); ++i) {
+ const GDScriptParser::FunctionNode *func = p_class->functions[i];
+ lsp::DocumentSymbol symbol;
+ parse_function_symbol(func, symbol);
+ r_symbol.children.push_back(symbol);
+ }
+
+ for (int i = 0; i < p_class->static_functions.size(); ++i) {
+ const GDScriptParser::FunctionNode *func = p_class->static_functions[i];
+ lsp::DocumentSymbol symbol;
+ parse_function_symbol(func, symbol);
+ r_symbol.children.push_back(symbol);
+ }
+
+ for (int i = 0; i < p_class->subclasses.size(); ++i) {
+ const GDScriptParser::ClassNode *subclass = p_class->subclasses[i];
+ lsp::DocumentSymbol symbol;
+ parse_class_symbol(subclass, symbol);
+ r_symbol.children.push_back(symbol);
+ }
+}
+
+void ExtendGDScriptParser::parse_function_symbol(const GDScriptParser::FunctionNode *p_func, lsp::DocumentSymbol &r_symbol) {
+
+ const String uri = get_uri();
+
+ r_symbol.name = p_func->name;
+ r_symbol.kind = lsp::SymbolKind::Function;
+ r_symbol.detail = "func " + p_func->name + "(";
+ r_symbol.deprecated = false;
+ const int line = LINE_NUMBER_TO_INDEX(p_func->line);
+ r_symbol.range.start.line = line;
+ r_symbol.range.start.character = p_func->column;
+ r_symbol.range.end.line = MAX(p_func->body->end_line - 2, p_func->body->line);
+ r_symbol.range.end.character = lines[r_symbol.range.end.line].length();
+ r_symbol.selectionRange.start.line = r_symbol.range.start.line;
+ r_symbol.documentation = parse_documentation_as_markdown(line);
+ r_symbol.uri = uri;
+ r_symbol.script_path = path;
+
+ String arguments;
+ for (int i = 0; i < p_func->arguments.size(); i++) {
+ lsp::DocumentSymbol symbol;
+ symbol.kind = lsp::SymbolKind::Variable;
+ symbol.name = p_func->arguments[i];
+ symbol.range.start.line = LINE_NUMBER_TO_INDEX(p_func->body->line);
+ symbol.range.start.character = p_func->body->column;
+ symbol.range.end = symbol.range.start;
+ symbol.uri = uri;
+ symbol.script_path = path;
+ r_symbol.children.push_back(symbol);
+ if (i > 0) {
+ arguments += ", ";
+ }
+ arguments += String(p_func->arguments[i]);
+ if (p_func->argument_types[i].kind != GDScriptParser::DataType::UNRESOLVED) {
+ arguments += ": " + p_func->argument_types[i].to_string();
+ }
+ int default_value_idx = i - (p_func->arguments.size() - p_func->default_values.size());
+ if (default_value_idx >= 0) {
+ const GDScriptParser::ConstantNode *const_node = dynamic_cast<const GDScriptParser::ConstantNode *>(p_func->default_values[default_value_idx]);
+ if (const_node == NULL) {
+ const GDScriptParser::OperatorNode *operator_node = dynamic_cast<const GDScriptParser::OperatorNode *>(p_func->default_values[default_value_idx]);
+ if (operator_node) {
+ const_node = dynamic_cast<const GDScriptParser::ConstantNode *>(operator_node->next);
+ }
+ }
+
+ if (const_node) {
+ String value = JSON::print(const_node->value);
+ arguments += " = " + value;
+ }
+ }
+ }
+ r_symbol.detail += arguments + ")";
+ if (p_func->return_type.kind != GDScriptParser::DataType::UNRESOLVED) {
+ r_symbol.detail += " -> " + p_func->return_type.to_string();
+ }
+
+ for (const Map<StringName, LocalVarNode *>::Element *E = p_func->body->variables.front(); E; E = E->next()) {
+ lsp::DocumentSymbol symbol;
+ const GDScriptParser::LocalVarNode *var = E->value();
+ symbol.name = E->key();
+ symbol.kind = lsp::SymbolKind::Variable;
+ symbol.range.start.line = LINE_NUMBER_TO_INDEX(E->get()->line);
+ symbol.range.start.character = E->get()->column;
+ symbol.range.end.line = symbol.range.start.line;
+ symbol.range.end.character = lines[symbol.range.end.line].length();
+ symbol.uri = uri;
+ symbol.script_path = path;
+ symbol.detail = "var " + symbol.name;
+ if (var->datatype.kind != GDScriptParser::DataType::UNRESOLVED) {
+ symbol.detail += ": " + var->datatype.to_string();
+ }
+ symbol.documentation = parse_documentation_as_markdown(line);
+ r_symbol.children.push_back(symbol);
+ }
+}
+
+String ExtendGDScriptParser::marked_documentation(const String &p_bbcode) {
+
+ String markdown = p_bbcode.strip_edges();
+
+ Vector<String> lines = markdown.split("\n");
+ bool in_code_block = false;
+ int code_block_indent = -1;
+
+ markdown = "";
+ for (int i = 0; i < lines.size(); i++) {
+ String line = lines[i];
+ int block_start = line.find("[codeblock]");
+ if (block_start != -1) {
+ code_block_indent = block_start;
+ in_code_block = true;
+ line = "'''gdscript";
+ line = "\n";
+ } else if (in_code_block) {
+ line = "\t" + line.substr(code_block_indent, line.length());
+ }
+
+ if (in_code_block && line.find("[/codeblock]") != -1) {
+ line = "'''\n";
+ line = "\n";
+ in_code_block = false;
+ }
+
+ if (!in_code_block) {
+ line = line.strip_edges();
+ line = line.replace("[code]", "`");
+ line = line.replace("[/code]", "`");
+ line = line.replace("[i]", "*");
+ line = line.replace("[/i]", "*");
+ line = line.replace("[b]", "**");
+ line = line.replace("[/b]", "**");
+ line = line.replace("[u]", "__");
+ line = line.replace("[/u]", "__");
+ line = line.replace("[method ", "`");
+ line = line.replace("[member ", "`");
+ line = line.replace("[signal ", "`");
+ line = line.replace("[enum ", "`");
+ line = line.replace("[constant ", "`");
+ line = line.replace("[", "`");
+ line = line.replace("]", "`");
+ }
+
+ if (!in_code_block && i < lines.size() - 1) {
+ line += "\n\n";
+ } else if (i < lines.size() - 1) {
+ line += "\n";
+ }
+ markdown += line;
+ }
+ return markdown;
+}
+
+String ExtendGDScriptParser::parse_documentation(int p_line, bool p_docs_down) {
+ ERR_FAIL_INDEX_V(p_line, lines.size(), String());
+
+ List<String> doc_lines;
+
+ if (!p_docs_down) { // inline comment
+ String inline_comment = lines[p_line];
+ int comment_start = inline_comment.find("#");
+ if (comment_start != -1) {
+ inline_comment = inline_comment.substr(comment_start, inline_comment.length()).strip_edges();
+ if (inline_comment.length() > 1) {
+ doc_lines.push_back(inline_comment.substr(1, inline_comment.length()));
+ }
+ }
+ }
+
+ int step = p_docs_down ? 1 : -1;
+ int start_line = p_docs_down ? p_line : p_line - 1;
+ for (int i = start_line; true; i += step) {
+
+ if (i < 0 || i >= lines.size()) break;
+
+ String line_comment = lines[i].strip_edges(true, false);
+ if (line_comment.begins_with("#")) {
+ line_comment = line_comment.substr(1, line_comment.length());
+ if (p_docs_down) {
+ doc_lines.push_back(line_comment);
+ } else {
+ doc_lines.push_front(line_comment);
+ }
+ } else {
+ break;
+ }
+ }
+
+ String doc;
+ for (List<String>::Element *E = doc_lines.front(); E; E = E->next()) {
+ doc += E->get() + "\n";
+ }
+ return doc;
+}
+
+String ExtendGDScriptParser::get_text_for_completion(const lsp::Position &p_cursor) const {
+
+ String longthing;
+ int len = lines.size();
+ for (int i = 0; i < len; i++) {
+
+ if (i == p_cursor.line) {
+ longthing += lines[i].substr(0, p_cursor.character);
+ longthing += String::chr(0xFFFF); //not unicode, represents the cursor
+ longthing += lines[i].substr(p_cursor.character, lines[i].size());
+ } else {
+
+ longthing += lines[i];
+ }
+
+ if (i != len - 1)
+ longthing += "\n";
+ }
+
+ return longthing;
+}
+
+String ExtendGDScriptParser::get_text_for_lookup_symbol(const lsp::Position &p_cursor, const String &p_symbol, bool p_func_requred) const {
+ String longthing;
+ int len = lines.size();
+ for (int i = 0; i < len; i++) {
+
+ if (i == p_cursor.line) {
+ String line = lines[i];
+ String first_part = line.substr(0, p_cursor.character);
+ String last_part = line.substr(p_cursor.character + 1, lines[i].length());
+ if (!p_symbol.empty()) {
+ String left_cursor_text;
+ for (int c = p_cursor.character - 1; c >= 0; c--) {
+ left_cursor_text = line.substr(c, p_cursor.character - c);
+ if (p_symbol.begins_with(left_cursor_text)) {
+ first_part = line.substr(0, c);
+ first_part += p_symbol;
+ break;
+ }
+ }
+ }
+
+ longthing += first_part;
+ longthing += String::chr(0xFFFF); //not unicode, represents the cursor
+ if (p_func_requred) {
+ longthing += "("; // tell the parser this is a function call
+ }
+ longthing += last_part;
+ } else {
+
+ longthing += lines[i];
+ }
+
+ if (i != len - 1)
+ longthing += "\n";
+ }
+
+ return longthing;
+}
+
+String ExtendGDScriptParser::get_identifier_under_position(const lsp::Position &p_position, Vector2i &p_offset) const {
+
+ ERR_FAIL_INDEX_V(p_position.line, lines.size(), "");
+ String line = lines[p_position.line];
+ ERR_FAIL_INDEX_V(p_position.character, line.size(), "");
+
+ int start_pos = p_position.character;
+ for (int c = p_position.character; c >= 0; c--) {
+ start_pos = c;
+ CharType ch = line[c];
+ bool valid_char = (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch == '_';
+ if (!valid_char) {
+ break;
+ }
+ }
+
+ int end_pos = p_position.character;
+ for (int c = p_position.character; c < line.length(); c++) {
+ CharType ch = line[c];
+ bool valid_char = (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch == '_';
+ if (!valid_char) {
+ break;
+ }
+ end_pos = c;
+ }
+ if (start_pos < end_pos) {
+ p_offset.x = start_pos - p_position.character;
+ p_offset.y = end_pos - p_position.character;
+ return line.substr(start_pos + 1, end_pos - start_pos);
+ }
+
+ return "";
+}
+
+String ExtendGDScriptParser::get_uri() const {
+ return GDScriptLanguageProtocol::get_singleton()->get_workspace()->get_file_uri(path);
+}
+
+const lsp::DocumentSymbol *ExtendGDScriptParser::search_symbol_defined_at_line(int p_line, const lsp::DocumentSymbol &p_parent) const {
+ const lsp::DocumentSymbol *ret = NULL;
+ if (p_line < p_parent.range.start.line) {
+ return ret;
+ } else if (p_parent.range.start.line == p_line) {
+ return &p_parent;
+ } else {
+ for (int i = 0; i < p_parent.children.size(); i++) {
+ ret = search_symbol_defined_at_line(p_line, p_parent.children[i]);
+ if (ret) {
+ break;
+ }
+ }
+ }
+ return ret;
+}
+
+String ExtendGDScriptParser::parse_documentation_as_markdown(int p_line, bool p_docs_down) {
+ return marked_documentation(parse_documentation(p_line, p_docs_down));
+}
+
+const lsp::DocumentSymbol *ExtendGDScriptParser::get_symbol_defined_at_line(int p_line) const {
+ if (p_line <= 0) {
+ return &class_symbol;
+ }
+ return search_symbol_defined_at_line(p_line, class_symbol);
+}
+
+const lsp::DocumentSymbol *ExtendGDScriptParser::get_member_symbol(const String &p_name, const String &p_subclass) const {
+
+ if (p_subclass.empty()) {
+ const lsp::DocumentSymbol *const *ptr = members.getptr(p_name);
+ if (ptr) {
+ return *ptr;
+ }
+ } else {
+ if (const ClassMembers *_class = inner_classes.getptr(p_subclass)) {
+ const lsp::DocumentSymbol *const *ptr = _class->getptr(p_name);
+ if (ptr) {
+ return *ptr;
+ }
+ }
+ }
+
+ return NULL;
+}
+
+const Array &ExtendGDScriptParser::get_member_completions() {
+
+ if (member_completions.empty()) {
+
+ const String *name = members.next(NULL);
+ while (name) {
+
+ const lsp::DocumentSymbol *symbol = members.get(*name);
+ lsp::CompletionItem item = symbol->make_completion_item();
+ item.data = JOIN_SYMBOLS(path, *name);
+ member_completions.push_back(item.to_json());
+
+ name = members.next(name);
+ }
+
+ const String *_class = inner_classes.next(NULL);
+ while (_class) {
+
+ const ClassMembers *inner_class = inner_classes.getptr(*_class);
+ const String *member_name = inner_class->next(NULL);
+ while (member_name) {
+ const lsp::DocumentSymbol *symbol = inner_class->get(*member_name);
+ lsp::CompletionItem item = symbol->make_completion_item();
+ item.data = JOIN_SYMBOLS(path, JOIN_SYMBOLS(*_class, *member_name));
+ member_completions.push_back(item.to_json());
+
+ member_name = inner_class->next(member_name);
+ }
+
+ _class = inner_classes.next(_class);
+ }
+ }
+
+ return member_completions;
+}
+
+Dictionary ExtendGDScriptParser::dump_function_api(const GDScriptParser::FunctionNode *p_func) const {
+ Dictionary func;
+ ERR_FAIL_NULL_V(p_func, func);
+ func["name"] = p_func->name;
+ func["return_type"] = p_func->return_type.to_string();
+ func["rpc_mode"] = p_func->rpc_mode;
+ Array arguments;
+ for (int i = 0; i < p_func->arguments.size(); i++) {
+ Dictionary arg;
+ arg["name"] = p_func->arguments[i];
+ arg["type"] = p_func->argument_types[i].to_string();
+ int default_value_idx = i - (p_func->arguments.size() - p_func->default_values.size());
+ if (default_value_idx >= 0) {
+ const GDScriptParser::ConstantNode *const_node = dynamic_cast<const GDScriptParser::ConstantNode *>(p_func->default_values[default_value_idx]);
+ if (const_node == NULL) {
+ const GDScriptParser::OperatorNode *operator_node = dynamic_cast<const GDScriptParser::OperatorNode *>(p_func->default_values[default_value_idx]);
+ if (operator_node) {
+ const_node = dynamic_cast<const GDScriptParser::ConstantNode *>(operator_node->next);
+ }
+ }
+ if (const_node) {
+ arg["default_value"] = const_node->value;
+ }
+ }
+ arguments.push_back(arg);
+ }
+ if (const lsp::DocumentSymbol *symbol = get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(p_func->line))) {
+ func["signature"] = symbol->detail;
+ func["description"] = symbol->documentation;
+ }
+ func["arguments"] = arguments;
+ return func;
+}
+
+Dictionary ExtendGDScriptParser::dump_class_api(const GDScriptParser::ClassNode *p_class) const {
+ Dictionary class_api;
+
+ ERR_FAIL_NULL_V(p_class, class_api);
+
+ class_api["name"] = String(p_class->name);
+ class_api["path"] = path;
+ Array extends_class;
+ for (int i = 0; i < p_class->extends_class.size(); i++) {
+ extends_class.append(String(p_class->extends_class[i]));
+ }
+ class_api["extends_class"] = extends_class;
+ class_api["extends_file"] = String(p_class->extends_file);
+ class_api["icon"] = String(p_class->icon_path);
+
+ if (const lsp::DocumentSymbol *symbol = get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(p_class->line))) {
+ class_api["signature"] = symbol->detail;
+ class_api["description"] = symbol->documentation;
+ }
+
+ Array subclasses;
+ for (int i = 0; i < p_class->subclasses.size(); i++) {
+ subclasses.push_back(dump_class_api(p_class->subclasses[i]));
+ }
+ class_api["sub_classes"] = subclasses;
+
+ Array constants;
+ for (Map<StringName, GDScriptParser::ClassNode::Constant>::Element *E = p_class->constant_expressions.front(); E; E = E->next()) {
+
+ const GDScriptParser::ClassNode::Constant &c = E->value();
+ const GDScriptParser::ConstantNode *node = dynamic_cast<const GDScriptParser::ConstantNode *>(c.expression);
+
+ Dictionary api;
+ api["name"] = E->key();
+ api["value"] = node->value;
+ api["data_type"] = node->datatype.to_string();
+ if (const lsp::DocumentSymbol *symbol = get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(node->line))) {
+ api["signature"] = symbol->detail;
+ api["description"] = symbol->documentation;
+ }
+ constants.push_back(api);
+ }
+ class_api["constants"] = constants;
+
+ Array members;
+ for (int i = 0; i < p_class->variables.size(); ++i) {
+ const GDScriptParser::ClassNode::Member &m = p_class->variables[i];
+ Dictionary api;
+ api["name"] = m.identifier;
+ api["data_type"] = m.data_type.to_string();
+ api["default_value"] = m.default_value;
+ api["setter"] = String(m.setter);
+ api["getter"] = String(m.getter);
+ api["export"] = m._export.type != Variant::NIL;
+ if (const lsp::DocumentSymbol *symbol = get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(m.line))) {
+ api["signature"] = symbol->detail;
+ api["description"] = symbol->documentation;
+ }
+ members.push_back(api);
+ }
+ class_api["members"] = members;
+
+ Array signals;
+ for (int i = 0; i < p_class->_signals.size(); ++i) {
+ const GDScriptParser::ClassNode::Signal &signal = p_class->_signals[i];
+ Dictionary api;
+ api["name"] = signal.name;
+ Array args;
+ for (int j = 0; j < signal.arguments.size(); j++) {
+ args.append(signal.arguments[j]);
+ }
+ api["arguments"] = args;
+ if (const lsp::DocumentSymbol *symbol = get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(signal.line))) {
+ api["signature"] = symbol->detail;
+ api["description"] = symbol->documentation;
+ }
+ signals.push_back(api);
+ }
+ class_api["signals"] = signals;
+
+ Array methods;
+ for (int i = 0; i < p_class->functions.size(); ++i) {
+ methods.append(dump_function_api(p_class->functions[i]));
+ }
+ class_api["methods"] = methods;
+
+ Array static_functions;
+ for (int i = 0; i < p_class->static_functions.size(); ++i) {
+ static_functions.append(dump_function_api(p_class->functions[i]));
+ }
+ class_api["static_functions"] = static_functions;
+
+ return class_api;
+}
+
+Dictionary ExtendGDScriptParser::generate_api() const {
+
+ Dictionary api;
+ const GDScriptParser::Node *head = get_parse_tree();
+ if (const GDScriptParser::ClassNode *gdclass = dynamic_cast<const GDScriptParser::ClassNode *>(head)) {
+ api = dump_class_api(gdclass);
+ }
+ return api;
+}
+
+Error ExtendGDScriptParser::parse(const String &p_code, const String &p_path) {
+ path = p_path;
+ lines = p_code.split("\n");
+
+ Error err = GDScriptParser::parse(p_code, p_path.get_base_dir(), false, p_path, false, NULL, false);
+ update_diagnostics();
+ update_symbols();
+ return err;
+}
diff --git a/modules/gdscript/language_server/gdscript_extend_parser.h b/modules/gdscript/language_server/gdscript_extend_parser.h
new file mode 100644
index 0000000000..dd0453d3ff
--- /dev/null
+++ b/modules/gdscript/language_server/gdscript_extend_parser.h
@@ -0,0 +1,103 @@
+/*************************************************************************/
+/* gdscript_extend_parser.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef GDSCRIPT_EXTEND_PARSER_H
+#define GDSCRIPT_EXTEND_PARSER_H
+
+#include "../gdscript_parser.h"
+#include "core/variant.h"
+#include "lsp.hpp"
+
+#ifndef LINE_NUMBER_TO_INDEX
+#define LINE_NUMBER_TO_INDEX(p_line) ((p_line)-1)
+#endif
+
+#ifndef SYMBOL_SEPERATOR
+#define SYMBOL_SEPERATOR "::"
+#endif
+
+#ifndef JOIN_SYMBOLS
+#define JOIN_SYMBOLS(p_path, name) ((p_path) + SYMBOL_SEPERATOR + (name))
+#endif
+
+typedef HashMap<String, const lsp::DocumentSymbol *> ClassMembers;
+
+class ExtendGDScriptParser : public GDScriptParser {
+
+ String path;
+ Vector<String> lines;
+
+ lsp::DocumentSymbol class_symbol;
+ Vector<lsp::Diagnostic> diagnostics;
+ ClassMembers members;
+ HashMap<String, ClassMembers> inner_classes;
+
+ void update_diagnostics();
+
+ void update_symbols();
+ void parse_class_symbol(const GDScriptParser::ClassNode *p_class, lsp::DocumentSymbol &r_symbol);
+ void parse_function_symbol(const GDScriptParser::FunctionNode *p_func, lsp::DocumentSymbol &r_symbol);
+
+ Dictionary dump_function_api(const GDScriptParser::FunctionNode *p_func) const;
+ Dictionary dump_class_api(const GDScriptParser::ClassNode *p_class) const;
+
+ String parse_documentation(int p_line, bool p_docs_down = false);
+ const lsp::DocumentSymbol *search_symbol_defined_at_line(int p_line, const lsp::DocumentSymbol &p_parent) const;
+
+ Array member_completions;
+
+ String parse_documentation_as_markdown(int p_line, bool p_docs_down = false);
+
+public:
+ static String marked_documentation(const String &p_bbcode);
+
+public:
+ _FORCE_INLINE_ const String &get_path() const { return path; }
+ _FORCE_INLINE_ const Vector<String> &get_lines() const { return lines; }
+ _FORCE_INLINE_ const lsp::DocumentSymbol &get_symbols() const { return class_symbol; }
+ _FORCE_INLINE_ const Vector<lsp::Diagnostic> &get_diagnostics() const { return diagnostics; }
+ _FORCE_INLINE_ const ClassMembers &get_members() const { return members; }
+ _FORCE_INLINE_ const HashMap<String, ClassMembers> &get_inner_classes() const { return inner_classes; }
+
+ String get_text_for_completion(const lsp::Position &p_cursor) const;
+ String get_text_for_lookup_symbol(const lsp::Position &p_cursor, const String &p_symbol = "", bool p_func_requred = false) const;
+ String get_identifier_under_position(const lsp::Position &p_position, Vector2i &p_offset) const;
+ String get_uri() const;
+
+ const lsp::DocumentSymbol *get_symbol_defined_at_line(int p_line) const;
+ const lsp::DocumentSymbol *get_member_symbol(const String &p_name, const String &p_subclass = "") const;
+
+ const Array &get_member_completions();
+ Dictionary generate_api() const;
+
+ Error parse(const String &p_code, const String &p_path);
+};
+
+#endif
diff --git a/modules/gdscript/language_server/gdscript_language_protocol.cpp b/modules/gdscript/language_server/gdscript_language_protocol.cpp
new file mode 100644
index 0000000000..afe461b68e
--- /dev/null
+++ b/modules/gdscript/language_server/gdscript_language_protocol.cpp
@@ -0,0 +1,211 @@
+/*************************************************************************/
+/* gdscript_language_protocol.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "gdscript_language_protocol.h"
+#include "core/io/json.h"
+#include "core/os/copymem.h"
+#include "core/project_settings.h"
+#include "editor/editor_node.h"
+
+GDScriptLanguageProtocol *GDScriptLanguageProtocol::singleton = NULL;
+
+void GDScriptLanguageProtocol::on_data_received(int p_id) {
+ lastest_client_id = p_id;
+ Ref<WebSocketPeer> peer = server->get_peer(p_id);
+ PoolByteArray data;
+ if (OK == peer->get_packet_buffer(data)) {
+ String message;
+ message.parse_utf8((const char *)data.read().ptr(), data.size());
+ if (message.begins_with("Content-Length:")) return;
+ String output = process_message(message);
+ if (!output.empty()) {
+ CharString charstr = output.utf8();
+ peer->put_packet((const uint8_t *)charstr.ptr(), charstr.length());
+ }
+ }
+}
+
+void GDScriptLanguageProtocol::on_client_connected(int p_id, const String &p_protocal) {
+ clients.set(p_id, server->get_peer(p_id));
+}
+
+void GDScriptLanguageProtocol::on_client_disconnected(int p_id, bool p_was_clean_close) {
+ clients.erase(p_id);
+}
+
+String GDScriptLanguageProtocol::process_message(const String &p_text) {
+ String ret = process_string(p_text);
+ if (ret.empty()) {
+ return ret;
+ } else {
+ return format_output(ret);
+ }
+}
+
+String GDScriptLanguageProtocol::format_output(const String &p_text) {
+
+ String header = "Content-Length: ";
+ CharString charstr = p_text.utf8();
+ size_t len = charstr.length();
+ header += itos(len);
+ header += "\r\n\r\n";
+
+ return header + p_text;
+}
+
+void GDScriptLanguageProtocol::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("initialize", "params"), &GDScriptLanguageProtocol::initialize);
+ ClassDB::bind_method(D_METHOD("initialized", "params"), &GDScriptLanguageProtocol::initialized);
+ ClassDB::bind_method(D_METHOD("on_data_received"), &GDScriptLanguageProtocol::on_data_received);
+ ClassDB::bind_method(D_METHOD("on_client_connected"), &GDScriptLanguageProtocol::on_client_connected);
+ ClassDB::bind_method(D_METHOD("on_client_disconnected"), &GDScriptLanguageProtocol::on_client_disconnected);
+ ClassDB::bind_method(D_METHOD("notify_all_clients", "p_method", "p_params"), &GDScriptLanguageProtocol::notify_all_clients, DEFVAL(Variant()));
+ ClassDB::bind_method(D_METHOD("notify_client", "p_method", "p_params", "p_client"), &GDScriptLanguageProtocol::notify_client, DEFVAL(Variant()), DEFVAL(-1));
+ ClassDB::bind_method(D_METHOD("is_smart_resolve_enabled"), &GDScriptLanguageProtocol::is_smart_resolve_enabled);
+ ClassDB::bind_method(D_METHOD("get_text_document"), &GDScriptLanguageProtocol::get_text_document);
+ ClassDB::bind_method(D_METHOD("get_workspace"), &GDScriptLanguageProtocol::get_workspace);
+ ClassDB::bind_method(D_METHOD("is_initialized"), &GDScriptLanguageProtocol::is_initialized);
+}
+
+Dictionary GDScriptLanguageProtocol::initialize(const Dictionary &p_params) {
+
+ lsp::InitializeResult ret;
+
+ String root_uri = p_params["rootUri"];
+ String root = p_params["rootPath"];
+ bool is_same_workspace = root == workspace->root;
+ is_same_workspace = root.to_lower() == workspace->root.to_lower();
+#ifdef WINDOWS_ENABLED
+ is_same_workspace = root.replace("\\", "/").to_lower() == workspace->root.to_lower();
+#endif
+
+ if (root_uri.length() && is_same_workspace) {
+ workspace->root_uri = root_uri;
+ } else {
+
+ workspace->root_uri = "file://" + workspace->root;
+
+ Dictionary params;
+ params["path"] = workspace->root;
+ Dictionary request = make_notification("gdscrip_client/changeWorkspace", params);
+ if (Ref<WebSocketPeer> *peer = clients.getptr(lastest_client_id)) {
+ String msg = JSON::print(request);
+ msg = format_output(msg);
+ CharString charstr = msg.utf8();
+ (*peer)->put_packet((const uint8_t *)charstr.ptr(), charstr.length());
+ }
+ }
+
+ if (!_initialized) {
+ workspace->initialize();
+ text_document->initialize();
+ _initialized = true;
+ }
+
+ return ret.to_json();
+}
+
+void GDScriptLanguageProtocol::initialized(const Variant &p_params) {
+}
+
+void GDScriptLanguageProtocol::poll() {
+ server->poll();
+}
+
+Error GDScriptLanguageProtocol::start(int p_port) {
+ if (server == NULL) {
+ server = dynamic_cast<WebSocketServer *>(ClassDB::instance("WebSocketServer"));
+ server->set_buffers(8192, 1024, 8192, 1024); // 8mb should be way more than enough
+ server->connect("data_received", this, "on_data_received");
+ server->connect("client_connected", this, "on_client_connected");
+ server->connect("client_disconnected", this, "on_client_disconnected");
+ }
+ return server->listen(p_port);
+}
+
+void GDScriptLanguageProtocol::stop() {
+ server->stop();
+}
+
+void GDScriptLanguageProtocol::notify_all_clients(const String &p_method, const Variant &p_params) {
+
+ Dictionary message = make_notification(p_method, p_params);
+ String msg = JSON::print(message);
+ msg = format_output(msg);
+ CharString charstr = msg.utf8();
+ const int *p_id = clients.next(NULL);
+ while (p_id != NULL) {
+ Ref<WebSocketPeer> peer = clients.get(*p_id);
+ (*peer)->put_packet((const uint8_t *)charstr.ptr(), charstr.length());
+ p_id = clients.next(p_id);
+ }
+}
+
+void GDScriptLanguageProtocol::notify_client(const String &p_method, const Variant &p_params, int p_client) {
+
+ if (p_client == -1) {
+ p_client = lastest_client_id;
+ }
+
+ Ref<WebSocketPeer> *peer = clients.getptr(p_client);
+ ERR_FAIL_COND(peer == NULL);
+
+ Dictionary message = make_notification(p_method, p_params);
+ String msg = JSON::print(message);
+ msg = format_output(msg);
+ CharString charstr = msg.utf8();
+
+ (*peer)->put_packet((const uint8_t *)charstr.ptr(), charstr.length());
+}
+
+bool GDScriptLanguageProtocol::is_smart_resolve_enabled() const {
+ return bool(_EDITOR_GET("network/language_server/enable_smart_resolve"));
+}
+
+bool GDScriptLanguageProtocol::is_goto_native_symbols_enabled() const {
+ return bool(_EDITOR_GET("network/language_server/show_native_symbols_in_editor"));
+}
+
+GDScriptLanguageProtocol::GDScriptLanguageProtocol() {
+ server = NULL;
+ singleton = this;
+ _initialized = false;
+ workspace.instance();
+ text_document.instance();
+ set_scope("textDocument", text_document.ptr());
+ set_scope("completionItem", text_document.ptr());
+ set_scope("workspace", workspace.ptr());
+ workspace->root = ProjectSettings::get_singleton()->get_resource_path();
+}
+
+GDScriptLanguageProtocol::~GDScriptLanguageProtocol() {
+ memdelete(server);
+ server = NULL;
+}
diff --git a/modules/gdscript/language_server/gdscript_language_protocol.h b/modules/gdscript/language_server/gdscript_language_protocol.h
new file mode 100644
index 0000000000..136b45fd78
--- /dev/null
+++ b/modules/gdscript/language_server/gdscript_language_protocol.h
@@ -0,0 +1,93 @@
+/*************************************************************************/
+/* gdscript_language_protocol.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef GDSCRIPT_PROTOCAL_SERVER_H
+#define GDSCRIPT_PROTOCAL_SERVER_H
+
+#include "gdscript_text_document.h"
+#include "gdscript_workspace.h"
+#include "lsp.hpp"
+#include "modules/jsonrpc/jsonrpc.h"
+#include "modules/websocket/websocket_peer.h"
+#include "modules/websocket/websocket_server.h"
+
+class GDScriptLanguageProtocol : public JSONRPC {
+ GDCLASS(GDScriptLanguageProtocol, JSONRPC)
+
+ enum LSPErrorCode {
+ RequestCancelled = -32800,
+ ContentModified = -32801,
+ };
+
+ static GDScriptLanguageProtocol *singleton;
+
+ HashMap<int, Ref<WebSocketPeer> > clients;
+ WebSocketServer *server;
+ int lastest_client_id;
+
+ Ref<GDScriptTextDocument> text_document;
+ Ref<GDScriptWorkspace> workspace;
+
+ void on_data_received(int p_id);
+ void on_client_connected(int p_id, const String &p_protocal);
+ void on_client_disconnected(int p_id, bool p_was_clean_close);
+
+ String process_message(const String &p_text);
+ String format_output(const String &p_text);
+
+ bool _initialized;
+
+protected:
+ static void _bind_methods();
+
+ Dictionary initialize(const Dictionary &p_params);
+ void initialized(const Variant &p_params);
+
+public:
+ _FORCE_INLINE_ static GDScriptLanguageProtocol *get_singleton() { return singleton; }
+ _FORCE_INLINE_ Ref<GDScriptWorkspace> get_workspace() { return workspace; }
+ _FORCE_INLINE_ Ref<GDScriptTextDocument> get_text_document() { return text_document; }
+ _FORCE_INLINE_ bool is_initialized() const { return _initialized; }
+
+ void poll();
+ Error start(int p_port);
+ void stop();
+
+ void notify_all_clients(const String &p_method, const Variant &p_params = Variant());
+ void notify_client(const String &p_method, const Variant &p_params = Variant(), int p_client = -1);
+
+ bool is_smart_resolve_enabled() const;
+ bool is_goto_native_symbols_enabled() const;
+
+ GDScriptLanguageProtocol();
+ ~GDScriptLanguageProtocol();
+};
+
+#endif
diff --git a/modules/gdscript/language_server/gdscript_language_server.cpp b/modules/gdscript/language_server/gdscript_language_server.cpp
new file mode 100644
index 0000000000..9bea4557ac
--- /dev/null
+++ b/modules/gdscript/language_server/gdscript_language_server.cpp
@@ -0,0 +1,88 @@
+/*************************************************************************/
+/* gdscript_language_server.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "gdscript_language_server.h"
+#include "core/os/file_access.h"
+#include "core/os/os.h"
+#include "editor/editor_node.h"
+
+GDScriptLanguageServer::GDScriptLanguageServer() {
+ thread = NULL;
+ thread_exit = false;
+ _EDITOR_DEF("network/language_server/remote_port", 6008);
+ _EDITOR_DEF("network/language_server/enable_smart_resolve", false);
+ _EDITOR_DEF("network/language_server/show_native_symbols_in_editor", false);
+}
+
+void GDScriptLanguageServer::_notification(int p_what) {
+
+ switch (p_what) {
+ case NOTIFICATION_ENTER_TREE:
+ start();
+ break;
+ case NOTIFICATION_EXIT_TREE:
+ stop();
+ break;
+ }
+}
+
+void GDScriptLanguageServer::thread_main(void *p_userdata) {
+ GDScriptLanguageServer *self = static_cast<GDScriptLanguageServer *>(p_userdata);
+ while (!self->thread_exit) {
+ self->protocol.poll();
+ OS::get_singleton()->delay_usec(10);
+ }
+}
+
+void GDScriptLanguageServer::start() {
+ int port = (int)_EDITOR_GET("network/language_server/remote_port");
+ if (protocol.start(port) == OK) {
+ EditorNode::get_log()->add_message("** GDScript Language Server Started **");
+ ERR_FAIL_COND(thread != NULL || thread_exit);
+ thread_exit = false;
+ thread = Thread::create(GDScriptLanguageServer::thread_main, this);
+ }
+}
+
+void GDScriptLanguageServer::stop() {
+ ERR_FAIL_COND(NULL == thread || thread_exit);
+ thread_exit = true;
+ Thread::wait_to_finish(thread);
+ memdelete(thread);
+ thread = NULL;
+ protocol.stop();
+ EditorNode::get_log()->add_message("** GDScript Language Server Stopped **");
+}
+
+void register_lsp_types() {
+ ClassDB::register_class<GDScriptLanguageProtocol>();
+ ClassDB::register_class<GDScriptTextDocument>();
+ ClassDB::register_class<GDScriptWorkspace>();
+}
diff --git a/modules/gdscript/language_server/gdscript_language_server.h b/modules/gdscript/language_server/gdscript_language_server.h
new file mode 100644
index 0000000000..83c2320d98
--- /dev/null
+++ b/modules/gdscript/language_server/gdscript_language_server.h
@@ -0,0 +1,60 @@
+/*************************************************************************/
+/* gdscript_language_server.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef GDSCRIPT_LANGUAGE_SERVER_H
+#define GDSCRIPT_LANGUAGE_SERVER_H
+
+#include "../gdscript_parser.h"
+#include "editor/editor_plugin.h"
+#include "gdscript_language_protocol.h"
+
+class GDScriptLanguageServer : public EditorPlugin {
+ GDCLASS(GDScriptLanguageServer, EditorPlugin);
+
+ GDScriptLanguageProtocol protocol;
+
+ Thread *thread;
+ bool thread_exit;
+ static void thread_main(void *p_userdata);
+
+private:
+ void _notification(int p_what);
+ void _iteration();
+
+public:
+ Error parse_script_file(const String &p_path);
+ GDScriptLanguageServer();
+ void start();
+ void stop();
+};
+
+void register_lsp_types();
+
+#endif // GDSCRIPT_LANGUAGE_SERVER_H
diff --git a/modules/gdscript/language_server/gdscript_text_document.cpp b/modules/gdscript/language_server/gdscript_text_document.cpp
new file mode 100644
index 0000000000..f211fae526
--- /dev/null
+++ b/modules/gdscript/language_server/gdscript_text_document.cpp
@@ -0,0 +1,391 @@
+/*************************************************************************/
+/* gdscript_text_document.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "gdscript_text_document.h"
+#include "../gdscript.h"
+#include "core/os/os.h"
+#include "editor/editor_settings.h"
+#include "editor/plugins/script_text_editor.h"
+#include "gdscript_extend_parser.h"
+#include "gdscript_language_protocol.h"
+
+void GDScriptTextDocument::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("didOpen"), &GDScriptTextDocument::didOpen);
+ ClassDB::bind_method(D_METHOD("didChange"), &GDScriptTextDocument::didChange);
+ ClassDB::bind_method(D_METHOD("documentSymbol"), &GDScriptTextDocument::documentSymbol);
+ ClassDB::bind_method(D_METHOD("completion"), &GDScriptTextDocument::completion);
+ ClassDB::bind_method(D_METHOD("resolve"), &GDScriptTextDocument::resolve);
+ ClassDB::bind_method(D_METHOD("foldingRange"), &GDScriptTextDocument::foldingRange);
+ ClassDB::bind_method(D_METHOD("codeLens"), &GDScriptTextDocument::codeLens);
+ ClassDB::bind_method(D_METHOD("documentLink"), &GDScriptTextDocument::documentLink);
+ ClassDB::bind_method(D_METHOD("colorPresentation"), &GDScriptTextDocument::colorPresentation);
+ ClassDB::bind_method(D_METHOD("hover"), &GDScriptTextDocument::hover);
+ ClassDB::bind_method(D_METHOD("definition"), &GDScriptTextDocument::definition);
+ ClassDB::bind_method(D_METHOD("show_native_symbol_in_editor"), &GDScriptTextDocument::show_native_symbol_in_editor);
+}
+
+void GDScriptTextDocument::didOpen(const Variant &p_param) {
+ lsp::TextDocumentItem doc = load_document_item(p_param);
+ sync_script_content(doc.uri, doc.text);
+}
+
+void GDScriptTextDocument::didChange(const Variant &p_param) {
+ lsp::TextDocumentItem doc = load_document_item(p_param);
+ Dictionary dict = p_param;
+ Array contentChanges = dict["contentChanges"];
+ for (int i = 0; i < contentChanges.size(); ++i) {
+ lsp::TextDocumentContentChangeEvent evt;
+ evt.load(contentChanges[i]);
+ doc.text = evt.text;
+ }
+ sync_script_content(doc.uri, doc.text);
+}
+
+lsp::TextDocumentItem GDScriptTextDocument::load_document_item(const Variant &p_param) {
+ lsp::TextDocumentItem doc;
+ Dictionary params = p_param;
+ doc.load(params["textDocument"]);
+ return doc;
+}
+
+void GDScriptTextDocument::initialize() {
+
+ if (GDScriptLanguageProtocol::get_singleton()->is_smart_resolve_enabled()) {
+
+ const HashMap<StringName, ClassMembers> &native_members = GDScriptLanguageProtocol::get_singleton()->get_workspace()->native_members;
+
+ const StringName *class_ptr = native_members.next(NULL);
+ while (class_ptr) {
+
+ const ClassMembers &members = native_members.get(*class_ptr);
+
+ const String *name = members.next(NULL);
+ while (name) {
+
+ const lsp::DocumentSymbol *symbol = members.get(*name);
+ lsp::CompletionItem item = symbol->make_completion_item();
+ item.data = JOIN_SYMBOLS(String(*class_ptr), *name);
+ native_member_completions.push_back(item.to_json());
+
+ name = members.next(name);
+ }
+
+ class_ptr = native_members.next(class_ptr);
+ }
+ }
+}
+
+Array GDScriptTextDocument::documentSymbol(const Dictionary &p_params) {
+ Dictionary params = p_params["textDocument"];
+ String uri = params["uri"];
+ String path = GDScriptLanguageProtocol::get_singleton()->get_workspace()->get_file_path(uri);
+ Array arr;
+ if (const Map<String, ExtendGDScriptParser *>::Element *parser = GDScriptLanguageProtocol::get_singleton()->get_workspace()->scripts.find(path)) {
+ Vector<lsp::DocumentedSymbolInformation> list;
+ parser->get()->get_symbols().symbol_tree_as_list(uri, list);
+ for (int i = 0; i < list.size(); i++) {
+ arr.push_back(list[i].to_json());
+ }
+ }
+ return arr;
+}
+
+Array GDScriptTextDocument::completion(const Dictionary &p_params) {
+
+ Array arr;
+
+ lsp::CompletionParams params;
+ params.load(p_params);
+ Dictionary request_data = params.to_json();
+
+ List<ScriptCodeCompletionOption> options;
+ GDScriptLanguageProtocol::get_singleton()->get_workspace()->completion(params, &options);
+
+ if (!options.empty()) {
+
+ int i = 0;
+ arr.resize(options.size());
+
+ for (const List<ScriptCodeCompletionOption>::Element *E = options.front(); E; E = E->next()) {
+
+ const ScriptCodeCompletionOption &option = E->get();
+ lsp::CompletionItem item;
+ item.label = option.display;
+ item.data = request_data;
+
+ switch (option.kind) {
+ case ScriptCodeCompletionOption::KIND_ENUM:
+ item.kind = lsp::CompletionItemKind::Enum;
+ break;
+ case ScriptCodeCompletionOption::KIND_CLASS:
+ item.kind = lsp::CompletionItemKind::Class;
+ break;
+ case ScriptCodeCompletionOption::KIND_MEMBER:
+ item.kind = lsp::CompletionItemKind::Property;
+ break;
+ case ScriptCodeCompletionOption::KIND_FUNCTION:
+ item.kind = lsp::CompletionItemKind::Method;
+ break;
+ case ScriptCodeCompletionOption::KIND_SIGNAL:
+ item.kind = lsp::CompletionItemKind::Event;
+ break;
+ case ScriptCodeCompletionOption::KIND_CONSTANT:
+ item.kind = lsp::CompletionItemKind::Constant;
+ break;
+ case ScriptCodeCompletionOption::KIND_VARIABLE:
+ item.kind = lsp::CompletionItemKind::Variable;
+ break;
+ case ScriptCodeCompletionOption::KIND_FILE_PATH:
+ item.kind = lsp::CompletionItemKind::File;
+ break;
+ case ScriptCodeCompletionOption::KIND_NODE_PATH:
+ item.kind = lsp::CompletionItemKind::Snippet;
+ break;
+ case ScriptCodeCompletionOption::KIND_PLAIN_TEXT:
+ item.kind = lsp::CompletionItemKind::Text;
+ break;
+ }
+
+ arr[i] = item.to_json();
+ i++;
+ }
+ } else if (GDScriptLanguageProtocol::get_singleton()->is_smart_resolve_enabled()) {
+
+ arr = native_member_completions.duplicate();
+
+ for (Map<String, ExtendGDScriptParser *>::Element *E = GDScriptLanguageProtocol::get_singleton()->get_workspace()->scripts.front(); E; E = E->next()) {
+
+ ExtendGDScriptParser *script = E->get();
+ const Array &items = script->get_member_completions();
+
+ const int start_size = arr.size();
+ arr.resize(start_size + items.size());
+ for (int i = start_size; i < arr.size(); i++) {
+ arr[i] = items[i - start_size];
+ }
+ }
+ }
+ return arr;
+}
+
+Dictionary GDScriptTextDocument::resolve(const Dictionary &p_params) {
+
+ lsp::CompletionItem item;
+ item.load(p_params);
+
+ lsp::CompletionParams params;
+ Variant data = p_params["data"];
+
+ const lsp::DocumentSymbol *symbol = NULL;
+
+ if (data.get_type() == Variant::DICTIONARY) {
+
+ params.load(p_params["data"]);
+ symbol = GDScriptLanguageProtocol::get_singleton()->get_workspace()->resolve_symbol(params, item.label, item.kind == lsp::CompletionItemKind::Method || item.kind == lsp::CompletionItemKind::Function);
+
+ } else if (data.get_type() == Variant::STRING) {
+
+ String query = data;
+
+ Vector<String> param_symbols = query.split(SYMBOL_SEPERATOR, false);
+
+ if (param_symbols.size() >= 2) {
+
+ String class_ = param_symbols[0];
+ StringName class_name = class_;
+ String member_name = param_symbols[param_symbols.size() - 1];
+ String inner_class_name;
+ if (param_symbols.size() >= 3) {
+ inner_class_name = param_symbols[1];
+ }
+
+ if (const ClassMembers *members = GDScriptLanguageProtocol::get_singleton()->get_workspace()->native_members.getptr(class_name)) {
+ if (const lsp::DocumentSymbol *const *member = members->getptr(member_name)) {
+ symbol = *member;
+ }
+ }
+
+ if (!symbol) {
+ if (const Map<String, ExtendGDScriptParser *>::Element *E = GDScriptLanguageProtocol::get_singleton()->get_workspace()->scripts.find(class_name)) {
+ symbol = E->get()->get_member_symbol(member_name, inner_class_name);
+ }
+ }
+ }
+ }
+
+ if (symbol) {
+ item.documentation = symbol->render();
+ }
+
+ if ((item.kind == lsp::CompletionItemKind::Method || item.kind == lsp::CompletionItemKind::Function) && !item.label.ends_with("):")) {
+ item.insertText = item.label + "(";
+ if (symbol && symbol->detail.find(",") == -1) {
+ item.insertText += ")";
+ }
+ } else if (item.kind == lsp::CompletionItemKind::Event) {
+ if (params.context.triggerKind == lsp::CompletionTriggerKind::TriggerCharacter && (params.context.triggerCharacter == "(")) {
+ const String quote_style = EDITOR_DEF("text_editor/completion/use_single_quotes", false) ? "'" : "\"";
+ item.insertText = quote_style + item.label + quote_style;
+ }
+ }
+
+ return item.to_json(true);
+}
+
+Array GDScriptTextDocument::foldingRange(const Dictionary &p_params) {
+ Dictionary params = p_params["textDocument"];
+ String path = params["uri"];
+ Array arr;
+ return arr;
+}
+
+Array GDScriptTextDocument::codeLens(const Dictionary &p_params) {
+ Array arr;
+ return arr;
+}
+
+Variant GDScriptTextDocument::documentLink(const Dictionary &p_params) {
+ Variant ret;
+ return ret;
+}
+
+Array GDScriptTextDocument::colorPresentation(const Dictionary &p_params) {
+ Array arr;
+ return arr;
+}
+
+Variant GDScriptTextDocument::hover(const Dictionary &p_params) {
+
+ lsp::TextDocumentPositionParams params;
+ params.load(p_params);
+
+ const lsp::DocumentSymbol *symbol = GDScriptLanguageProtocol::get_singleton()->get_workspace()->resolve_symbol(params);
+ if (symbol) {
+
+ lsp::Hover hover;
+ hover.contents = symbol->render();
+ return hover.to_json();
+
+ } else if (GDScriptLanguageProtocol::get_singleton()->is_smart_resolve_enabled()) {
+
+ Dictionary ret;
+ Array contents;
+ List<const lsp::DocumentSymbol *> list;
+ GDScriptLanguageProtocol::get_singleton()->get_workspace()->resolve_related_symbols(params, list);
+ for (List<const lsp::DocumentSymbol *>::Element *E = list.front(); E; E = E->next()) {
+ if (const lsp::DocumentSymbol *s = E->get()) {
+ contents.push_back(s->render().value);
+ }
+ }
+ ret["contents"] = contents;
+ return ret;
+ }
+
+ return Variant();
+}
+
+Array GDScriptTextDocument::definition(const Dictionary &p_params) {
+ Array arr;
+
+ lsp::TextDocumentPositionParams params;
+ params.load(p_params);
+
+ const lsp::DocumentSymbol *symbol = GDScriptLanguageProtocol::get_singleton()->get_workspace()->resolve_symbol(params);
+ if (symbol) {
+ lsp::Location location;
+ location.uri = symbol->uri;
+ location.range = symbol->range;
+
+ const String &path = GDScriptLanguageProtocol::get_singleton()->get_workspace()->get_file_path(symbol->uri);
+ if (file_checker->file_exists(path)) {
+ arr.push_back(location.to_json());
+ } else if (!symbol->native_class.empty() && GDScriptLanguageProtocol::get_singleton()->is_goto_native_symbols_enabled()) {
+ String id;
+ switch (symbol->kind) {
+ case lsp::SymbolKind::Class:
+ id = "class_name:" + symbol->name;
+ break;
+ case lsp::SymbolKind::Constant:
+ id = "class_constant:" + symbol->native_class + ":" + symbol->name;
+ break;
+ case lsp::SymbolKind::Property:
+ case lsp::SymbolKind::Variable:
+ id = "class_property:" + symbol->native_class + ":" + symbol->name;
+ break;
+ case lsp::SymbolKind::Enum:
+ id = "class_enum:" + symbol->native_class + ":" + symbol->name;
+ break;
+ case lsp::SymbolKind::Method:
+ case lsp::SymbolKind::Function:
+ id = "class_method:" + symbol->native_class + ":" + symbol->name;
+ break;
+ default:
+ id = "class_global:" + symbol->native_class + ":" + symbol->name;
+ break;
+ }
+ call_deferred("show_native_symbol_in_editor", id);
+ }
+ } else if (GDScriptLanguageProtocol::get_singleton()->is_smart_resolve_enabled()) {
+
+ List<const lsp::DocumentSymbol *> list;
+ GDScriptLanguageProtocol::get_singleton()->get_workspace()->resolve_related_symbols(params, list);
+ for (List<const lsp::DocumentSymbol *>::Element *E = list.front(); E; E = E->next()) {
+
+ if (const lsp::DocumentSymbol *s = E->get()) {
+ if (!s->uri.empty()) {
+ lsp::Location location;
+ location.uri = s->uri;
+ location.range = s->range;
+ arr.push_back(location.to_json());
+ }
+ }
+ }
+ }
+
+ return arr;
+}
+
+GDScriptTextDocument::GDScriptTextDocument() {
+ file_checker = FileAccess::create(FileAccess::ACCESS_RESOURCES);
+}
+
+GDScriptTextDocument::~GDScriptTextDocument() {
+ memdelete(file_checker);
+}
+
+void GDScriptTextDocument::sync_script_content(const String &p_uri, const String &p_content) {
+ String path = GDScriptLanguageProtocol::get_singleton()->get_workspace()->get_file_path(p_uri);
+ GDScriptLanguageProtocol::get_singleton()->get_workspace()->parse_script(path, p_content);
+}
+
+void GDScriptTextDocument::show_native_symbol_in_editor(const String &p_symbol_id) {
+ ScriptEditor::get_singleton()->call_deferred("_help_class_goto", p_symbol_id);
+ OS::get_singleton()->move_window_to_foreground();
+}
diff --git a/modules/gdscript/language_server/gdscript_text_document.h b/modules/gdscript/language_server/gdscript_text_document.h
new file mode 100644
index 0000000000..d15022d2c4
--- /dev/null
+++ b/modules/gdscript/language_server/gdscript_text_document.h
@@ -0,0 +1,73 @@
+/*************************************************************************/
+/* gdscript_text_document.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef GDSCRIPT_TEXT_DOCUMENT_H
+#define GDSCRIPT_TEXT_DOCUMENT_H
+
+#include "core/os/file_access.h"
+#include "core/reference.h"
+#include "lsp.hpp"
+
+class GDScriptTextDocument : public Reference {
+ GDCLASS(GDScriptTextDocument, Reference)
+protected:
+ static void _bind_methods();
+
+ FileAccess *file_checker;
+
+ void didOpen(const Variant &p_param);
+ void didChange(const Variant &p_param);
+
+ void sync_script_content(const String &p_path, const String &p_content);
+ void show_native_symbol_in_editor(const String &p_symbol_id);
+
+ Array native_member_completions;
+
+private:
+ lsp::TextDocumentItem load_document_item(const Variant &p_param);
+
+public:
+ Array documentSymbol(const Dictionary &p_params);
+ Array completion(const Dictionary &p_params);
+ Dictionary resolve(const Dictionary &p_params);
+ Array foldingRange(const Dictionary &p_params);
+ Array codeLens(const Dictionary &p_params);
+ Variant documentLink(const Dictionary &p_params);
+ Array colorPresentation(const Dictionary &p_params);
+ Variant hover(const Dictionary &p_params);
+ Array definition(const Dictionary &p_params);
+
+ void initialize();
+
+ GDScriptTextDocument();
+ virtual ~GDScriptTextDocument();
+};
+
+#endif
diff --git a/modules/gdscript/language_server/gdscript_workspace.cpp b/modules/gdscript/language_server/gdscript_workspace.cpp
new file mode 100644
index 0000000000..1901daacff
--- /dev/null
+++ b/modules/gdscript/language_server/gdscript_workspace.cpp
@@ -0,0 +1,504 @@
+/*************************************************************************/
+/* gdscript_workspace.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "gdscript_workspace.h"
+#include "../gdscript.h"
+#include "../gdscript_parser.h"
+#include "core/project_settings.h"
+#include "core/script_language.h"
+#include "editor/editor_help.h"
+#include "gdscript_language_protocol.h"
+
+void GDScriptWorkspace::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("symbol"), &GDScriptWorkspace::symbol);
+ ClassDB::bind_method(D_METHOD("parse_script", "p_path", "p_content"), &GDScriptWorkspace::parse_script);
+ ClassDB::bind_method(D_METHOD("parse_local_script", "p_path"), &GDScriptWorkspace::parse_local_script);
+ ClassDB::bind_method(D_METHOD("get_file_path", "p_uri"), &GDScriptWorkspace::get_file_path);
+ ClassDB::bind_method(D_METHOD("get_file_uri", "p_path"), &GDScriptWorkspace::get_file_uri);
+ ClassDB::bind_method(D_METHOD("publish_diagnostics", "p_path"), &GDScriptWorkspace::publish_diagnostics);
+ ClassDB::bind_method(D_METHOD("generate_script_api", "p_path"), &GDScriptWorkspace::generate_script_api);
+}
+
+void GDScriptWorkspace::remove_cache_parser(const String &p_path) {
+ Map<String, ExtendGDScriptParser *>::Element *parser = parse_results.find(p_path);
+ Map<String, ExtendGDScriptParser *>::Element *script = scripts.find(p_path);
+ if (parser && script) {
+ if (script->get() && script->get() == script->get()) {
+ memdelete(script->get());
+ } else {
+ memdelete(script->get());
+ memdelete(parser->get());
+ }
+ parse_results.erase(p_path);
+ scripts.erase(p_path);
+ } else if (parser) {
+ memdelete(parser->get());
+ parse_results.erase(p_path);
+ } else if (script) {
+ memdelete(script->get());
+ scripts.erase(p_path);
+ }
+}
+
+const lsp::DocumentSymbol *GDScriptWorkspace::get_native_symbol(const String &p_class, const String &p_member) const {
+
+ StringName class_name = p_class;
+ StringName empty;
+
+ while (class_name != empty) {
+ if (const Map<StringName, lsp::DocumentSymbol>::Element *E = native_symbols.find(class_name)) {
+ const lsp::DocumentSymbol &class_symbol = E->value();
+
+ if (p_member.empty()) {
+ return &class_symbol;
+ } else {
+ for (int i = 0; i < class_symbol.children.size(); i++) {
+ const lsp::DocumentSymbol &symbol = class_symbol.children[i];
+ if (symbol.name == p_member) {
+ return &symbol;
+ }
+ }
+ }
+ }
+ class_name = ClassDB::get_parent_class(class_name);
+ }
+
+ return NULL;
+}
+
+const lsp::DocumentSymbol *GDScriptWorkspace::get_script_symbol(const String &p_path) const {
+ const Map<String, ExtendGDScriptParser *>::Element *S = scripts.find(p_path);
+ if (S) {
+ return &(S->get()->get_symbols());
+ }
+ return NULL;
+}
+
+void GDScriptWorkspace::reload_all_workspace_scripts() {
+ List<String> pathes;
+ list_script_files("res://", pathes);
+ for (List<String>::Element *E = pathes.front(); E; E = E->next()) {
+ const String &path = E->get();
+ Error err;
+ String content = FileAccess::get_file_as_string(path, &err);
+ ERR_CONTINUE(err != OK);
+ err = parse_script(path, content);
+
+ if (err != OK) {
+ Map<String, ExtendGDScriptParser *>::Element *S = parse_results.find(path);
+ String err_msg = "Failed parse script " + path;
+ if (S) {
+ err_msg += "\n" + S->get()->get_error();
+ }
+ ERR_EXPLAIN(err_msg);
+ ERR_CONTINUE(err != OK);
+ }
+ }
+}
+
+void GDScriptWorkspace::list_script_files(const String &p_root_dir, List<String> &r_files) {
+ Error err;
+ DirAccessRef dir = DirAccess::open(p_root_dir, &err);
+ if (OK == err) {
+ dir->list_dir_begin();
+ String file_name = dir->get_next();
+ while (file_name.length()) {
+ if (dir->current_is_dir() && file_name != "." && file_name != ".." && file_name != "./") {
+ list_script_files(p_root_dir.plus_file(file_name), r_files);
+ } else if (file_name.ends_with(".gd")) {
+ String script_file = p_root_dir.plus_file(file_name);
+ r_files.push_back(script_file);
+ }
+ file_name = dir->get_next();
+ }
+ }
+}
+
+ExtendGDScriptParser *GDScriptWorkspace::get_parse_successed_script(const String &p_path) {
+ const Map<String, ExtendGDScriptParser *>::Element *S = scripts.find(p_path);
+ if (!S) {
+ parse_local_script(p_path);
+ S = scripts.find(p_path);
+ }
+ if (S) {
+ return S->get();
+ }
+ return NULL;
+}
+
+ExtendGDScriptParser *GDScriptWorkspace::get_parse_result(const String &p_path) {
+ const Map<String, ExtendGDScriptParser *>::Element *S = parse_results.find(p_path);
+ if (!S) {
+ parse_local_script(p_path);
+ S = parse_results.find(p_path);
+ }
+ if (S) {
+ return S->get();
+ }
+ return NULL;
+}
+
+Array GDScriptWorkspace::symbol(const Dictionary &p_params) {
+ String query = p_params["query"];
+ Array arr;
+ if (!query.empty()) {
+ for (Map<String, ExtendGDScriptParser *>::Element *E = scripts.front(); E; E = E->next()) {
+ Vector<lsp::DocumentedSymbolInformation> script_symbols;
+ E->get()->get_symbols().symbol_tree_as_list(E->key(), script_symbols);
+ for (int i = 0; i < script_symbols.size(); ++i) {
+ if (query.is_subsequence_ofi(script_symbols[i].name)) {
+ arr.push_back(script_symbols[i].to_json());
+ }
+ }
+ }
+ }
+ return arr;
+}
+
+Error GDScriptWorkspace::initialize() {
+ if (initialized) return OK;
+
+ DocData *doc = EditorHelp::get_doc_data();
+ for (Map<String, DocData::ClassDoc>::Element *E = doc->class_list.front(); E; E = E->next()) {
+
+ const DocData::ClassDoc &class_data = E->value();
+ lsp::DocumentSymbol class_symbol;
+ String class_name = E->key();
+ class_symbol.name = class_name;
+ class_symbol.native_class = class_name;
+ class_symbol.kind = lsp::SymbolKind::Class;
+ class_symbol.detail = String("<Native> class ") + class_name;
+ if (!class_data.inherits.empty()) {
+ class_symbol.detail += " extends " + class_data.inherits;
+ }
+ class_symbol.documentation = ExtendGDScriptParser::marked_documentation(class_data.brief_description) + "\n" + ExtendGDScriptParser::marked_documentation(class_data.description);
+
+ for (int i = 0; i < class_data.constants.size(); i++) {
+ const DocData::ConstantDoc &const_data = class_data.constants[i];
+ lsp::DocumentSymbol symbol;
+ symbol.name = const_data.name;
+ symbol.native_class = class_name;
+ symbol.kind = lsp::SymbolKind::Constant;
+ symbol.detail = "const " + class_name + "." + const_data.name;
+ if (const_data.enumeration.length()) {
+ symbol.detail += ": " + const_data.enumeration;
+ }
+ symbol.detail += " = " + const_data.value;
+ symbol.documentation = ExtendGDScriptParser::marked_documentation(const_data.description);
+ class_symbol.children.push_back(symbol);
+ }
+
+ Vector<DocData::PropertyDoc> properties;
+ properties.append_array(class_data.properties);
+ const int theme_prop_start_idx = properties.size();
+ properties.append_array(class_data.theme_properties);
+
+ for (int i = 0; i < class_data.properties.size(); i++) {
+ const DocData::PropertyDoc &data = class_data.properties[i];
+ lsp::DocumentSymbol symbol;
+ symbol.name = data.name;
+ symbol.native_class = class_name;
+ symbol.kind = lsp::SymbolKind::Property;
+ symbol.detail = String(i >= theme_prop_start_idx ? "<Theme> var" : "var") + " " + class_name + "." + data.name;
+ if (data.enumeration.length()) {
+ symbol.detail += ": " + data.enumeration;
+ } else {
+ symbol.detail += ": " + data.type;
+ }
+ symbol.documentation = ExtendGDScriptParser::marked_documentation(data.description);
+ class_symbol.children.push_back(symbol);
+ }
+
+ Vector<DocData::MethodDoc> methods_signals;
+ methods_signals.append_array(class_data.methods);
+ const int signal_start_idx = methods_signals.size();
+ methods_signals.append_array(class_data.signals);
+
+ for (int i = 0; i < methods_signals.size(); i++) {
+ const DocData::MethodDoc &data = methods_signals[i];
+
+ lsp::DocumentSymbol symbol;
+ symbol.name = data.name;
+ symbol.native_class = class_name;
+ symbol.kind = i >= signal_start_idx ? lsp::SymbolKind::Event : lsp::SymbolKind::Method;
+
+ String params = "";
+ bool arg_default_value_started = false;
+ for (int j = 0; j < data.arguments.size(); j++) {
+ const DocData::ArgumentDoc &arg = data.arguments[j];
+ if (!arg_default_value_started && !arg.default_value.empty()) {
+ arg_default_value_started = true;
+ }
+ String arg_str = arg.name + ": " + arg.type;
+ if (arg_default_value_started) {
+ arg_str += " = " + arg.default_value;
+ }
+ if (j < data.arguments.size() - 1) {
+ arg_str += ", ";
+ }
+ params += arg_str;
+ }
+ if (data.qualifiers.find("vararg") != -1) {
+ params += params.empty() ? "..." : ", ...";
+ }
+
+ symbol.detail = "func " + class_name + "." + data.name + "(" + params + ") -> " + data.return_type;
+ symbol.documentation = ExtendGDScriptParser::marked_documentation(data.description);
+ class_symbol.children.push_back(symbol);
+ }
+
+ native_symbols.insert(class_name, class_symbol);
+ }
+
+ reload_all_workspace_scripts();
+
+ if (GDScriptLanguageProtocol::get_singleton()->is_smart_resolve_enabled()) {
+ for (Map<StringName, lsp::DocumentSymbol>::Element *E = native_symbols.front(); E; E = E->next()) {
+ ClassMembers members;
+ const lsp::DocumentSymbol &class_symbol = E->get();
+ for (int i = 0; i < class_symbol.children.size(); i++) {
+ const lsp::DocumentSymbol &symbol = class_symbol.children[i];
+ members.set(symbol.name, &symbol);
+ }
+ native_members.set(E->key(), members);
+ }
+
+ // cache member completions
+ for (Map<String, ExtendGDScriptParser *>::Element *S = scripts.front(); S; S = S->next()) {
+ S->get()->get_member_completions();
+ }
+ }
+
+ return OK;
+}
+
+Error GDScriptWorkspace::parse_script(const String &p_path, const String &p_content) {
+
+ ExtendGDScriptParser *parser = memnew(ExtendGDScriptParser);
+ Error err = parser->parse(p_content, p_path);
+ Map<String, ExtendGDScriptParser *>::Element *last_parser = parse_results.find(p_path);
+ Map<String, ExtendGDScriptParser *>::Element *last_script = scripts.find(p_path);
+
+ if (err == OK) {
+
+ remove_cache_parser(p_path);
+ parse_results[p_path] = parser;
+ scripts[p_path] = parser;
+
+ } else {
+ if (last_parser && last_script && last_parser->get() != last_script->get()) {
+ memdelete(last_parser->get());
+ }
+ parse_results[p_path] = parser;
+ }
+
+ publish_diagnostics(p_path);
+
+ return err;
+}
+
+Error GDScriptWorkspace::parse_local_script(const String &p_path) {
+ Error err;
+ String content = FileAccess::get_file_as_string(p_path, &err);
+ if (err == OK) {
+ err = parse_script(p_path, content);
+ }
+ return err;
+}
+
+String GDScriptWorkspace::get_file_path(const String &p_uri) const {
+ String path = p_uri;
+ path = path.replace(root_uri + "/", "res://");
+ path = path.http_unescape();
+ return path;
+}
+
+String GDScriptWorkspace::get_file_uri(const String &p_path) const {
+ String uri = p_path;
+ uri = uri.replace("res://", root_uri + "/");
+ return uri;
+}
+
+void GDScriptWorkspace::publish_diagnostics(const String &p_path) {
+ Dictionary params;
+ Array errors;
+ const Map<String, ExtendGDScriptParser *>::Element *ele = parse_results.find(p_path);
+ if (ele) {
+ const Vector<lsp::Diagnostic> &list = ele->get()->get_diagnostics();
+ errors.resize(list.size());
+ for (int i = 0; i < list.size(); ++i) {
+ errors[i] = list[i].to_json();
+ }
+ }
+ params["diagnostics"] = errors;
+ params["uri"] = get_file_uri(p_path);
+ GDScriptLanguageProtocol::get_singleton()->notify_client("textDocument/publishDiagnostics", params);
+}
+
+void GDScriptWorkspace::completion(const lsp::CompletionParams &p_params, List<ScriptCodeCompletionOption> *r_options) {
+
+ String path = get_file_path(p_params.textDocument.uri);
+ String call_hint;
+ bool forced = false;
+
+ if (const ExtendGDScriptParser *parser = get_parse_result(path)) {
+ String code = parser->get_text_for_completion(p_params.position);
+ GDScriptLanguage::get_singleton()->complete_code(code, path, NULL, r_options, forced, call_hint);
+ }
+}
+
+const lsp::DocumentSymbol *GDScriptWorkspace::resolve_symbol(const lsp::TextDocumentPositionParams &p_doc_pos, const String &p_symbol_name, bool p_func_requred) {
+
+ const lsp::DocumentSymbol *symbol = NULL;
+
+ String path = get_file_path(p_doc_pos.textDocument.uri);
+ if (const ExtendGDScriptParser *parser = get_parse_result(path)) {
+
+ String symbol_identifier = p_symbol_name;
+ Vector<String> identifier_parts = symbol_identifier.split("(");
+ if (identifier_parts.size()) {
+ symbol_identifier = identifier_parts[0];
+ }
+
+ lsp::Position pos = p_doc_pos.position;
+ if (symbol_identifier.empty()) {
+ Vector2i offset;
+ symbol_identifier = parser->get_identifier_under_position(p_doc_pos.position, offset);
+ pos.character += offset.y;
+ }
+
+ if (!symbol_identifier.empty()) {
+
+ if (ScriptServer::is_global_class(symbol_identifier)) {
+
+ String class_path = ScriptServer::get_global_class_path(symbol_identifier);
+ symbol = get_script_symbol(class_path);
+
+ } else {
+
+ ScriptLanguage::LookupResult ret;
+ if (OK == GDScriptLanguage::get_singleton()->lookup_code(parser->get_text_for_lookup_symbol(pos, symbol_identifier, p_func_requred), symbol_identifier, path, NULL, ret)) {
+
+ if (ret.type == ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION) {
+
+ String target_script_path = path;
+ if (!ret.script.is_null()) {
+ target_script_path = ret.script->get_path();
+ }
+
+ if (const ExtendGDScriptParser *target_parser = get_parse_result(target_script_path)) {
+ symbol = target_parser->get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(ret.location));
+ }
+
+ } else {
+
+ String member = ret.class_member;
+ if (member.empty() && symbol_identifier != ret.class_name) {
+ member = symbol_identifier;
+ }
+ symbol = get_native_symbol(ret.class_name, member);
+ }
+ } else {
+ symbol = parser->get_member_symbol(symbol_identifier);
+ }
+ }
+ }
+ }
+
+ return symbol;
+}
+
+void GDScriptWorkspace::resolve_related_symbols(const lsp::TextDocumentPositionParams &p_doc_pos, List<const lsp::DocumentSymbol *> &r_list) {
+
+ String path = get_file_path(p_doc_pos.textDocument.uri);
+ if (const ExtendGDScriptParser *parser = get_parse_result(path)) {
+
+ String symbol_identifier;
+ Vector2i offset;
+ symbol_identifier = parser->get_identifier_under_position(p_doc_pos.position, offset);
+
+ const StringName *class_ptr = native_members.next(NULL);
+ while (class_ptr) {
+ const ClassMembers &members = native_members.get(*class_ptr);
+ if (const lsp::DocumentSymbol *const *symbol = members.getptr(symbol_identifier)) {
+ r_list.push_back(*symbol);
+ }
+ class_ptr = native_members.next(class_ptr);
+ }
+
+ for (Map<String, ExtendGDScriptParser *>::Element *E = scripts.front(); E; E = E->next()) {
+ const ExtendGDScriptParser *script = E->get();
+ const ClassMembers &members = script->get_members();
+ if (const lsp::DocumentSymbol *const *symbol = members.getptr(symbol_identifier)) {
+ r_list.push_back(*symbol);
+ }
+
+ const HashMap<String, ClassMembers> &inner_classes = script->get_inner_classes();
+ const String *_class = inner_classes.next(NULL);
+ while (_class) {
+
+ const ClassMembers *inner_class = inner_classes.getptr(*_class);
+ if (const lsp::DocumentSymbol *const *symbol = inner_class->getptr(symbol_identifier)) {
+ r_list.push_back(*symbol);
+ }
+
+ _class = inner_classes.next(_class);
+ }
+ }
+ }
+}
+
+Dictionary GDScriptWorkspace::generate_script_api(const String &p_path) {
+ Dictionary api;
+ if (const ExtendGDScriptParser *parser = get_parse_successed_script(p_path)) {
+ api = parser->generate_api();
+ }
+ return api;
+}
+
+GDScriptWorkspace::GDScriptWorkspace() {
+ ProjectSettings::get_singleton()->get_resource_path();
+}
+
+GDScriptWorkspace::~GDScriptWorkspace() {
+ Set<String> cached_parsers;
+
+ for (Map<String, ExtendGDScriptParser *>::Element *E = parse_results.front(); E; E = E->next()) {
+ cached_parsers.insert(E->key());
+ }
+
+ for (Map<String, ExtendGDScriptParser *>::Element *E = scripts.front(); E; E = E->next()) {
+ cached_parsers.insert(E->key());
+ }
+
+ for (Set<String>::Element *E = cached_parsers.front(); E; E = E->next()) {
+ remove_cache_parser(E->get());
+ }
+}
diff --git a/modules/gdscript/language_server/gdscript_workspace.h b/modules/gdscript/language_server/gdscript_workspace.h
new file mode 100644
index 0000000000..adce169d4b
--- /dev/null
+++ b/modules/gdscript/language_server/gdscript_workspace.h
@@ -0,0 +1,91 @@
+/*************************************************************************/
+/* gdscript_workspace.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef GDSCRIPT_WORKSPACE_H
+#define GDSCRIPT_WORKSPACE_H
+
+#include "../gdscript_parser.h"
+#include "core/variant.h"
+#include "gdscript_extend_parser.h"
+#include "lsp.hpp"
+
+class GDScriptWorkspace : public Reference {
+ GDCLASS(GDScriptWorkspace, Reference);
+
+protected:
+ static void _bind_methods();
+ void remove_cache_parser(const String &p_path);
+ bool initialized = false;
+ Map<StringName, lsp::DocumentSymbol> native_symbols;
+
+ const lsp::DocumentSymbol *get_native_symbol(const String &p_class, const String &p_member = "") const;
+ const lsp::DocumentSymbol *get_script_symbol(const String &p_path) const;
+
+ void reload_all_workspace_scripts();
+
+ ExtendGDScriptParser *get_parse_successed_script(const String &p_path);
+ ExtendGDScriptParser *get_parse_result(const String &p_path);
+
+ void strip_flat_symbols(const String &p_branch);
+ void list_script_files(const String &p_root_dir, List<String> &r_files);
+
+public:
+ String root;
+ String root_uri;
+
+ Map<String, ExtendGDScriptParser *> scripts;
+ Map<String, ExtendGDScriptParser *> parse_results;
+ HashMap<StringName, ClassMembers> native_members;
+
+public:
+ Array symbol(const Dictionary &p_params);
+
+public:
+ Error initialize();
+
+ Error parse_script(const String &p_path, const String &p_content);
+ Error parse_local_script(const String &p_path);
+
+ String get_file_path(const String &p_uri) const;
+ String get_file_uri(const String &p_path) const;
+
+ void publish_diagnostics(const String &p_path);
+ void completion(const lsp::CompletionParams &p_params, List<ScriptCodeCompletionOption> *r_options);
+
+ const lsp::DocumentSymbol *resolve_symbol(const lsp::TextDocumentPositionParams &p_doc_pos, const String &p_symbol_name = "", bool p_func_requred = false);
+ void resolve_related_symbols(const lsp::TextDocumentPositionParams &p_doc_pos, List<const lsp::DocumentSymbol *> &r_list);
+
+ Dictionary generate_script_api(const String &p_path);
+
+ GDScriptWorkspace();
+ ~GDScriptWorkspace();
+};
+
+#endif
diff --git a/modules/gdscript/language_server/lsp.hpp b/modules/gdscript/language_server/lsp.hpp
new file mode 100644
index 0000000000..3e57b6ee7e
--- /dev/null
+++ b/modules/gdscript/language_server/lsp.hpp
@@ -0,0 +1,1506 @@
+/*************************************************************************/
+/* lsp.hpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef GODOT_LSP_H
+#define GODOT_LSP_H
+
+#include "core/variant.h"
+
+namespace lsp {
+
+typedef String DocumentUri;
+
+/**
+ * Text documents are identified using a URI. On the protocol level, URIs are passed as strings.
+ */
+struct TextDocumentIdentifier {
+ /**
+ * The text document's URI.
+ */
+ DocumentUri uri;
+
+ _FORCE_INLINE_ void load(const Dictionary &p_params) {
+ uri = p_params["uri"];
+ }
+
+ _FORCE_INLINE_ Dictionary to_json() const {
+ Dictionary dict;
+ dict["uri"] = uri;
+ return dict;
+ }
+};
+
+/**
+ * Position in a text document expressed as zero-based line and zero-based character offset.
+ * A position is between two characters like an ‘insert’ cursor in a editor.
+ * Special values like for example -1 to denote the end of a line are not supported.
+ */
+struct Position {
+ /**
+ * Line position in a document (zero-based).
+ */
+ int line = 0;
+
+ /**
+ * Character offset on a line in a document (zero-based). Assuming that the line is
+ * represented as a string, the `character` value represents the gap between the
+ * `character` and `character + 1`.
+ *
+ * If the character value is greater than the line length it defaults back to the
+ * line length.
+ */
+ int character = 0;
+
+ _FORCE_INLINE_ void load(const Dictionary &p_params) {
+ line = p_params["line"];
+ character = p_params["character"];
+ }
+
+ _FORCE_INLINE_ Dictionary to_json() const {
+ Dictionary dict;
+ dict["line"] = line;
+ dict["character"] = character;
+ return dict;
+ }
+};
+
+/**
+ * A range in a text document expressed as (zero-based) start and end positions.
+ * A range is comparable to a selection in an editor. Therefore the end position is exclusive.
+ * If you want to specify a range that contains a line including the line ending character(s) then use an end position denoting the start of the next line.
+ */
+struct Range {
+ /**
+ * The range's start position.
+ */
+ Position start;
+
+ /**
+ * The range's end position.
+ */
+ Position end;
+
+ _FORCE_INLINE_ void load(const Dictionary &p_params) {
+ start.load(p_params["start"]);
+ end.load(p_params["end"]);
+ }
+
+ _FORCE_INLINE_ Dictionary to_json() const {
+ Dictionary dict;
+ dict["start"] = start.to_json();
+ dict["end"] = end.to_json();
+ return dict;
+ }
+};
+
+/**
+ * Represents a location inside a resource, such as a line inside a text file.
+ */
+struct Location {
+ DocumentUri uri;
+ Range range;
+
+ _FORCE_INLINE_ void load(const Dictionary &p_params) {
+ uri = p_params["uri"];
+ range.load(p_params["range"]);
+ }
+
+ _FORCE_INLINE_ Dictionary to_json() const {
+ Dictionary dict;
+ dict["uri"] = uri;
+ dict["range"] = range.to_json();
+ return dict;
+ }
+};
+
+/**
+ * Represents a link between a source and a target location.
+ */
+struct LocationLink {
+
+ /**
+ * Span of the origin of this link.
+ *
+ * Used as the underlined span for mouse interaction. Defaults to the word range at
+ * the mouse position.
+ */
+ Range *originSelectionRange = NULL;
+
+ /**
+ * The target resource identifier of this link.
+ */
+ String targetUri;
+
+ /**
+ * The full target range of this link. If the target for example is a symbol then target range is the
+ * range enclosing this symbol not including leading/trailing whitespace but everything else
+ * like comments. This information is typically used to highlight the range in the editor.
+ */
+ Range targetRange;
+
+ /**
+ * The range that should be selected and revealed when this link is being followed, e.g the name of a function.
+ * Must be contained by the the `targetRange`. See also `DocumentSymbol#range`
+ */
+ Range targetSelectionRange;
+};
+
+/**
+ * A parameter literal used in requests to pass a text document and a position inside that document.
+ */
+struct TextDocumentPositionParams {
+ /**
+ * The text document.
+ */
+ TextDocumentIdentifier textDocument;
+
+ /**
+ * The position inside the text document.
+ */
+ Position position;
+
+ _FORCE_INLINE_ void load(const Dictionary &p_params) {
+ textDocument.load(p_params["textDocument"]);
+ position.load(p_params["position"]);
+ }
+
+ _FORCE_INLINE_ Dictionary to_json() const {
+ Dictionary dict;
+ dict["textDocument"] = textDocument.to_json();
+ dict["position"] = position.to_json();
+ return dict;
+ }
+};
+
+/**
+ * A textual edit applicable to a text document.
+ */
+struct TextEdit {
+ /**
+ * The range of the text document to be manipulated. To insert
+ * text into a document create a range where start === end.
+ */
+ Range range;
+
+ /**
+ * The string to be inserted. For delete operations use an
+ * empty string.
+ */
+ String newText;
+};
+
+/**
+ * Represents a reference to a command.
+ * Provides a title which will be used to represent a command in the UI.
+ * Commands are identified by a string identifier.
+ * The recommended way to handle commands is to implement their execution on the server side if the client and server provides the corresponding capabilities.
+ * Alternatively the tool extension code could handle the command. The protocol currently doesn’t specify a set of well-known commands.
+ */
+struct Command {
+ /**
+ * Title of the command, like `save`.
+ */
+ String title;
+ /**
+ * The identifier of the actual command handler.
+ */
+ String command;
+ /**
+ * Arguments that the command handler should be
+ * invoked with.
+ */
+ Array arguments;
+
+ Dictionary to_json() const {
+ Dictionary dict;
+ dict["title"] = title;
+ dict["command"] = command;
+ if (arguments.size()) dict["arguments"] = arguments;
+ return dict;
+ }
+};
+
+namespace TextDocumentSyncKind {
+/**
+ * Documents should not be synced at all.
+ */
+static const int None = 0;
+
+/**
+ * Documents are synced by always sending the full content
+ * of the document.
+ */
+static const int Full = 1;
+
+/**
+ * Documents are synced by sending the full content on open.
+ * After that only incremental updates to the document are
+ * send.
+ */
+static const int Incremental = 2;
+}; // namespace TextDocumentSyncKind
+
+/**
+ * Completion options.
+ */
+struct CompletionOptions {
+ /**
+ * The server provides support to resolve additional
+ * information for a completion item.
+ */
+ bool resolveProvider = true;
+
+ /**
+ * The characters that trigger completion automatically.
+ */
+ Vector<String> triggerCharacters;
+
+ CompletionOptions() {
+ triggerCharacters.push_back(".");
+ triggerCharacters.push_back("$");
+ triggerCharacters.push_back("'");
+ triggerCharacters.push_back("\"");
+ triggerCharacters.push_back("(");
+ triggerCharacters.push_back(",");
+ }
+
+ Dictionary to_json() const {
+ Dictionary dict;
+ dict["resolveProvider"] = resolveProvider;
+ dict["triggerCharacters"] = triggerCharacters;
+ return dict;
+ }
+};
+
+/**
+ * Signature help options.
+ */
+struct SignatureHelpOptions {
+ /**
+ * The characters that trigger signature help
+ * automatically.
+ */
+ Vector<String> triggerCharacters;
+
+ Dictionary to_json() {
+ Dictionary dict;
+ dict["triggerCharacters"] = triggerCharacters;
+ return dict;
+ }
+};
+
+/**
+ * Code Lens options.
+ */
+struct CodeLensOptions {
+ /**
+ * Code lens has a resolve provider as well.
+ */
+ bool resolveProvider = false;
+
+ Dictionary to_json() {
+ Dictionary dict;
+ dict["resolveProvider"] = resolveProvider;
+ return dict;
+ }
+};
+
+/**
+ * Rename options
+ */
+struct RenameOptions {
+ /**
+ * Renames should be checked and tested before being executed.
+ */
+ bool prepareProvider = false;
+
+ Dictionary to_json() {
+ Dictionary dict;
+ dict["prepareProvider"] = prepareProvider;
+ return dict;
+ }
+};
+
+/**
+ * Document link options.
+ */
+struct DocumentLinkOptions {
+ /**
+ * Document links have a resolve provider as well.
+ */
+ bool resolveProvider = false;
+
+ Dictionary to_json() {
+ Dictionary dict;
+ dict["resolveProvider"] = resolveProvider;
+ return dict;
+ }
+};
+
+/**
+ * Execute command options.
+ */
+struct ExecuteCommandOptions {
+ /**
+ * The commands to be executed on the server
+ */
+ Vector<String> commands;
+
+ Dictionary to_json() {
+ Dictionary dict;
+ dict["commands"] = commands;
+ return dict;
+ }
+};
+
+/**
+ * Save options.
+ */
+struct SaveOptions {
+ /**
+ * The client is supposed to include the content on save.
+ */
+ bool includeText = true;
+
+ Dictionary to_json() {
+ Dictionary dict;
+ dict["includeText"] = includeText;
+ return dict;
+ }
+};
+
+/**
+ * Color provider options.
+ */
+struct ColorProviderOptions {
+ Dictionary to_json() {
+ Dictionary dict;
+ return dict;
+ }
+};
+
+/**
+ * Folding range provider options.
+ */
+struct FoldingRangeProviderOptions {
+ Dictionary to_json() {
+ Dictionary dict;
+ return dict;
+ }
+};
+
+struct TextDocumentSyncOptions {
+ /**
+ * Open and close notifications are sent to the server. If omitted open close notification should not
+ * be sent.
+ */
+ bool openClose = true;
+
+ /**
+ * Change notifications are sent to the server. See TextDocumentSyncKind.None, TextDocumentSyncKind.Full
+ * and TextDocumentSyncKind.Incremental. If omitted it defaults to TextDocumentSyncKind.None.
+ */
+ int change = TextDocumentSyncKind::Full;
+
+ /**
+ * If present will save notifications are sent to the server. If omitted the notification should not be
+ * sent.
+ */
+ bool willSave = false;
+
+ /**
+ * If present will save wait until requests are sent to the server. If omitted the request should not be
+ * sent.
+ */
+ bool willSaveWaitUntil = false;
+
+ /**
+ * If present save notifications are sent to the server. If omitted the notification should not be
+ * sent.
+ */
+ SaveOptions save;
+
+ Dictionary to_json() {
+ Dictionary dict;
+ dict["willSaveWaitUntil"] = willSaveWaitUntil;
+ dict["willSave"] = willSave;
+ dict["openClose"] = openClose;
+ dict["change"] = change;
+ dict["change"] = save.to_json();
+ return dict;
+ }
+};
+
+/**
+ * Static registration options to be returned in the initialize request.
+ */
+struct StaticRegistrationOptions {
+ /**
+ * The id used to register the request. The id can be used to deregister
+ * the request again. See also Registration#id.
+ */
+ String id;
+};
+
+/**
+ * Format document on type options.
+ */
+struct DocumentOnTypeFormattingOptions {
+ /**
+ * A character on which formatting should be triggered, like `}`.
+ */
+ String firstTriggerCharacter;
+
+ /**
+ * More trigger characters.
+ */
+ Vector<String> moreTriggerCharacter;
+
+ Dictionary to_json() {
+ Dictionary dict;
+ dict["firstTriggerCharacter"] = firstTriggerCharacter;
+ dict["moreTriggerCharacter"] = moreTriggerCharacter;
+ return dict;
+ }
+};
+
+struct TextDocumentItem {
+ /**
+ * The text document's URI.
+ */
+ DocumentUri uri;
+
+ /**
+ * The text document's language identifier.
+ */
+ String languageId;
+
+ /**
+ * The version number of this document (it will increase after each
+ * change, including undo/redo).
+ */
+ int version;
+
+ /**
+ * The content of the opened text document.
+ */
+ String text;
+
+ void load(const Dictionary &p_dict) {
+ uri = p_dict["uri"];
+ languageId = p_dict["languageId"];
+ version = p_dict["version"];
+ text = p_dict["text"];
+ }
+
+ Dictionary to_json() const {
+ Dictionary dict;
+ dict["uri"] = uri;
+ dict["languageId"] = languageId;
+ dict["version"] = version;
+ dict["text"] = text;
+ return dict;
+ }
+};
+
+/**
+ * An event describing a change to a text document. If range and rangeLength are omitted
+ * the new text is considered to be the full content of the document.
+ */
+struct TextDocumentContentChangeEvent {
+ /**
+ * The range of the document that changed.
+ */
+ Range range;
+
+ /**
+ * The length of the range that got replaced.
+ */
+ int rangeLength;
+
+ /**
+ * The new text of the range/document.
+ */
+ String text;
+
+ void load(const Dictionary &p_params) {
+ text = p_params["text"];
+ rangeLength = p_params["rangeLength"];
+ range.load(p_params["range"]);
+ }
+};
+
+namespace DiagnosticSeverity {
+/**
+ * Reports an error.
+ */
+static const int Error = 1;
+/**
+ * Reports a warning.
+ */
+static const int Warning = 2;
+/**
+ * Reports an information.
+ */
+static const int Information = 3;
+/**
+ * Reports a hint.
+ */
+static const int Hint = 4;
+}; // namespace DiagnosticSeverity
+
+/**
+ * Represents a related message and source code location for a diagnostic. This should be
+ * used to point to code locations that cause or related to a diagnostics, e.g when duplicating
+ * a symbol in a scope.
+ */
+struct DiagnosticRelatedInformation {
+ /**
+ * The location of this related diagnostic information.
+ */
+ Location location;
+
+ /**
+ * The message of this related diagnostic information.
+ */
+ String message;
+
+ Dictionary to_json() const {
+ Dictionary dict;
+ dict["location"] = location.to_json(),
+ dict["message"] = message;
+ return dict;
+ }
+};
+
+/**
+ * Represents a diagnostic, such as a compiler error or warning.
+ * Diagnostic objects are only valid in the scope of a resource.
+ */
+struct Diagnostic {
+ /**
+ * The range at which the message applies.
+ */
+ Range range;
+
+ /**
+ * The diagnostic's severity. Can be omitted. If omitted it is up to the
+ * client to interpret diagnostics as error, warning, info or hint.
+ */
+ int severity;
+
+ /**
+ * The diagnostic's code, which might appear in the user interface.
+ */
+ int code;
+
+ /**
+ * A human-readable string describing the source of this
+ * diagnostic, e.g. 'typescript' or 'super lint'.
+ */
+ String source;
+
+ /**
+ * The diagnostic's message.
+ */
+ String message;
+
+ /**
+ * An array of related diagnostic information, e.g. when symbol-names within
+ * a scope collide all definitions can be marked via this property.
+ */
+ Vector<DiagnosticRelatedInformation> relatedInformation;
+
+ Dictionary to_json() const {
+ Dictionary dict;
+ dict["range"] = range.to_json();
+ dict["code"] = code;
+ dict["severity"] = severity;
+ dict["message"] = message;
+ dict["source"] = source;
+ if (!relatedInformation.empty()) {
+ Array arr;
+ arr.resize(relatedInformation.size());
+ for (int i = 0; i < relatedInformation.size(); i++) {
+ arr[i] = relatedInformation[i].to_json();
+ }
+ dict["relatedInformation"] = arr;
+ }
+ return dict;
+ }
+};
+
+/**
+ * Describes the content type that a client supports in various
+ * result literals like `Hover`, `ParameterInfo` or `CompletionItem`.
+ *
+ * Please note that `MarkupKinds` must not start with a `$`. This kinds
+ * are reserved for internal usage.
+ */
+namespace MarkupKind {
+static const String PlainText = "plaintext";
+static const String Markdown = "markdown";
+}; // namespace MarkupKind
+
+/**
+ * A `MarkupContent` literal represents a string value which content is interpreted base on its
+ * kind flag. Currently the protocol supports `plaintext` and `markdown` as markup kinds.
+ *
+ * If the kind is `markdown` then the value can contain fenced code blocks like in GitHub issues.
+ * See https://help.github.com/articles/creating-and-highlighting-code-blocks/#syntax-highlighting
+ *
+ * Here is an example how such a string can be constructed using JavaScript / TypeScript:
+ * ```typescript
+ * let markdown: MarkdownContent = {
+ * kind: MarkupKind.Markdown,
+ * value: [
+ * '# Header',
+ * 'Some text',
+ * '```typescript',
+ * 'someCode();',
+ * '```'
+ * ].join('\n')
+ * };
+ * ```
+ *
+ * *Please Note* that clients might sanitize the return markdown. A client could decide to
+ * remove HTML from the markdown to avoid script execution.
+ */
+struct MarkupContent {
+ /**
+ * The type of the Markup
+ */
+ String kind;
+
+ /**
+ * The content itself
+ */
+ String value;
+
+ MarkupContent() {
+ kind = MarkupKind::Markdown;
+ }
+
+ MarkupContent(const String &p_value) {
+ value = p_value;
+ kind = MarkupKind::Markdown;
+ }
+
+ Dictionary to_json() const {
+ Dictionary dict;
+ dict["kind"] = kind;
+ dict["value"] = value;
+ return dict;
+ }
+};
+
+/**
+ * The kind of a completion entry.
+ */
+namespace CompletionItemKind {
+static const int Text = 1;
+static const int Method = 2;
+static const int Function = 3;
+static const int Constructor = 4;
+static const int Field = 5;
+static const int Variable = 6;
+static const int Class = 7;
+static const int Interface = 8;
+static const int Module = 9;
+static const int Property = 10;
+static const int Unit = 11;
+static const int Value = 12;
+static const int Enum = 13;
+static const int Keyword = 14;
+static const int Snippet = 15;
+static const int Color = 16;
+static const int File = 17;
+static const int Reference = 18;
+static const int Folder = 19;
+static const int EnumMember = 20;
+static const int Constant = 21;
+static const int Struct = 22;
+static const int Event = 23;
+static const int Operator = 24;
+static const int TypeParameter = 25;
+}; // namespace CompletionItemKind
+
+/**
+ * Defines whether the insert text in a completion item should be interpreted as
+ * plain text or a snippet.
+ */
+namespace InsertTextFormat {
+/**
+ * The primary text to be inserted is treated as a plain string.
+ */
+static const int PlainText = 1;
+
+/**
+ * The primary text to be inserted is treated as a snippet.
+ *
+ * A snippet can define tab stops and placeholders with `$1`, `$2`
+ * and `${3:foo}`. `$0` defines the final tab stop, it defaults to
+ * the end of the snippet. Placeholders with equal identifiers are linked,
+ * that is typing in one will update others too.
+ */
+static const int Snippet = 2;
+}; // namespace InsertTextFormat
+
+struct CompletionItem {
+ /**
+ * The label of this completion item. By default
+ * also the text that is inserted when selecting
+ * this completion.
+ */
+ String label;
+
+ /**
+ * The kind of this completion item. Based of the kind
+ * an icon is chosen by the editor. The standardized set
+ * of available values is defined in `CompletionItemKind`.
+ */
+ int kind;
+
+ /**
+ * A human-readable string with additional information
+ * about this item, like type or symbol information.
+ */
+ String detail;
+
+ /**
+ * A human-readable string that represents a doc-comment.
+ */
+ MarkupContent documentation;
+
+ /**
+ * Indicates if this item is deprecated.
+ */
+ bool deprecated = false;
+
+ /**
+ * Select this item when showing.
+ *
+ * *Note* that only one completion item can be selected and that the
+ * tool / client decides which item that is. The rule is that the *first*
+ * item of those that match best is selected.
+ */
+ bool preselect = false;
+
+ /**
+ * A string that should be used when comparing this item
+ * with other items. When `falsy` the label is used.
+ */
+ String sortText;
+
+ /**
+ * A string that should be used when filtering a set of
+ * completion items. When `falsy` the label is used.
+ */
+ String filterText;
+
+ /**
+ * A string that should be inserted into a document when selecting
+ * this completion. When `falsy` the label is used.
+ *
+ * The `insertText` is subject to interpretation by the client side.
+ * Some tools might not take the string literally. For example
+ * VS Code when code complete is requested in this example `con<cursor position>`
+ * and a completion item with an `insertText` of `console` is provided it
+ * will only insert `sole`. Therefore it is recommended to use `textEdit` instead
+ * since it avoids additional client side interpretation.
+ *
+ * @deprecated Use textEdit instead.
+ */
+ String insertText;
+
+ /**
+ * The format of the insert text. The format applies to both the `insertText` property
+ * and the `newText` property of a provided `textEdit`.
+ */
+ int insertTextFormat;
+
+ /**
+ * An edit which is applied to a document when selecting this completion. When an edit is provided the value of
+ * `insertText` is ignored.
+ *
+ * *Note:* The range of the edit must be a single line range and it must contain the position at which completion
+ * has been requested.
+ */
+ TextEdit textEdit;
+
+ /**
+ * An optional array of additional text edits that are applied when
+ * selecting this completion. Edits must not overlap (including the same insert position)
+ * with the main edit nor with themselves.
+ *
+ * Additional text edits should be used to change text unrelated to the current cursor position
+ * (for example adding an import statement at the top of the file if the completion item will
+ * insert an unqualified type).
+ */
+ Vector<TextEdit> additionalTextEdits;
+
+ /**
+ * An optional set of characters that when pressed while this completion is active will accept it first and
+ * then type that character. *Note* that all commit characters should have `length=1` and that superfluous
+ * characters will be ignored.
+ */
+ Vector<String> commitCharacters;
+
+ /**
+ * An optional command that is executed *after* inserting this completion. *Note* that
+ * additional modifications to the current document should be described with the
+ * additionalTextEdits-property.
+ */
+ Command command;
+
+ /**
+ * A data entry field that is preserved on a completion item between
+ * a completion and a completion resolve request.
+ */
+ Variant data;
+
+ _FORCE_INLINE_ Dictionary to_json(bool resolved = false) const {
+ Dictionary dict;
+ dict["label"] = label;
+ dict["kind"] = kind;
+ dict["data"] = data;
+ if (resolved) {
+ dict["insertText"] = insertText;
+ dict["detail"] = detail;
+ dict["documentation"] = documentation.to_json();
+ dict["deprecated"] = deprecated;
+ dict["preselect"] = preselect;
+ dict["sortText"] = sortText;
+ dict["filterText"] = filterText;
+ if (commitCharacters.size()) dict["commitCharacters"] = commitCharacters;
+ dict["command"] = command.to_json();
+ }
+ return dict;
+ }
+
+ void load(const Dictionary &p_dict) {
+ if (p_dict.has("label")) label = p_dict["label"];
+ if (p_dict.has("kind")) kind = p_dict["kind"];
+ if (p_dict.has("detail")) detail = p_dict["detail"];
+ if (p_dict.has("documentation")) {
+ Variant doc = p_dict["documentation"];
+ if (doc.get_type() == Variant::STRING) {
+ documentation.value = doc;
+ } else if (doc.get_type() == Variant::DICTIONARY) {
+ Dictionary v = doc;
+ documentation.value = v["value"];
+ }
+ }
+ if (p_dict.has("deprecated")) deprecated = p_dict["deprecated"];
+ if (p_dict.has("preselect")) preselect = p_dict["preselect"];
+ if (p_dict.has("sortText")) sortText = p_dict["sortText"];
+ if (p_dict.has("filterText")) filterText = p_dict["filterText"];
+ if (p_dict.has("insertText")) insertText = p_dict["insertText"];
+ if (p_dict.has("data")) data = p_dict["data"];
+ }
+};
+
+/**
+ * Represents a collection of [completion items](#CompletionItem) to be presented
+ * in the editor.
+ */
+struct CompletionList {
+ /**
+ * This list it not complete. Further typing should result in recomputing
+ * this list.
+ */
+ bool isIncomplete;
+
+ /**
+ * The completion items.
+ */
+ Vector<CompletionItem> items;
+};
+
+/**
+ * A symbol kind.
+ */
+namespace SymbolKind {
+static const int File = 1;
+static const int Module = 2;
+static const int Namespace = 3;
+static const int Package = 4;
+static const int Class = 5;
+static const int Method = 6;
+static const int Property = 7;
+static const int Field = 8;
+static const int Constructor = 9;
+static const int Enum = 10;
+static const int Interface = 11;
+static const int Function = 12;
+static const int Variable = 13;
+static const int Constant = 14;
+static const int String = 15;
+static const int Number = 16;
+static const int Boolean = 17;
+static const int Array = 18;
+static const int Object = 19;
+static const int Key = 20;
+static const int Null = 21;
+static const int EnumMember = 22;
+static const int Struct = 23;
+static const int Event = 24;
+static const int Operator = 25;
+static const int TypeParameter = 26;
+}; // namespace SymbolKind
+
+/**
+ * Represents information about programming constructs like variables, classes,
+ * interfaces etc.
+ */
+struct SymbolInformation {
+ /**
+ * The name of this symbol.
+ */
+ String name;
+
+ /**
+ * The kind of this symbol.
+ */
+ int kind = SymbolKind::File;
+
+ /**
+ * Indicates if this symbol is deprecated.
+ */
+ bool deprecated = false;
+
+ /**
+ * The location of this symbol. The location's range is used by a tool
+ * to reveal the location in the editor. If the symbol is selected in the
+ * tool the range's start information is used to position the cursor. So
+ * the range usually spans more then the actual symbol's name and does
+ * normally include things like visibility modifiers.
+ *
+ * The range doesn't have to denote a node range in the sense of a abstract
+ * syntax tree. It can therefore not be used to re-construct a hierarchy of
+ * the symbols.
+ */
+ Location location;
+
+ /**
+ * The name of the symbol containing this symbol. This information is for
+ * user interface purposes (e.g. to render a qualifier in the user interface
+ * if necessary). It can't be used to re-infer a hierarchy for the document
+ * symbols.
+ */
+ String containerName;
+
+ _FORCE_INLINE_ Dictionary to_json() const {
+ Dictionary dict;
+ dict["name"] = name;
+ dict["kind"] = kind;
+ dict["deprecated"] = deprecated;
+ dict["location"] = location.to_json();
+ dict["containerName"] = containerName;
+ return dict;
+ }
+};
+
+struct DocumentedSymbolInformation : public SymbolInformation {
+ /**
+ * A human-readable string with additional information
+ */
+ String detail;
+
+ /**
+ * A human-readable string that represents a doc-comment.
+ */
+ String documentation;
+};
+
+/**
+ * Represents programming constructs like variables, classes, interfaces etc. that appear in a document. Document symbols can be
+ * hierarchical and they have two ranges: one that encloses its definition and one that points to its most interesting range,
+ * e.g. the range of an identifier.
+ */
+struct DocumentSymbol {
+
+ /**
+ * The name of this symbol. Will be displayed in the user interface and therefore must not be
+ * an empty string or a string only consisting of white spaces.
+ */
+ String name;
+
+ /**
+ * More detail for this symbol, e.g the signature of a function.
+ */
+ String detail;
+
+ /**
+ * Documentation for this symbol
+ */
+ String documentation;
+
+ /**
+ * Class name for the native symbols
+ */
+ String native_class;
+
+ /**
+ * The kind of this symbol.
+ */
+ int kind = SymbolKind::File;
+
+ /**
+ * Indicates if this symbol is deprecated.
+ */
+ bool deprecated = false;
+
+ /**
+ * The range enclosing this symbol not including leading/trailing whitespace but everything else
+ * like comments. This information is typically used to determine if the clients cursor is
+ * inside the symbol to reveal in the symbol in the UI.
+ */
+ Range range;
+
+ /**
+ * The range that should be selected and revealed when this symbol is being picked, e.g the name of a function.
+ * Must be contained by the `range`.
+ */
+ Range selectionRange;
+
+ DocumentUri uri;
+ String script_path;
+
+ /**
+ * Children of this symbol, e.g. properties of a class.
+ */
+ Vector<DocumentSymbol> children;
+
+ _FORCE_INLINE_ Dictionary to_json() const {
+ Dictionary dict;
+ dict["name"] = name;
+ dict["detail"] = detail;
+ dict["kind"] = kind;
+ dict["deprecated"] = deprecated;
+ dict["range"] = range.to_json();
+ dict["selectionRange"] = selectionRange.to_json();
+ Array arr;
+ arr.resize(children.size());
+ for (int i = 0; i < children.size(); i++) {
+ arr[i] = children[i].to_json();
+ }
+ dict["children"] = arr;
+ return dict;
+ }
+
+ void symbol_tree_as_list(const String &p_uri, Vector<DocumentedSymbolInformation> &r_list, const String &p_container = "", bool p_join_name = false) const {
+ DocumentedSymbolInformation si;
+ if (p_join_name && !p_container.empty()) {
+ si.name = p_container + ">" + name;
+ } else {
+ si.name = name;
+ }
+ si.kind = kind;
+ si.containerName = p_container;
+ si.deprecated = deprecated;
+ si.location.uri = p_uri;
+ si.location.range = range;
+ si.detail = detail;
+ si.documentation = documentation;
+ r_list.push_back(si);
+ for (int i = 0; i < children.size(); i++) {
+ children[i].symbol_tree_as_list(p_uri, r_list, si.name, p_join_name);
+ }
+ }
+
+ _FORCE_INLINE_ MarkupContent render() const {
+ MarkupContent markdown;
+ if (detail.length()) {
+ markdown.value = "\t" + detail + "\n\n";
+ }
+ if (documentation.length()) {
+ markdown.value += documentation + "\n\n";
+ }
+ if (script_path.length()) {
+ markdown.value += "Defined in [" + script_path + "](" + uri + ")";
+ }
+ return markdown;
+ }
+
+ _FORCE_INLINE_ CompletionItem make_completion_item(bool resolved = false) const {
+
+ lsp::CompletionItem item;
+ item.label = name;
+
+ if (resolved) {
+ item.documentation = render();
+ }
+
+ switch (kind) {
+ case lsp::SymbolKind::Enum:
+ item.kind = lsp::CompletionItemKind::Enum;
+ break;
+ case lsp::SymbolKind::Class:
+ item.kind = lsp::CompletionItemKind::Class;
+ break;
+ case lsp::SymbolKind::Property:
+ item.kind = lsp::CompletionItemKind::Property;
+ break;
+ case lsp::SymbolKind::Method:
+ case lsp::SymbolKind::Function:
+ item.kind = lsp::CompletionItemKind::Method;
+ break;
+ case lsp::SymbolKind::Event:
+ item.kind = lsp::CompletionItemKind::Event;
+ break;
+ case lsp::SymbolKind::Constant:
+ item.kind = lsp::CompletionItemKind::Constant;
+ break;
+ case lsp::SymbolKind::Variable:
+ item.kind = lsp::CompletionItemKind::Variable;
+ break;
+ case lsp::SymbolKind::File:
+ item.kind = lsp::CompletionItemKind::File;
+ break;
+ default:
+ item.kind = lsp::CompletionItemKind::Text;
+ break;
+ }
+
+ return item;
+ }
+};
+
+/**
+ * Enum of known range kinds
+ */
+namespace FoldingRangeKind {
+/**
+ * Folding range for a comment
+ */
+static const String Comment = "comment";
+/**
+ * Folding range for a imports or includes
+ */
+static const String Imports = "imports";
+/**
+ * Folding range for a region (e.g. `#region`)
+ */
+static const String Region = "region";
+} // namespace FoldingRangeKind
+
+/**
+ * Represents a folding range.
+ */
+struct FoldingRange {
+
+ /**
+ * The zero-based line number from where the folded range starts.
+ */
+ int startLine = 0;
+
+ /**
+ * The zero-based character offset from where the folded range starts. If not defined, defaults to the length of the start line.
+ */
+ int startCharacter = 0;
+
+ /**
+ * The zero-based line number where the folded range ends.
+ */
+ int endLine = 0;
+
+ /**
+ * The zero-based character offset before the folded range ends. If not defined, defaults to the length of the end line.
+ */
+ int endCharacter = 0;
+
+ /**
+ * Describes the kind of the folding range such as `comment' or 'region'. The kind
+ * is used to categorize folding ranges and used by commands like 'Fold all comments'. See
+ * [FoldingRangeKind](#FoldingRangeKind) for an enumeration of standardized kinds.
+ */
+ String kind = FoldingRangeKind::Region;
+
+ _FORCE_INLINE_ Dictionary to_json() const {
+ Dictionary dict;
+ dict["startLine"] = startLine;
+ dict["startCharacter"] = startCharacter;
+ dict["endLine"] = endLine;
+ dict["endCharacter"] = endCharacter;
+ return dict;
+ }
+};
+
+/**
+ * How a completion was triggered
+ */
+namespace CompletionTriggerKind {
+/**
+ * Completion was triggered by typing an identifier (24x7 code
+ * complete), manual invocation (e.g Ctrl+Space) or via API.
+ */
+static const int Invoked = 1;
+
+/**
+ * Completion was triggered by a trigger character specified by
+ * the `triggerCharacters` properties of the `CompletionRegistrationOptions`.
+ */
+static const int TriggerCharacter = 2;
+
+/**
+ * Completion was re-triggered as the current completion list is incomplete.
+ */
+static const int TriggerForIncompleteCompletions = 3;
+} // namespace CompletionTriggerKind
+
+/**
+ * Contains additional information about the context in which a completion request is triggered.
+ */
+struct CompletionContext {
+ /**
+ * How the completion was triggered.
+ */
+ int triggerKind = CompletionTriggerKind::TriggerCharacter;
+
+ /**
+ * The trigger character (a single character) that has trigger code complete.
+ * Is undefined if `triggerKind !== CompletionTriggerKind.TriggerCharacter`
+ */
+ String triggerCharacter;
+
+ void load(const Dictionary &p_params) {
+ triggerKind = int(p_params["triggerKind"]);
+ triggerCharacter = p_params["triggerCharacter"];
+ }
+};
+
+struct CompletionParams : public TextDocumentPositionParams {
+
+ /**
+ * The completion context. This is only available if the client specifies
+ * to send this using `ClientCapabilities.textDocument.completion.contextSupport === true`
+ */
+ CompletionContext context;
+
+ void load(const Dictionary &p_params) {
+ TextDocumentPositionParams::load(p_params);
+ context.load(p_params["context"]);
+ }
+};
+
+/**
+ * The result of a hover request.
+ */
+struct Hover {
+ /**
+ * The hover's content
+ */
+ MarkupContent contents;
+
+ /**
+ * An optional range is a range inside a text document
+ * that is used to visualize a hover, e.g. by changing the background color.
+ */
+ Range range;
+
+ _FORCE_INLINE_ Dictionary to_json() const {
+ Dictionary dict;
+ dict["range"] = range.to_json();
+ dict["contents"] = contents.to_json();
+ return dict;
+ }
+};
+
+struct ServerCapabilities {
+ /**
+ * Defines how text documents are synced. Is either a detailed structure defining each notification or
+ * for backwards compatibility the TextDocumentSyncKind number. If omitted it defaults to `TextDocumentSyncKind.None`.
+ */
+ TextDocumentSyncOptions textDocumentSync;
+
+ /**
+ * The server provides hover support.
+ */
+ bool hoverProvider = true;
+
+ /**
+ * The server provides completion support.
+ */
+ CompletionOptions completionProvider;
+
+ /**
+ * The server provides signature help support.
+ */
+ SignatureHelpOptions signatureHelpProvider;
+
+ /**
+ * The server provides goto definition support.
+ */
+ bool definitionProvider = true;
+
+ /**
+ * The server provides Goto Type Definition support.
+ *
+ * Since 3.6.0
+ */
+ bool typeDefinitionProvider = false;
+
+ /**
+ * The server provides Goto Implementation support.
+ *
+ * Since 3.6.0
+ */
+ bool implementationProvider = false;
+
+ /**
+ * The server provides find references support.
+ */
+ bool referencesProvider = false;
+
+ /**
+ * The server provides document highlight support.
+ */
+ bool documentHighlightProvider = false;
+
+ /**
+ * The server provides document symbol support.
+ */
+ bool documentSymbolProvider = true;
+
+ /**
+ * The server provides workspace symbol support.
+ */
+ bool workspaceSymbolProvider = true;
+
+ /**
+ * The server provides code actions. The `CodeActionOptions` return type is only
+ * valid if the client signals code action literal support via the property
+ * `textDocument.codeAction.codeActionLiteralSupport`.
+ */
+ bool codeActionProvider = false;
+
+ /**
+ * The server provides code lens.
+ */
+ CodeLensOptions codeLensProvider;
+
+ /**
+ * The server provides document formatting.
+ */
+ bool documentFormattingProvider = false;
+
+ /**
+ * The server provides document range formatting.
+ */
+ bool documentRangeFormattingProvider = false;
+
+ /**
+ * The server provides document formatting on typing.
+ */
+ DocumentOnTypeFormattingOptions documentOnTypeFormattingProvider;
+
+ /**
+ * The server provides rename support. RenameOptions may only be
+ * specified if the client states that it supports
+ * `prepareSupport` in its initial `initialize` request.
+ */
+ RenameOptions renameProvider;
+
+ /**
+ * The server provides document link support.
+ */
+ DocumentLinkOptions documentLinkProvider;
+
+ /**
+ * The server provides color provider support.
+ *
+ * Since 3.6.0
+ */
+ ColorProviderOptions colorProvider;
+
+ /**
+ * The server provides folding provider support.
+ *
+ * Since 3.10.0
+ */
+ FoldingRangeProviderOptions foldingRangeProvider;
+
+ /**
+ * The server provides go to declaration support.
+ *
+ * Since 3.14.0
+ */
+ bool declarationProvider = true;
+
+ /**
+ * The server provides execute command support.
+ */
+ ExecuteCommandOptions executeCommandProvider;
+
+ _FORCE_INLINE_ Dictionary to_json() {
+ Dictionary dict;
+ dict["textDocumentSync"] = (int)textDocumentSync.change;
+ dict["completionProvider"] = completionProvider.to_json();
+ dict["signatureHelpProvider"] = signatureHelpProvider.to_json();
+ dict["codeLensProvider"] = false; // codeLensProvider.to_json();
+ dict["documentOnTypeFormattingProvider"] = documentOnTypeFormattingProvider.to_json();
+ dict["renameProvider"] = renameProvider.to_json();
+ dict["documentLinkProvider"] = documentLinkProvider.to_json();
+ dict["colorProvider"] = false; // colorProvider.to_json();
+ dict["foldingRangeProvider"] = false; //foldingRangeProvider.to_json();
+ dict["executeCommandProvider"] = executeCommandProvider.to_json();
+ dict["hoverProvider"] = hoverProvider;
+ dict["definitionProvider"] = definitionProvider;
+ dict["typeDefinitionProvider"] = typeDefinitionProvider;
+ dict["implementationProvider"] = implementationProvider;
+ dict["referencesProvider"] = referencesProvider;
+ dict["documentHighlightProvider"] = documentHighlightProvider;
+ dict["documentSymbolProvider"] = documentSymbolProvider;
+ dict["workspaceSymbolProvider"] = workspaceSymbolProvider;
+ dict["codeActionProvider"] = codeActionProvider;
+ dict["documentFormattingProvider"] = documentFormattingProvider;
+ dict["documentRangeFormattingProvider"] = documentRangeFormattingProvider;
+ dict["declarationProvider"] = declarationProvider;
+ return dict;
+ }
+};
+
+struct InitializeResult {
+ /**
+ * The capabilities the language server provides.
+ */
+ ServerCapabilities capabilities;
+
+ _FORCE_INLINE_ Dictionary to_json() {
+ Dictionary dict;
+ dict["capabilities"] = capabilities.to_json();
+ return dict;
+ }
+};
+
+} // namespace lsp
+
+#endif
diff --git a/modules/gdscript/register_types.cpp b/modules/gdscript/register_types.cpp
index 62117dcaf3..d07949b34b 100644
--- a/modules/gdscript/register_types.cpp
+++ b/modules/gdscript/register_types.cpp
@@ -37,6 +37,7 @@
#include "editor/gdscript_highlighter.h"
#include "gdscript.h"
#include "gdscript_tokenizer.h"
+#include "language_server/gdscript_language_server.h"
GDScriptLanguage *script_language_gd = NULL;
Ref<ResourceFormatLoaderGDScript> resource_loader_gd;
@@ -44,6 +45,7 @@ Ref<ResourceFormatSaverGDScript> resource_saver_gd;
#ifdef TOOLS_ENABLED
+#include "core/engine.h"
#include "editor/editor_export.h"
#include "editor/editor_node.h"
#include "editor/editor_settings.h"
@@ -134,6 +136,11 @@ static void _editor_init() {
Ref<EditorExportGDScript> gd_export;
gd_export.instance();
EditorExport::get_singleton()->add_export_plugin(gd_export);
+
+ register_lsp_types();
+ GDScriptLanguageServer *lsp_plugin = memnew(GDScriptLanguageServer);
+ EditorNode::get_singleton()->add_editor_plugin(lsp_plugin);
+ Engine::get_singleton()->add_singleton(Engine::Singleton("GDScriptLanguageProtocol", GDScriptLanguageProtocol::get_singleton()));
}
#endif
diff --git a/modules/jsonrpc/SCsub b/modules/jsonrpc/SCsub
new file mode 100644
index 0000000000..13c9ffb253
--- /dev/null
+++ b/modules/jsonrpc/SCsub
@@ -0,0 +1,7 @@
+#!/usr/bin/env python
+
+Import('env')
+Import('env_modules')
+
+env_jsonrpc = env_modules.Clone()
+env_jsonrpc.add_source_files(env.modules_sources, "*.cpp")
diff --git a/modules/jsonrpc/config.py b/modules/jsonrpc/config.py
new file mode 100644
index 0000000000..53bc827027
--- /dev/null
+++ b/modules/jsonrpc/config.py
@@ -0,0 +1,5 @@
+def can_build(env, platform):
+ return True
+
+def configure(env):
+ pass
diff --git a/modules/jsonrpc/jsonrpc.cpp b/modules/jsonrpc/jsonrpc.cpp
new file mode 100644
index 0000000000..b18b48d1b0
--- /dev/null
+++ b/modules/jsonrpc/jsonrpc.cpp
@@ -0,0 +1,171 @@
+/*************************************************************************/
+/* jsonrpc.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "jsonrpc.h"
+#include "core/io/json.h"
+
+JSONRPC::JSONRPC() {
+}
+
+JSONRPC::~JSONRPC() {
+}
+
+void JSONRPC::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("set_scope", "scope", "target"), &JSONRPC::set_scope);
+ ClassDB::bind_method(D_METHOD("process_action", "action", "recurse"), &JSONRPC::process_action, DEFVAL(false));
+ ClassDB::bind_method(D_METHOD("process_string", "action"), &JSONRPC::process_string);
+
+ ClassDB::bind_method(D_METHOD("make_request", "method", "params", "id"), &JSONRPC::make_request);
+ ClassDB::bind_method(D_METHOD("make_response", "result", "id"), &JSONRPC::make_response);
+ ClassDB::bind_method(D_METHOD("make_notification", "method", "params"), &JSONRPC::make_notification);
+ ClassDB::bind_method(D_METHOD("make_response_error", "code", "message", "id"), &JSONRPC::make_response_error, DEFVAL(Variant()));
+
+ BIND_ENUM_CONSTANT(ParseError)
+ BIND_ENUM_CONSTANT(InvalidRequest)
+ BIND_ENUM_CONSTANT(MethodNotFound)
+ BIND_ENUM_CONSTANT(InvalidParams)
+ BIND_ENUM_CONSTANT(InternalError)
+}
+
+Dictionary JSONRPC::make_response_error(int p_code, const String &p_message, const Variant &p_id) const {
+ Dictionary dict;
+ dict["jsonrpc"] = "2.0";
+
+ Dictionary err;
+ err["code"] = p_code;
+ err["message"] = p_message;
+
+ dict["error"] = err;
+ dict["id"] = p_id;
+
+ return dict;
+}
+
+Dictionary JSONRPC::make_response(const Variant &p_value, const Variant &p_id) {
+ Dictionary dict;
+ dict["jsonrpc"] = "2.0";
+ dict["id"] = p_id;
+ dict["result"] = p_value;
+ return dict;
+}
+
+Dictionary JSONRPC::make_notification(const String &p_method, const Variant &p_params) {
+ Dictionary dict;
+ dict["jsonrpc"] = "2.0";
+ dict["method"] = p_method;
+ dict["params"] = p_params;
+ return dict;
+}
+
+Dictionary JSONRPC::make_request(const String &p_method, const Variant &p_params, const Variant &p_id) {
+ Dictionary dict;
+ dict["jsonrpc"] = "2.0";
+ dict["method"] = p_method;
+ dict["params"] = p_params;
+ dict["id"] = p_id;
+ return dict;
+}
+
+Variant JSONRPC::process_action(const Variant &p_action, bool p_process_arr_elements) {
+ Variant ret;
+ if (p_action.get_type() == Variant::DICTIONARY) {
+ Dictionary dict = p_action;
+ String method = dict.get("method", "");
+ Array args;
+ if (dict.has("params")) {
+ Variant params = dict.get("params", Variant());
+ if (params.get_type() == Variant::ARRAY) {
+ args = params;
+ } else {
+ args.push_back(params);
+ }
+ }
+
+ Object *object = this;
+ if (method_scopes.has(method.get_base_dir())) {
+ object = method_scopes[method.get_base_dir()];
+ method = method.get_file();
+ }
+
+ Variant id;
+ if (dict.has("id")) {
+ id = dict["id"];
+ }
+
+ if (object == NULL || !object->has_method(method)) {
+ ret = make_response_error(JSONRPC::MethodNotFound, "Method not found", id);
+ } else {
+ Variant call_ret = object->callv(method, args);
+ if (id.get_type() != Variant::NIL) {
+ ret = make_response(call_ret, id);
+ }
+ }
+ } else if (p_action.get_type() == Variant::ARRAY && p_process_arr_elements) {
+ Array arr = p_action;
+ int size = arr.size();
+ if (size) {
+ Array arr_ret;
+ for (int i = 0; i < size; i++) {
+ const Variant &var = arr.get(i);
+ arr_ret.push_back(process_action(var));
+ }
+ ret = arr_ret;
+ } else {
+ ret = make_response_error(JSONRPC::InvalidRequest, "Invalid Request");
+ }
+ } else {
+ ret = make_response_error(JSONRPC::InvalidRequest, "Invalid Request");
+ }
+ return ret;
+}
+
+String JSONRPC::process_string(const String &p_input) {
+
+ if (p_input.empty()) return String();
+
+ Variant ret;
+ Variant input;
+ String err_message;
+ int err_line;
+ if (OK != JSON::parse(p_input, input, err_message, err_line)) {
+ ret = make_response_error(JSONRPC::ParseError, "Parse error");
+ } else {
+ ret = process_action(input, true);
+ }
+
+ if (ret.get_type() == Variant::NIL) {
+ return "";
+ }
+ return JSON::print(ret);
+}
+
+void JSONRPC::set_scope(const String &p_scope, Object *p_obj) {
+ method_scopes[p_scope] = p_obj;
+}
diff --git a/modules/jsonrpc/jsonrpc.h b/modules/jsonrpc/jsonrpc.h
new file mode 100644
index 0000000000..bcb34ecc65
--- /dev/null
+++ b/modules/jsonrpc/jsonrpc.h
@@ -0,0 +1,70 @@
+/*************************************************************************/
+/* jsonrpc.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef GODOT_JSON_RPC_H
+#define GODOT_JSON_RPC_H
+
+#include "core/object.h"
+#include "core/variant.h"
+
+class JSONRPC : public Object {
+ GDCLASS(JSONRPC, Object)
+
+ Map<String, Object *> method_scopes;
+
+protected:
+ static void _bind_methods();
+
+public:
+ JSONRPC();
+ ~JSONRPC();
+
+ enum ErrorCode {
+ ParseError = -32700,
+ InvalidRequest = -32600,
+ MethodNotFound = -32601,
+ InvalidParams = -32602,
+ InternalError = -32603,
+ };
+
+ Dictionary make_response_error(int p_code, const String &p_message, const Variant &p_id = Variant()) const;
+ Dictionary make_response(const Variant &p_value, const Variant &p_id);
+ Dictionary make_notification(const String &p_method, const Variant &p_params);
+ Dictionary make_request(const String &p_method, const Variant &p_params, const Variant &p_id);
+
+ Variant process_action(const Variant &p_action, bool p_process_arr_elements = false);
+ String process_string(const String &p_input);
+
+ void set_scope(const String &p_scope, Object *p_obj);
+};
+
+VARIANT_ENUM_CAST(JSONRPC::ErrorCode);
+
+#endif
diff --git a/modules/jsonrpc/register_types.cpp b/modules/jsonrpc/register_types.cpp
new file mode 100644
index 0000000000..242b0e9df4
--- /dev/null
+++ b/modules/jsonrpc/register_types.cpp
@@ -0,0 +1,40 @@
+/*************************************************************************/
+/* register_types.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "register_types.h"
+#include "core/class_db.h"
+#include "jsonrpc.h"
+
+void register_jsonrpc_types() {
+ ClassDB::register_class<JSONRPC>();
+}
+
+void unregister_jsonrpc_types() {
+}
diff --git a/modules/jsonrpc/register_types.h b/modules/jsonrpc/register_types.h
new file mode 100644
index 0000000000..e4648b901f
--- /dev/null
+++ b/modules/jsonrpc/register_types.h
@@ -0,0 +1,32 @@
+/*************************************************************************/
+/* register_types.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+void register_jsonrpc_types();
+void unregister_jsonrpc_types();
diff --git a/modules/mbedtls/ssl_context_mbedtls.cpp b/modules/mbedtls/ssl_context_mbedtls.cpp
index 97b5e23f58..eeaf831b4a 100644
--- a/modules/mbedtls/ssl_context_mbedtls.cpp
+++ b/modules/mbedtls/ssl_context_mbedtls.cpp
@@ -1,5 +1,5 @@
/*************************************************************************/
-/* ssl_context_mbed_tls.cpp */
+/* ssl_context_mbedtls.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
diff --git a/modules/mbedtls/ssl_context_mbedtls.h b/modules/mbedtls/ssl_context_mbedtls.h
index b78ee37b03..e49d532912 100644
--- a/modules/mbedtls/ssl_context_mbedtls.h
+++ b/modules/mbedtls/ssl_context_mbedtls.h
@@ -1,5 +1,5 @@
/*************************************************************************/
-/* ssl_context_mbed_tls.h */
+/* ssl_context_mbedtls.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
diff --git a/modules/mbedtls/stream_peer_mbedtls.cpp b/modules/mbedtls/stream_peer_mbedtls.cpp
index e2eb19fc74..a2e342e219 100755
--- a/modules/mbedtls/stream_peer_mbedtls.cpp
+++ b/modules/mbedtls/stream_peer_mbedtls.cpp
@@ -1,5 +1,5 @@
/*************************************************************************/
-/* stream_peer_mbed_tls.cpp */
+/* stream_peer_mbedtls.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
diff --git a/modules/mbedtls/stream_peer_mbedtls.h b/modules/mbedtls/stream_peer_mbedtls.h
index 060e76b4f3..eec7eab631 100755
--- a/modules/mbedtls/stream_peer_mbedtls.h
+++ b/modules/mbedtls/stream_peer_mbedtls.h
@@ -1,5 +1,5 @@
/*************************************************************************/
-/* stream_peer_mbed_tls.h */
+/* stream_peer_mbedtls.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
diff --git a/modules/mono/class_db_api_json.cpp b/modules/mono/class_db_api_json.cpp
index 4a6637434a..7580911a0a 100644
--- a/modules/mono/class_db_api_json.cpp
+++ b/modules/mono/class_db_api_json.cpp
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */
+/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
diff --git a/modules/mono/class_db_api_json.h b/modules/mono/class_db_api_json.h
index ddfe2debea..9888ecfb55 100644
--- a/modules/mono/class_db_api_json.h
+++ b/modules/mono/class_db_api_json.h
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */
+/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp
index 8c17bac3c9..4c9dd9c1a9 100644
--- a/modules/mono/csharp_script.cpp
+++ b/modules/mono/csharp_script.cpp
@@ -2661,7 +2661,7 @@ void CSharpScript::_get_property_list(List<PropertyInfo> *p_properties) const {
void CSharpScript::_bind_methods() {
- ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "new", &CSharpScript::_new, MethodInfo(Variant::OBJECT, "new"));
+ ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "new", &CSharpScript::_new, MethodInfo("new"));
}
Ref<CSharpScript> CSharpScript::create_for_managed_type(GDMonoClass *p_class, GDMonoClass *p_native) {
diff --git a/modules/mono/mono_gd/android_mono_config.h b/modules/mono/mono_gd/android_mono_config.h
index 78d3fdec98..c5cc244aec 100644
--- a/modules/mono/mono_gd/android_mono_config.h
+++ b/modules/mono/mono_gd/android_mono_config.h
@@ -1,3 +1,33 @@
+/*************************************************************************/
+/* android_mono_config.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
#ifndef ANDROID_MONO_CONFIG_H
#define ANDROID_MONO_CONFIG_H
diff --git a/modules/recast/navigation_mesh_generator.cpp b/modules/recast/navigation_mesh_generator.cpp
index 14467dc5c7..c5b60f2dca 100644
--- a/modules/recast/navigation_mesh_generator.cpp
+++ b/modules/recast/navigation_mesh_generator.cpp
@@ -49,6 +49,10 @@
#include "modules/csg/csg_shape.h"
#endif
+#ifdef MODULE_GRIDMAP_ENABLED
+#include "modules/gridmap/grid_map.h"
+#endif
+
EditorNavigationMeshGenerator *EditorNavigationMeshGenerator::singleton = NULL;
void EditorNavigationMeshGenerator::_add_vertex(const Vector3 &p_vec3, Vector<float> &p_verticies) {
@@ -240,8 +244,21 @@ void EditorNavigationMeshGenerator::_parse_geometry(Transform p_accumulated_tran
}
}
- if (Object::cast_to<Spatial>(p_node)) {
+#ifdef MODULE_GRIDMAP_ENABLED
+ if (Object::cast_to<GridMap>(p_node) && p_generate_from != NavigationMesh::PARSED_GEOMETRY_STATIC_COLLIDERS) {
+ GridMap *gridmap_instance = Object::cast_to<GridMap>(p_node);
+ Array meshes = gridmap_instance->get_meshes();
+ Transform xform = gridmap_instance->get_transform();
+ for (int i = 0; i < meshes.size(); i += 2) {
+ Ref<Mesh> mesh = meshes[i + 1];
+ if (mesh.is_valid()) {
+ _add_mesh(mesh, p_accumulated_transform * xform * meshes[i], p_verticies, p_indices);
+ }
+ }
+ }
+#endif
+ if (Object::cast_to<Spatial>(p_node)) {
Spatial *spatial = Object::cast_to<Spatial>(p_node);
p_accumulated_transform = p_accumulated_transform * spatial->get_transform();
}
diff --git a/modules/websocket/wsl_peer.cpp b/modules/websocket/wsl_peer.cpp
index f94f3dfff7..74fb901232 100644
--- a/modules/websocket/wsl_peer.cpp
+++ b/modules/websocket/wsl_peer.cpp
@@ -1,5 +1,5 @@
/*************************************************************************/
-/* lws_peer.cpp */
+/* wsl_peer.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
diff --git a/modules/xatlas_unwrap/register_types.cpp b/modules/xatlas_unwrap/register_types.cpp
index 04911301ff..65b3cf08f5 100644
--- a/modules/xatlas_unwrap/register_types.cpp
+++ b/modules/xatlas_unwrap/register_types.cpp
@@ -59,7 +59,7 @@ bool xatlas_mesh_lightmap_unwrap_callback(float p_texel_size, const float *p_ver
xatlas::PackOptions pack_options;
pack_options.maxChartSize = 4096;
- pack_options.bruteForce = true;
+ pack_options.blockAlign = true;
pack_options.texelsPerUnit = 1.0 / p_texel_size;
xatlas::Atlas *atlas = xatlas::Create();
@@ -78,7 +78,7 @@ bool xatlas_mesh_lightmap_unwrap_callback(float p_texel_size, const float *p_ver
float h = *r_size_hint_y;
if (w == 0 || h == 0) {
- return false; //could not bake
+ return false; //could not bake because there is no area
}
const xatlas::Mesh &output = atlas->meshes[0];
@@ -106,7 +106,7 @@ bool xatlas_mesh_lightmap_unwrap_callback(float p_texel_size, const float *p_ver
*r_index_count = output.indexCount;
- //xatlas::Destroy(atlas);
+ xatlas::Destroy(atlas);
printf("Done\n");
return true;
}
diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp
index b655feecbe..034e9266f6 100644
--- a/scene/gui/control.cpp
+++ b/scene/gui/control.cpp
@@ -2918,7 +2918,11 @@ void Control::_bind_methods() {
BIND_VMETHOD(MethodInfo("_gui_input", PropertyInfo(Variant::OBJECT, "event", PROPERTY_HINT_RESOURCE_TYPE, "InputEvent")));
BIND_VMETHOD(MethodInfo(Variant::VECTOR2, "_get_minimum_size"));
- BIND_VMETHOD(MethodInfo(Variant::OBJECT, "get_drag_data", PropertyInfo(Variant::VECTOR2, "position")));
+
+ MethodInfo get_drag_data = MethodInfo("get_drag_data", PropertyInfo(Variant::VECTOR2, "position"));
+ get_drag_data.return_val.usage |= PROPERTY_USAGE_NIL_IS_VARIANT;
+ BIND_VMETHOD(get_drag_data);
+
BIND_VMETHOD(MethodInfo(Variant::BOOL, "can_drop_data", PropertyInfo(Variant::VECTOR2, "position"), PropertyInfo(Variant::NIL, "data")));
BIND_VMETHOD(MethodInfo("drop_data", PropertyInfo(Variant::VECTOR2, "position"), PropertyInfo(Variant::NIL, "data")));
BIND_VMETHOD(MethodInfo(Variant::OBJECT, "_make_custom_tooltip", PropertyInfo(Variant::STRING, "for_text")));
diff --git a/scene/resources/environment.cpp b/scene/resources/environment.cpp
index 0f0974114f..afb7f1102b 100644
--- a/scene/resources/environment.cpp
+++ b/scene/resources/environment.cpp
@@ -1399,7 +1399,7 @@ Environment::Environment() :
fog_depth_enabled = true;
fog_depth_begin = 10;
- fog_depth_end = 0;
+ fog_depth_end = 100;
fog_depth_curve = 1;
fog_transmit_enabled = false;
diff --git a/servers/arvr/arvr_interface.h b/servers/arvr/arvr_interface.h
index ffafa4fcf5..85556b2757 100644
--- a/servers/arvr/arvr_interface.h
+++ b/servers/arvr/arvr_interface.h
@@ -39,7 +39,7 @@
/**
@author Bastiaan Olij <mux213@gmail.com>
- The ARVR interface is a template class ontop of which we build interface to differt AR, VR and tracking SDKs.
+ The ARVR interface is a template class ontop of which we build interface to different AR, VR and tracking SDKs.
The idea is that we subclass this class, implement the logic, and then instantiate a singleton of each interface
when Godot starts. These instances do not initialize themselves but register themselves with the AR/VR server.
diff --git a/servers/visual/shader_language.cpp b/servers/visual/shader_language.cpp
index e9d85c7eb0..4bc65a8f4f 100644
--- a/servers/visual/shader_language.cpp
+++ b/servers/visual/shader_language.cpp
@@ -5141,6 +5141,14 @@ Error ShaderLanguage::_parse_shader(const Map<StringName, FunctionInfo> &p_funct
if (err)
return err;
+ if (func_node->return_type != DataType::TYPE_VOID) {
+
+ BlockNode *block = func_node->body;
+ if (_find_last_flow_op_in_block(block, FlowOperation::FLOW_OP_RETURN) != OK) {
+ _set_error("Expected at least one return statement in a non-void function.");
+ return ERR_PARSE_ERROR;
+ }
+ }
current_function = StringName();
}
}
@@ -5151,6 +5159,57 @@ Error ShaderLanguage::_parse_shader(const Map<StringName, FunctionInfo> &p_funct
return OK;
}
+Error ShaderLanguage::_find_last_flow_op_in_op(ControlFlowNode *p_flow, FlowOperation p_op) {
+
+ bool found = false;
+
+ for (int i = p_flow->blocks.size() - 1; i >= 0; i--) {
+ if (p_flow->blocks[i]->type == Node::TYPE_BLOCK) {
+ BlockNode *last_block = (BlockNode *)p_flow->blocks[i];
+ if (_find_last_flow_op_in_block(last_block, p_op) == OK) {
+ found = true;
+ break;
+ }
+ }
+ }
+ if (found) {
+ return OK;
+ }
+ return FAILED;
+}
+
+Error ShaderLanguage::_find_last_flow_op_in_block(BlockNode *p_block, FlowOperation p_op) {
+
+ bool found = false;
+
+ for (int i = p_block->statements.size() - 1; i >= 0; i--) {
+
+ if (p_block->statements[i]->type == Node::TYPE_CONTROL_FLOW) {
+ ControlFlowNode *flow = (ControlFlowNode *)p_block->statements[i];
+ if (flow->flow_op == p_op) {
+ found = true;
+ break;
+ } else {
+ if (_find_last_flow_op_in_op(flow, p_op) == OK) {
+ found = true;
+ break;
+ }
+ }
+ } else if (p_block->statements[i]->type == Node::TYPE_BLOCK) {
+ BlockNode *block = (BlockNode *)p_block->statements[i];
+ if (_find_last_flow_op_in_block(block, p_op) == OK) {
+ found = true;
+ break;
+ }
+ }
+ }
+
+ if (found) {
+ return OK;
+ }
+ return FAILED;
+}
+
// skips over whitespace and /* */ and // comments
static int _get_first_ident_pos(const String &p_code) {
diff --git a/servers/visual/shader_language.h b/servers/visual/shader_language.h
index b71788513e..6753456323 100644
--- a/servers/visual/shader_language.h
+++ b/servers/visual/shader_language.h
@@ -750,6 +750,9 @@ private:
Error _parse_block(BlockNode *p_block, const Map<StringName, BuiltInInfo> &p_builtin_types, bool p_just_one = false, bool p_can_break = false, bool p_can_continue = false);
Error _parse_shader(const Map<StringName, FunctionInfo> &p_functions, const Vector<StringName> &p_render_modes, const Set<String> &p_shader_types);
+ Error _find_last_flow_op_in_block(BlockNode *p_block, FlowOperation p_op);
+ Error _find_last_flow_op_in_op(ControlFlowNode *p_flow, FlowOperation p_op);
+
public:
//static void get_keyword_list(ShaderType p_type,List<String> *p_keywords);
diff --git a/thirdparty/README.md b/thirdparty/README.md
index 99f917dbc1..3f2fc6d8f9 100644
--- a/thirdparty/README.md
+++ b/thirdparty/README.md
@@ -513,7 +513,7 @@ File extracted from upstream release tarball:
## xatlas
- Upstream: https://github.com/jpcy/xatlas
-- Version: git (f65a664, 2019)
+- Version: git (b4b5426, 2019)
- License: MIT
Files extracted from upstream source:
diff --git a/thirdparty/xatlas/avoid-failing-on-bad-geometry.patch b/thirdparty/xatlas/avoid-failing-on-bad-geometry.patch
deleted file mode 100644
index a28cd9f82b..0000000000
--- a/thirdparty/xatlas/avoid-failing-on-bad-geometry.patch
+++ /dev/null
@@ -1,157 +0,0 @@
-diff --git a/thirdparty/xatlas/xatlas.cpp b/thirdparty/xatlas/xatlas.cpp
-index df5ef94db..eb0824a51 100644
---- a/thirdparty/xatlas/xatlas.cpp
-+++ b/thirdparty/xatlas/xatlas.cpp
-@@ -1276,6 +1276,9 @@ class Vertex
- {
- public:
- uint32_t id;
-+ // -- GODOT start --
-+ uint32_t original_id;
-+ // -- GODOT end --
- Edge *edge;
- Vertex *next;
- Vertex *prev;
-@@ -1283,7 +1286,10 @@ public:
- Vector3 nor;
- Vector2 tex;
-
-- Vertex(uint32_t id) : id(id), edge(NULL), pos(0.0f), nor(0.0f), tex(0.0f)
-+ // -- GODOT start --
-+ //Vertex(uint32_t id) : id(id), edge(NULL), pos(0.0f), nor(0.0f), tex(0.0f)
-+ Vertex(uint32_t id) : id(id), original_id(id), edge(NULL), pos(0.0f), nor(0.0f), tex(0.0f)
-+ // -- GODOT end --
- {
- next = this;
- prev = this;
-@@ -1934,6 +1940,64 @@ public:
- return f;
- }
-
-+ // -- GODOT start --
-+ Face *addUniqueFace(uint32_t v0, uint32_t v1, uint32_t v2) {
-+
-+ int base_vertex = m_vertexArray.size();
-+
-+ uint32_t ids[3] = { v0, v1, v2 };
-+
-+ Vector3 base[3] = {
-+ m_vertexArray[v0]->pos,
-+ m_vertexArray[v1]->pos,
-+ m_vertexArray[v2]->pos,
-+ };
-+
-+ //make sure its not a degenerate
-+ bool degenerate = distanceSquared(base[0], base[1]) < NV_EPSILON || distanceSquared(base[0], base[2]) < NV_EPSILON || distanceSquared(base[1], base[2]) < NV_EPSILON;
-+ xaDebugAssert(!degenerate);
-+
-+ float min_x = 0;
-+
-+ for (int i = 0; i < 3; i++) {
-+ if (i == 0 || m_vertexArray[v0]->pos.x < min_x) {
-+ min_x = m_vertexArray[v0]->pos.x;
-+ }
-+ }
-+
-+ float max_x = 0;
-+
-+ for (int j = 0; j < m_vertexArray.size(); j++) {
-+ if (j == 0 || m_vertexArray[j]->pos.x > max_x) { //vertex already exists
-+ max_x = m_vertexArray[j]->pos.x;
-+ }
-+ }
-+
-+ //separate from everything else, in x axis
-+ for (int i = 0; i < 3; i++) {
-+
-+ base[i].x -= min_x;
-+ base[i].x += max_x + 10.0;
-+ }
-+
-+ for (int i = 0; i < 3; i++) {
-+ Vertex *v = new Vertex(m_vertexArray.size());
-+ v->pos = base[i];
-+ v->nor = m_vertexArray[ids[i]]->nor,
-+ v->tex = m_vertexArray[ids[i]]->tex,
-+
-+ v->original_id = ids[i];
-+ m_vertexArray.push_back(v);
-+ }
-+
-+ uint32_t indexArray[3];
-+ indexArray[0] = base_vertex + 0;
-+ indexArray[1] = base_vertex + 1;
-+ indexArray[2] = base_vertex + 2;
-+ return addFace(indexArray, 3, 0, 3);
-+ }
-+ // -- GODOT end --
-+
- // These functions disconnect the given element from the mesh and delete it.
-
- // @@ We must always disconnect edge pairs simultaneously.
-@@ -2915,6 +2979,14 @@ Mesh *triangulate(const Mesh *inputMesh)
- Vector2 p0 = polygonPoints[i0];
- Vector2 p1 = polygonPoints[i1];
- Vector2 p2 = polygonPoints[i2];
-+
-+ // -- GODOT start --
-+ bool degenerate = distance(p0, p1) < NV_EPSILON || distance(p0, p2) < NV_EPSILON || distance(p1, p2) < NV_EPSILON;
-+ if (degenerate) {
-+ continue;
-+ }
-+ // -- GODOT end --
-+
- float d = clamp(dot(p0 - p1, p2 - p1) / (length(p0 - p1) * length(p2 - p1)), -1.0f, 1.0f);
- float angle = acosf(d);
- float area = triangleArea(p0, p1, p2);
-@@ -2938,6 +3010,11 @@ Mesh *triangulate(const Mesh *inputMesh)
- }
- }
- }
-+ // -- GODOT start --
-+ if (!bestIsValid)
-+ break;
-+ // -- GODOT end --
-+
- xaDebugAssert(minAngle <= 2 * PI);
- // Clip best ear:
- uint32_t i0 = (bestEar + size - 1) % size;
-@@ -5606,7 +5683,10 @@ public:
- }
- if (chartMeshIndices[vertex->id] == ~0) {
- chartMeshIndices[vertex->id] = m_chartMesh->vertexCount();
-- m_chartToOriginalMap.push_back(vertex->id);
-+ // -- GODOT start --
-+ //m_chartToOriginalMap.push_back(vertex->id);
-+ m_chartToOriginalMap.push_back(vertex->original_id);
-+ // -- GODOT end --
- m_chartToUnifiedMap.push_back(unifiedMeshIndices[unifiedVertex->id]);
- halfedge::Vertex *v = m_chartMesh->addVertex(vertex->pos);
- v->nor = vertex->nor;
-@@ -5699,7 +5779,10 @@ public:
- const halfedge::Vertex *vertex = it.current()->vertex;
- if (chartMeshIndices[vertex->id] == ~0) {
- chartMeshIndices[vertex->id] = m_chartMesh->vertexCount();
-- m_chartToOriginalMap.push_back(vertex->id);
-+ // -- GODOT start --
-+ //m_chartToOriginalMap.push_back(vertex->id);
-+ m_chartToOriginalMap.push_back(vertex->original_id);
-+ // -- GODOT end --
- halfedge::Vertex *v = m_chartMesh->addVertex(vertex->pos);
- v->nor = vertex->nor;
- v->tex = vertex->tex; // @@ Not necessary.
-@@ -7573,6 +7656,14 @@ AddMeshError AddMesh(Atlas *atlas, const InputMesh &mesh, bool useColocalVertice
- }
- }
- internal::halfedge::Face *face = heMesh->addFace(tri[0], tri[1], tri[2]);
-+
-+ // -- GODOT start --
-+ if (!face && heMesh->errorCode == internal::halfedge::Mesh::ErrorCode::AlreadyAddedEdge) {
-+ //there is still hope for this, no reason to not add, at least add as separate
-+ face = heMesh->addUniqueFace(tri[0], tri[1], tri[2]);
-+ }
-+ // -- GODOT end --
-+
- if (!face) {
- if (heMesh->errorCode == internal::halfedge::Mesh::ErrorCode::AlreadyAddedEdge)
- error.code = AddMeshErrorCode::AlreadyAddedEdge;
diff --git a/thirdparty/xatlas/build-fix-limits.patch b/thirdparty/xatlas/build-fix-limits.patch
deleted file mode 100644
index 00d07371c0..0000000000
--- a/thirdparty/xatlas/build-fix-limits.patch
+++ /dev/null
@@ -1,14 +0,0 @@
-diff --git a/thirdparty/xatlas/xatlas.h b/thirdparty/xatlas/xatlas.h
-index 7e556c6c3..dbf8ca08c 100644
---- a/thirdparty/xatlas/xatlas.h
-+++ b/thirdparty/xatlas/xatlas.h
-@@ -3,6 +3,9 @@
- #ifndef XATLAS_H
- #define XATLAS_H
- #include <float.h> // FLT_MAX
-+// -- GODOT start --
-+#include <limits.h> // INT_MAX, UINT_MAX
-+// -- GODOT end --
-
- namespace xatlas {
-
diff --git a/thirdparty/xatlas/xatlas.cpp b/thirdparty/xatlas/xatlas.cpp
index 1b30305cd4..56794211a6 100644
--- a/thirdparty/xatlas/xatlas.cpp
+++ b/thirdparty/xatlas/xatlas.cpp
@@ -93,8 +93,24 @@ Copyright (c) 2012 Brandon Pelfrey
#define XA_ALLOC(tag, type) (type *)internal::Realloc(nullptr, sizeof(type), tag, __FILE__, __LINE__)
#define XA_ALLOC_ARRAY(tag, type, num) (type *)internal::Realloc(nullptr, sizeof(type) * num, tag, __FILE__, __LINE__)
#define XA_REALLOC(tag, ptr, type, num) (type *)internal::Realloc(ptr, sizeof(type) * num, tag, __FILE__, __LINE__)
+#define XA_REALLOC_SIZE(tag, ptr, size) (uint8_t *)internal::Realloc(ptr, size, tag, __FILE__, __LINE__)
#define XA_FREE(ptr) internal::Realloc(ptr, 0, internal::MemTag::Default, __FILE__, __LINE__)
-#define XA_NEW(tag, type, ...) new (XA_ALLOC(tag, type)) type(__VA_ARGS__)
+#define XA_NEW(tag, type) new (XA_ALLOC(tag, type)) type()
+#define XA_NEW_ARGS(tag, type, ...) new (XA_ALLOC(tag, type)) type(__VA_ARGS__)
+
+#ifdef _MSC_VER
+#define XA_INLINE __forceinline
+#else
+#define XA_INLINE inline
+#endif
+
+#if defined(__clang__) || defined(__GNUC__)
+#define XA_NODISCARD [[nodiscard]]
+#elif defined(_MSC_VER)
+#define XA_NODISCARD _Check_return_
+#else
+#define XA_NODISCARD
+#endif
#define XA_UNUSED(a) ((void)(a))
@@ -102,6 +118,7 @@ Copyright (c) 2012 Brandon Pelfrey
#define XA_MERGE_CHARTS 1
#define XA_MERGE_CHARTS_MIN_NORMAL_DEVIATION 0.5f
#define XA_RECOMPUTE_CHARTS 1
+#define XA_SKIP_PARAMETERIZATION 0 // Use the orthogonal parameterization from segment::Atlas
#define XA_CLOSE_HOLES_CHECK_EDGE_INTERSECTION 0
#define XA_DEBUG_HEAP 0
@@ -140,6 +157,7 @@ namespace xatlas {
namespace internal {
static ReallocFunc s_realloc = realloc;
+static FreeFunc s_free = free;
static PrintFunc s_print = printf;
static bool s_printVerbose = false;
@@ -167,6 +185,7 @@ struct AllocHeader
const char *file;
int line;
int tag;
+ uint32_t id;
AllocHeader *prev, *next;
bool free;
};
@@ -174,6 +193,7 @@ struct AllocHeader
static std::mutex s_allocMutex;
static AllocHeader *s_allocRoot = nullptr;
static size_t s_allocTotalSize = 0, s_allocPeakSize = 0, s_allocTotalTagSize[MemTag::Count] = { 0 }, s_allocPeakTagSize[MemTag::Count] = { 0 };
+static uint32_t s_allocId =0 ;
static constexpr uint32_t kAllocRedzone = 0x12345678;
static void *Realloc(void *ptr, size_t size, int tag, const char *file, int line)
@@ -214,6 +234,7 @@ static void *Realloc(void *ptr, size_t size, int tag, const char *file, int line
header->file = file;
header->line = line;
header->tag = tag;
+ header->id = s_allocId++;
header->free = false;
if (!s_allocRoot) {
s_allocRoot = header;
@@ -242,7 +263,7 @@ static void ReportLeaks()
AllocHeader *header = s_allocRoot;
while (header) {
if (!header->free) {
- printf(" Leak: %zu bytes %s %d\n", header->size, header->file, header->line);
+ printf(" Leak: ID %u, %zu bytes, %s %d\n", header->id, header->size, header->file, header->line);
anyLeaks = true;
}
auto redzone = (const uint32_t *)((const uint8_t *)header + header->size - sizeof(kAllocRedzone));
@@ -287,6 +308,10 @@ static void PrintMemoryUsage()
#else
static void *Realloc(void *ptr, size_t size, int /*tag*/, const char * /*file*/, int /*line*/)
{
+ if (ptr && size == 0 && s_free) {
+ s_free(ptr);
+ return nullptr;
+ }
void *mem = s_realloc(ptr, size);
if (size > 0) {
XA_DEBUG_ASSERT(mem);
@@ -304,6 +329,7 @@ static void *Realloc(void *ptr, size_t size, int /*tag*/, const char * /*file*/,
struct ProfileData
{
clock_t addMeshReal;
+ clock_t addMeshCopyData;
std::atomic<clock_t> addMeshThread;
std::atomic<clock_t> addMeshCreateColocals;
std::atomic<clock_t> addMeshCreateFaceGroups;
@@ -312,11 +338,14 @@ struct ProfileData
std::atomic<clock_t> addMeshCreateChartGroupsThread;
clock_t computeChartsReal;
std::atomic<clock_t> computeChartsThread;
- std::atomic<clock_t> atlasBuilder;
- std::atomic<clock_t> atlasBuilderInit;
- std::atomic<clock_t> atlasBuilderCreateInitialCharts;
- std::atomic<clock_t> atlasBuilderGrowCharts;
- std::atomic<clock_t> atlasBuilderMergeCharts;
+ std::atomic<clock_t> buildAtlas;
+ std::atomic<clock_t> buildAtlasInit;
+ std::atomic<clock_t> buildAtlasPlaceSeeds;
+ std::atomic<clock_t> buildAtlasRelocateSeeds;
+ std::atomic<clock_t> buildAtlasResetCharts;
+ std::atomic<clock_t> buildAtlasGrowCharts;
+ std::atomic<clock_t> buildAtlasMergeCharts;
+ std::atomic<clock_t> buildAtlasFillHoles;
std::atomic<clock_t> createChartMeshesReal;
std::atomic<clock_t> createChartMeshesThread;
std::atomic<clock_t> fixChartMeshTJunctions;
@@ -327,11 +356,15 @@ struct ProfileData
std::atomic<clock_t> parameterizeChartsLSCM;
std::atomic<clock_t> parameterizeChartsEvaluateQuality;
clock_t packCharts;
+ clock_t packChartsAddCharts;
+ std::atomic<clock_t> packChartsAddChartsThread;
+ std::atomic<clock_t> packChartsAddChartsRestoreTexcoords;
clock_t packChartsRasterize;
clock_t packChartsDilate;
clock_t packChartsFindLocation;
std::atomic<clock_t> packChartsFindLocationThread;
clock_t packChartsBlit;
+ clock_t buildOutputMeshes;
};
static ProfileData s_profile;
@@ -540,10 +573,10 @@ static bool operator!=(const Vector2 &a, const Vector2 &b)
return a.x != b.x || a.y != b.y;
}
-static Vector2 operator+(const Vector2 &a, const Vector2 &b)
+/*static Vector2 operator+(const Vector2 &a, const Vector2 &b)
{
return Vector2(a.x + b.x, a.y + b.y);
-}
+}*/
static Vector2 operator-(const Vector2 &a, const Vector2 &b)
{
@@ -738,11 +771,6 @@ static Vector3 operator*(const Vector3 &v, float s)
return Vector3(v.x * s, v.y * s, v.z * s);
}
-static Vector3 operator*(float s, const Vector3 &v)
-{
- return Vector3(v.x * s, v.y * s, v.z * s);
-}
-
static Vector3 operator/(const Vector3 &v, float s)
{
return v * (1.0f / s);
@@ -949,282 +977,202 @@ struct AABB
Vector3 min, max;
};
-template <typename T>
-static void construct_range(T * ptr, uint32_t new_size, uint32_t old_size) {
- for (uint32_t i = old_size; i < new_size; i++) {
- new(ptr+i) T; // placement new
- }
-}
-
-template <typename T>
-static void construct_range(T * ptr, uint32_t new_size, uint32_t old_size, const T & elem) {
- for (uint32_t i = old_size; i < new_size; i++) {
- new(ptr+i) T(elem); // placement new
- }
-}
-
-template <typename T>
-static void construct_range(T * ptr, uint32_t new_size, uint32_t old_size, const T * src) {
- for (uint32_t i = old_size; i < new_size; i++) {
- new(ptr+i) T(src[i]); // placement new
- }
-}
-
-template <typename T>
-static void destroy_range(T * ptr, uint32_t new_size, uint32_t old_size) {
- for (uint32_t i = new_size; i < old_size; i++) {
- (ptr+i)->~T(); // Explicit call to the destructor
- }
-}
-
-/**
-* Replacement for std::vector that is easier to debug and provides
-* some nice foreach enumerators.
-*/
-template<typename T>
-class Array {
-public:
- typedef uint32_t size_type;
-
- Array(int memTag = MemTag::Default) : m_memTag(memTag), m_buffer(nullptr), m_capacity(0), m_size(0) {}
+struct ArrayBase
+{
+ ArrayBase(uint32_t elementSize, int memTag = MemTag::Default) : buffer(nullptr), elementSize(elementSize), size(0), capacity(0), memTag(memTag) {}
- Array(const Array &a) : m_memTag(a.m_memTag), m_buffer(nullptr), m_capacity(0), m_size(0)
+ ~ArrayBase()
{
- copy(a.m_buffer, a.m_size);
+ XA_FREE(buffer);
}
- ~Array()
+ XA_INLINE void clear()
{
- destroy();
+ size = 0;
}
- const Array<T> &operator=(const Array<T> &other)
+ void copyTo(ArrayBase &other) const
{
- m_memTag = other.m_memTag;
- m_buffer = other.m_buffer;
- m_capacity = other.m_capacity;
- m_size = other.m_size;
- return *this;
+ XA_DEBUG_ASSERT(elementSize == other.elementSize);
+ other.resize(size, true);
+ memcpy(other.buffer, buffer, size * elementSize);
}
- const T & operator[]( uint32_t index ) const
- {
- XA_DEBUG_ASSERT(index < m_size);
- return m_buffer[index];
- }
-
- T & operator[] ( uint32_t index )
+ void destroy()
{
- XA_DEBUG_ASSERT(index < m_size);
- return m_buffer[index];
+ size = 0;
+ XA_FREE(buffer);
+ buffer = nullptr;
+ capacity = 0;
+ size = 0;
}
- uint32_t size() const { return m_size; }
- const T * data() const { return m_buffer; }
- T * data() { return m_buffer; }
- T * begin() { return m_buffer; }
- T * end() { return m_buffer + m_size; }
- const T * begin() const { return m_buffer; }
- const T * end() const { return m_buffer + m_size; }
- bool isEmpty() const { return m_size == 0; }
-
- void push_back( const T & val )
+ // Insert the given element at the given index shifting all the elements up.
+ void insertAt(uint32_t index, const uint8_t *value)
{
- XA_DEBUG_ASSERT(&val < m_buffer || &val >= m_buffer+m_size);
- uint32_t old_size = m_size;
- uint32_t new_size = m_size + 1;
- setArraySize(new_size);
- construct_range(m_buffer, new_size, old_size, val);
+ XA_DEBUG_ASSERT(index >= 0 && index <= size);
+ resize(size + 1, false);
+ if (index < size - 1)
+ memmove(buffer + elementSize * (index + 1), buffer + elementSize * index, elementSize * (size - 1 - index));
+ memcpy(&buffer[index * elementSize], value, elementSize);
}
- void pop_back()
+ void moveTo(ArrayBase &other)
{
- XA_DEBUG_ASSERT( m_size > 0 );
- resize( m_size - 1 );
+ XA_DEBUG_ASSERT(elementSize == other.elementSize);
+ other.destroy();
+ other.buffer = buffer;
+ other.elementSize = elementSize;
+ other.size = size;
+ other.capacity = capacity;
+ other.memTag = memTag;
+ buffer = nullptr;
+ elementSize = size = capacity = 0;
}
- const T & back() const
+ void pop_back()
{
- XA_DEBUG_ASSERT( m_size > 0 );
- return m_buffer[m_size-1];
+ XA_DEBUG_ASSERT(size > 0);
+ resize(size - 1, false);
}
- T & back()
+ void push_back(const uint8_t *value)
{
- XA_DEBUG_ASSERT( m_size > 0 );
- return m_buffer[m_size-1];
+ XA_DEBUG_ASSERT(value < buffer || value >= buffer + size);
+ resize(size + 1, false);
+ memcpy(&buffer[(size - 1) * elementSize], value, elementSize);
}
- const T & front() const
+ // Remove the element at the given index. This is an expensive operation!
+ void removeAt(uint32_t index)
{
- XA_DEBUG_ASSERT( m_size > 0 );
- return m_buffer[0];
+ XA_DEBUG_ASSERT(index >= 0 && index < size);
+ if (size != 1)
+ memmove(buffer + elementSize * index, buffer + elementSize * (index + 1), elementSize * (size - 1 - index));
+ size--;
}
- T & front()
+ void reserve(uint32_t desiredSize)
{
- XA_DEBUG_ASSERT( m_size > 0 );
- return m_buffer[0];
+ if (desiredSize > capacity)
+ setArrayCapacity(desiredSize);
}
- // Remove the element at the given index. This is an expensive operation!
- void removeAt(uint32_t index)
+ void resize(uint32_t newSize, bool exact)
{
- XA_DEBUG_ASSERT(index >= 0 && index < m_size);
- if (m_size == 1) {
- clear();
- }
- else {
- m_buffer[index].~T();
- memmove(m_buffer+index, m_buffer+index+1, sizeof(T) * (m_size - 1 - index));
- m_size--;
+ size = newSize;
+ if (size > capacity) {
+ // First allocation is always exact. Otherwise, following allocations grow array to 150% of desired size.
+ uint32_t newBufferSize;
+ if (capacity == 0 || exact)
+ newBufferSize = size;
+ else
+ newBufferSize = size + (size >> 2);
+ setArrayCapacity(newBufferSize);
}
}
- // Insert the given element at the given index shifting all the elements up.
- void insertAt(uint32_t index, const T & val = T())
+ void setArrayCapacity(uint32_t newCapacity)
{
- XA_DEBUG_ASSERT( index >= 0 && index <= m_size );
- setArraySize(m_size + 1);
- if (index < m_size - 1) {
- memmove(m_buffer+index+1, m_buffer+index, sizeof(T) * (m_size - 1 - index));
+ XA_DEBUG_ASSERT(newCapacity >= size);
+ if (newCapacity == 0) {
+ // free the buffer.
+ if (buffer != nullptr) {
+ XA_FREE(buffer);
+ buffer = nullptr;
+ }
+ } else {
+ // realloc the buffer
+ buffer = XA_REALLOC_SIZE(memTag, buffer, newCapacity * elementSize);
}
- // Copy-construct into the newly opened slot.
- new(m_buffer+index) T(val);
- }
-
- void append(const Array<T> & other)
- {
- append(other.m_buffer, other.m_size);
+ capacity = newCapacity;
}
- void resize(uint32_t new_size)
- {
- uint32_t old_size = m_size;
- // Destruct old elements (if we're shrinking).
- destroy_range(m_buffer, new_size, old_size);
- setArraySize(new_size);
- // Call default constructors
- construct_range(m_buffer, new_size, old_size);
- }
+ uint8_t *buffer;
+ uint32_t elementSize;
+ uint32_t size;
+ uint32_t capacity;
+ int memTag;
+};
- void resize(uint32_t new_size, const T & elem)
- {
- XA_DEBUG_ASSERT(&elem < m_buffer || &elem > m_buffer+m_size);
- uint32_t old_size = m_size;
- // Destruct old elements (if we're shrinking).
- destroy_range(m_buffer, new_size, old_size);
- setArraySize(new_size);
- // Call copy constructors
- construct_range(m_buffer, new_size, old_size, elem);
- }
+template<typename T>
+class Array
+{
+public:
+ Array(int memTag = MemTag::Default) : m_base(sizeof(T), memTag) {}
+ Array(const Array&) = delete;
+ const Array &operator=(const Array &) = delete;
- void clear()
+ XA_INLINE const T &operator[](uint32_t index) const
{
- // Destruct old elements
- destroy_range(m_buffer, 0, m_size);
- m_size = 0;
+ XA_DEBUG_ASSERT(index < m_base.size);
+ return ((const T *)m_base.buffer)[index];
}
- void destroy()
+ XA_INLINE T &operator[](uint32_t index)
{
- clear();
- XA_FREE(m_buffer);
- m_buffer = nullptr;
- m_capacity = 0;
- m_size = 0;
+ XA_DEBUG_ASSERT(index < m_base.size);
+ return ((T *)m_base.buffer)[index];
}
- void reserve(uint32_t desired_size)
+ XA_INLINE const T &back() const
{
- if (desired_size > m_capacity) {
- setArrayCapacity(desired_size);
- }
+ XA_DEBUG_ASSERT(!isEmpty());
+ return ((const T *)m_base.buffer)[m_base.size - 1];
}
- void copy(const T * data, uint32_t count)
- {
- destroy_range(m_buffer, 0, m_size);
- setArraySize(count);
- construct_range(m_buffer, count, 0, data);
- }
+ XA_INLINE T *begin() { return (T *)m_base.buffer; }
+ XA_INLINE void clear() { m_base.clear(); }
+ void copyTo(Array &other) const { m_base.copyTo(other.m_base); }
+ XA_INLINE const T *data() const { return (const T *)m_base.buffer; }
+ XA_INLINE T *data() { return (T *)m_base.buffer; }
+ XA_INLINE T *end() { return (T *)m_base.buffer + m_base.size; }
+ XA_INLINE bool isEmpty() const { return m_base.size == 0; }
+ void insertAt(uint32_t index, const T &value) { m_base.insertAt(index, (const uint8_t *)&value); }
+ void moveTo(Array &other) { m_base.moveTo(other.m_base); }
+ void push_back(const T &value) { m_base.push_back((const uint8_t *)&value); }
+ void pop_back() { m_base.pop_back(); }
+ void removeAt(uint32_t index) { m_base.removeAt(index); }
+ void reserve(uint32_t desiredSize) { m_base.reserve(desiredSize); }
+ void resize(uint32_t newSize) { m_base.resize(newSize, true); }
- void moveTo(Array<T> &other)
+ void setAll(const T &value)
{
- other.destroy();
- swap(m_buffer, other.m_buffer);
- swap(m_capacity, other.m_capacity);
- swap(m_size, other.m_size);
+ auto buffer = (T *)m_base.buffer;
+ for (uint32_t i = 0; i < m_base.size; i++)
+ buffer[i] = value;
}
-protected:
- void setArraySize(uint32_t new_size)
- {
- m_size = new_size;
- if (new_size > m_capacity) {
- uint32_t new_buffer_size;
- if (m_capacity == 0) {
- // first allocation is exact
- new_buffer_size = new_size;
- }
- else {
- // following allocations grow array by 25%
- new_buffer_size = new_size + (new_size >> 2);
- }
- setArrayCapacity( new_buffer_size );
- }
- }
- void setArrayCapacity(uint32_t new_capacity)
- {
- XA_DEBUG_ASSERT(new_capacity >= m_size);
- if (new_capacity == 0) {
- // free the buffer.
- if (m_buffer != nullptr) {
- XA_FREE(m_buffer);
- m_buffer = nullptr;
- }
- }
- else {
- // realloc the buffer
- m_buffer = XA_REALLOC(m_memTag, m_buffer, T, new_capacity);
- }
- m_capacity = new_capacity;
- }
+ XA_INLINE uint32_t size() const { return m_base.size; }
+ XA_INLINE void zeroOutMemory() { memset(m_base.buffer, 0, m_base.elementSize * m_base.size); }
- int m_memTag;
- T * m_buffer;
- uint32_t m_capacity;
- uint32_t m_size;
+private:
+ ArrayBase m_base;
};
-/// Basis class to compute tangent space basis, ortogonalizations and to
-/// transform vectors from one space to another.
+/// Basis class to compute tangent space basis, ortogonalizations and to transform vectors from one space to another.
struct Basis
{
- void buildFrameForDirection(const Vector3 &d, float angle = 0)
+ XA_NODISCARD static Vector3 computeTangent(const Vector3 &normal)
{
- XA_ASSERT(isNormalized(d));
- normal = d;
+ XA_ASSERT(isNormalized(normal));
// Choose minimum axis.
- if (fabsf(normal.x) < fabsf(normal.y) && fabsf(normal.x) < fabsf(normal.z)) {
+ Vector3 tangent;
+ if (fabsf(normal.x) < fabsf(normal.y) && fabsf(normal.x) < fabsf(normal.z))
tangent = Vector3(1, 0, 0);
- } else if (fabsf(normal.y) < fabsf(normal.z)) {
+ else if (fabsf(normal.y) < fabsf(normal.z))
tangent = Vector3(0, 1, 0);
- } else {
+ else
tangent = Vector3(0, 0, 1);
- }
// Ortogonalize
tangent -= normal * dot(normal, tangent);
tangent = normalize(tangent, kEpsilon);
- bitangent = cross(normal, tangent);
- // Rotate frame around normal according to angle.
- if (angle != 0.0f) {
- float c = cosf(angle);
- float s = sinf(angle);
- Vector3 tmp = c * tangent - s * bitangent;
- bitangent = s * tangent + c * bitangent;
- tangent = tmp;
- }
+ return tangent;
+ }
+
+ XA_NODISCARD static Vector3 computeBitangent(const Vector3 &normal, const Vector3 &tangent)
+ {
+ return cross(normal, tangent);
}
Vector3 tangent = Vector3(0.0f);
@@ -1246,7 +1194,7 @@ public:
void resize(uint32_t new_size)
{
m_size = new_size;
- m_wordArray.resize( (m_size + 31) >> 5 );
+ m_wordArray.resize((m_size + 31) >> 5);
}
/// Get bit.
@@ -1286,35 +1234,28 @@ public:
{
m_rowStride = (m_width + 63) >> 6;
m_data.resize(m_rowStride * m_height);
+ m_data.zeroOutMemory();
}
- BitImage(const BitImage &other)
- {
- m_width = other.m_width;
- m_height = other.m_height;
- m_rowStride = other.m_rowStride;
- m_data.resize(m_rowStride * m_height);
- memcpy(m_data.data(), other.m_data.data(), m_rowStride * m_height * sizeof(uint64_t));
- }
+ BitImage(const BitImage &other) = delete;
+ const BitImage &operator=(const BitImage &other) = delete;
+ uint32_t width() const { return m_width; }
+ uint32_t height() const { return m_height; }
- const BitImage &operator=(const BitImage &other)
+ void copyTo(BitImage &other)
{
- m_width = other.m_width;
- m_height = other.m_height;
- m_rowStride = other.m_rowStride;
- m_data = other.m_data;
- return *this;
+ other.m_width = m_width;
+ other.m_height = m_height;
+ other.m_rowStride = m_rowStride;
+ m_data.copyTo(other.m_data);
}
- uint32_t width() const { return m_width; }
- uint32_t height() const { return m_height; }
-
void resize(uint32_t w, uint32_t h, bool discard)
{
const uint32_t rowStride = (w + 63) >> 6;
if (discard) {
m_data.resize(rowStride * h);
- memset(m_data.data(), 0, m_data.size() * sizeof(uint64_t));
+ m_data.zeroOutMemory();
} else {
Array<uint64_t> tmp;
tmp.resize(rowStride * h);
@@ -1351,7 +1292,7 @@ public:
void clearAll()
{
- memset(m_data.data(), 0, m_data.size() * sizeof(uint64_t));
+ m_data.zeroOutMemory();
}
bool canBlit(const BitImage &image, uint32_t offsetX, uint32_t offsetY) const
@@ -1405,7 +1346,7 @@ public:
tmp.setBitAt(x, y);
}
}
- swap(m_data, tmp.m_data);
+ tmp.m_data.copyTo(m_data);
}
}
@@ -1754,36 +1695,28 @@ class FullVector
{
public:
FullVector(uint32_t dim) { m_array.resize(dim); }
- FullVector(const FullVector &v) : m_array(v.m_array) {}
-
- const FullVector &operator=(const FullVector &v)
- {
- XA_ASSERT(dimension() == v.dimension());
- m_array = v.m_array;
- return *this;
- }
-
- uint32_t dimension() const { return m_array.size(); }
- const float &operator[]( uint32_t index ) const { return m_array[index]; }
- float &operator[] ( uint32_t index ) { return m_array[index]; }
+ FullVector(const FullVector &v) { v.m_array.copyTo(m_array); }
+ const FullVector &operator=(const FullVector &v) = delete;
+ XA_INLINE uint32_t dimension() const { return m_array.size(); }
+ XA_INLINE const float &operator[](uint32_t index) const { return m_array[index]; }
+ XA_INLINE float &operator[](uint32_t index) { return m_array[index]; }
void fill(float f)
{
const uint32_t dim = dimension();
- for (uint32_t i = 0; i < dim; i++) {
+ for (uint32_t i = 0; i < dim; i++)
m_array[i] = f;
- }
}
private:
Array<float> m_array;
};
-template<typename Key, typename Value, typename H = Hash<Key>, typename E = Equal<Key> >
+template<typename Key, typename H = Hash<Key>, typename E = Equal<Key> >
class HashMap
{
public:
- HashMap(int memTag, uint32_t size) : m_memTag(memTag), m_size(size), m_numSlots(0), m_slots(nullptr), m_keys(memTag), m_values(memTag), m_next(memTag)
+ HashMap(int memTag, uint32_t size) : m_memTag(memTag), m_size(size), m_numSlots(0), m_slots(nullptr), m_keys(memTag), m_next(memTag)
{
}
@@ -1793,15 +1726,12 @@ public:
XA_FREE(m_slots);
}
- const Value &value(uint32_t index) const { return m_values[index]; }
-
- void add(const Key &key, const Value &value)
+ void add(const Key &key)
{
if (!m_slots)
alloc();
const uint32_t hash = computeHash(key);
m_keys.push_back(key);
- m_values.push_back(value);
m_next.push_back(m_slots[hash]);
m_slots[hash] = m_next.size() - 1;
}
@@ -1842,7 +1772,6 @@ private:
for (uint32_t i = 0; i < m_numSlots; i++)
m_slots[i] = UINT32_MAX;
m_keys.reserve(m_size);
- m_values.reserve(m_size);
m_next.reserve(m_size);
}
@@ -1857,7 +1786,6 @@ private:
uint32_t m_numSlots;
uint32_t *m_slots;
Array<Key> m_keys;
- Array<Value> m_values;
Array<uint32_t> m_next;
};
@@ -2190,7 +2118,7 @@ private:
}
}
// Remove duplicate element.
- XA_DEBUG_ASSERT(output.front() == output.back());
+ XA_DEBUG_ASSERT(output.size() > 0);
output.pop_back();
}
@@ -2284,7 +2212,7 @@ public:
const EdgeKey key(vertex0, vertex1);
if (m_edgeMap.get(key) != UINT32_MAX)
result = AddFaceResult::DuplicateEdge;
- m_edgeMap.add(key, firstIndex + i);
+ m_edgeMap.add(key);
}
}
return result;
@@ -2301,7 +2229,9 @@ public:
Array<uint32_t> colocals;
Array<uint32_t> potential;
m_colocalVertexCount = 0;
- m_nextColocalVertex.resize(vertexCount, UINT32_MAX);
+ m_nextColocalVertex.resize(vertexCount);
+ for (uint32_t i = 0; i < vertexCount; i++)
+ m_nextColocalVertex[i] = UINT32_MAX;
for (uint32_t i = 0; i < vertexCount; i++) {
if (m_nextColocalVertex[i] != UINT32_MAX)
continue; // Already linked.
@@ -2467,12 +2397,10 @@ public:
void linkBoundaries()
{
const uint32_t edgeCount = m_indices.size();
- HashMap<uint32_t, uint32_t> vertexToEdgeMap(MemTag::Mesh, edgeCount);
+ HashMap<uint32_t> vertexToEdgeMap(MemTag::Mesh, edgeCount); // Edge is index / 2
for (uint32_t i = 0; i < edgeCount; i++) {
- const uint32_t vertex0 = m_indices[meshEdgeIndex0(i)];
- const uint32_t vertex1 = m_indices[meshEdgeIndex1(i)];
- vertexToEdgeMap.add(vertex0, i);
- vertexToEdgeMap.add(vertex1, i);
+ vertexToEdgeMap.add(m_indices[meshEdgeIndex0(i)]);
+ vertexToEdgeMap.add(m_indices[meshEdgeIndex1(i)]);
}
m_nextBoundaryEdges.resize(edgeCount);
for (uint32_t i = 0; i < edgeCount; i++)
@@ -2497,9 +2425,9 @@ public:
const uint32_t startVertex = m_indices[meshEdgeIndex1(currentEdge)];
uint32_t bestNextEdge = UINT32_MAX;
for (ColocalVertexIterator it(this, startVertex); !it.isDone(); it.advance()) {
- uint32_t mapOtherEdgeIndex = vertexToEdgeMap.get(it.vertex());
- while (mapOtherEdgeIndex != UINT32_MAX) {
- const uint32_t otherEdge = vertexToEdgeMap.value(mapOtherEdgeIndex);
+ uint32_t mapIndex = vertexToEdgeMap.get(it.vertex());
+ while (mapIndex != UINT32_MAX) {
+ const uint32_t otherEdge = mapIndex / 2; // Two vertices added per edge.
if (m_oppositeEdges[otherEdge] != UINT32_MAX)
goto next; // Not a boundary edge.
if (linkedEdges.bitAt(otherEdge))
@@ -2515,7 +2443,7 @@ public:
if (bestNextEdge != firstEdge && (bestNextEdge == UINT32_MAX || it.vertex() == startVertex))
bestNextEdge = otherEdge;
next:
- mapOtherEdgeIndex = vertexToEdgeMap.getNext(mapOtherEdgeIndex);
+ mapIndex = vertexToEdgeMap.getNext(mapIndex);
}
}
if (bestNextEdge == UINT32_MAX) {
@@ -2567,9 +2495,8 @@ public:
uint32_t result = UINT32_MAX;
if (m_nextColocalVertex.isEmpty()) {
EdgeKey key(vertex0, vertex1);
- uint32_t mapEdgeIndex = m_edgeMap.get(key);
- while (mapEdgeIndex != UINT32_MAX) {
- const uint32_t edge = m_edgeMap.value(mapEdgeIndex);
+ uint32_t edge = m_edgeMap.get(key);
+ while (edge != UINT32_MAX) {
// Don't find edges of ignored faces.
if ((faceGroup == UINT32_MAX || m_faceGroups[meshEdgeFace(edge)] == faceGroup) && !isFaceIgnored(meshEdgeFace(edge))) {
//XA_DEBUG_ASSERT(m_id != UINT32_MAX || (m_id == UINT32_MAX && result == UINT32_MAX)); // duplicate edge - ignore on initial meshes
@@ -2578,15 +2505,14 @@ public:
return result;
#endif
}
- mapEdgeIndex = m_edgeMap.getNext(mapEdgeIndex);
+ edge = m_edgeMap.getNext(edge);
}
} else {
for (ColocalVertexIterator it0(this, vertex0); !it0.isDone(); it0.advance()) {
for (ColocalVertexIterator it1(this, vertex1); !it1.isDone(); it1.advance()) {
EdgeKey key(it0.vertex(), it1.vertex());
- uint32_t mapEdgeIndex = m_edgeMap.get(key);
- while (mapEdgeIndex != UINT32_MAX) {
- const uint32_t edge = m_edgeMap.value(mapEdgeIndex);
+ uint32_t edge = m_edgeMap.get(key);
+ while (edge != UINT32_MAX) {
// Don't find edges of ignored faces.
if ((faceGroup == UINT32_MAX || m_faceGroups[meshEdgeFace(edge)] == faceGroup) && !isFaceIgnored(meshEdgeFace(edge))) {
XA_DEBUG_ASSERT(m_id != UINT32_MAX || (m_id == UINT32_MAX && result == UINT32_MAX)); // duplicate edge - ignore on initial meshes
@@ -2595,7 +2521,7 @@ public:
return result;
#endif
}
- mapEdgeIndex = m_edgeMap.getNext(mapEdgeIndex);
+ edge = m_edgeMap.getNext(edge);
}
}
}
@@ -2802,24 +2728,24 @@ public:
return false;
}
- float epsilon() const { return m_epsilon; }
- uint32_t edgeCount() const { return m_indices.size(); }
- uint32_t oppositeEdge(uint32_t edge) const { return m_oppositeEdges[edge]; }
- bool isBoundaryEdge(uint32_t edge) const { return m_oppositeEdges[edge] == UINT32_MAX; }
- bool isBoundaryVertex(uint32_t vertex) const { return m_boundaryVertices[vertex]; }
- uint32_t colocalVertexCount() const { return m_colocalVertexCount; }
- uint32_t vertexCount() const { return m_positions.size(); }
- uint32_t vertexAt(uint32_t i) const { return m_indices[i]; }
- const Vector3 &position(uint32_t vertex) const { return m_positions[vertex]; }
- const Vector3 &normal(uint32_t vertex) const { XA_DEBUG_ASSERT(m_flags & MeshFlags::HasNormals); return m_normals[vertex]; }
- const Vector2 &texcoord(uint32_t vertex) const { return m_texcoords[vertex]; }
- Vector2 &texcoord(uint32_t vertex) { return m_texcoords[vertex]; }
- Vector2 *texcoords() { return m_texcoords.data(); }
- uint32_t faceCount() const { return m_indices.size() / 3; }
- uint32_t faceGroupCount() const { XA_DEBUG_ASSERT(m_flags & MeshFlags::HasFaceGroups); return m_faceGroups.size(); }
- uint32_t faceGroupAt(uint32_t face) const { XA_DEBUG_ASSERT(m_flags & MeshFlags::HasFaceGroups); return m_faceGroups[face]; }
- const uint32_t *indices() const { return m_indices.data(); }
- uint32_t indexCount() const { return m_indices.size(); }
+ XA_INLINE float epsilon() const { return m_epsilon; }
+ XA_INLINE uint32_t edgeCount() const { return m_indices.size(); }
+ XA_INLINE uint32_t oppositeEdge(uint32_t edge) const { return m_oppositeEdges[edge]; }
+ XA_INLINE bool isBoundaryEdge(uint32_t edge) const { return m_oppositeEdges[edge] == UINT32_MAX; }
+ XA_INLINE bool isBoundaryVertex(uint32_t vertex) const { return m_boundaryVertices[vertex]; }
+ XA_INLINE uint32_t colocalVertexCount() const { return m_colocalVertexCount; }
+ XA_INLINE uint32_t vertexCount() const { return m_positions.size(); }
+ XA_INLINE uint32_t vertexAt(uint32_t i) const { return m_indices[i]; }
+ XA_INLINE const Vector3 &position(uint32_t vertex) const { return m_positions[vertex]; }
+ XA_INLINE const Vector3 &normal(uint32_t vertex) const { XA_DEBUG_ASSERT(m_flags & MeshFlags::HasNormals); return m_normals[vertex]; }
+ XA_INLINE const Vector2 &texcoord(uint32_t vertex) const { return m_texcoords[vertex]; }
+ XA_INLINE Vector2 &texcoord(uint32_t vertex) { return m_texcoords[vertex]; }
+ XA_INLINE Vector2 *texcoords() { return m_texcoords.data(); }
+ XA_INLINE uint32_t faceCount() const { return m_indices.size() / 3; }
+ XA_INLINE uint32_t faceGroupCount() const { XA_DEBUG_ASSERT(m_flags & MeshFlags::HasFaceGroups); return m_faceGroups.size(); }
+ XA_INLINE uint32_t faceGroupAt(uint32_t face) const { XA_DEBUG_ASSERT(m_flags & MeshFlags::HasFaceGroups); return m_faceGroups[face]; }
+ XA_INLINE const uint32_t *indices() const { return m_indices.data(); }
+ XA_INLINE uint32_t indexCount() const { return m_indices.size(); }
private:
bool isFaceIgnored(uint32_t face) const { return (m_flags & MeshFlags::HasIgnoredFaces) && m_faceIgnore[face]; }
@@ -2865,7 +2791,7 @@ private:
uint32_t v1;
};
- HashMap<EdgeKey, uint32_t> m_edgeMap;
+ HashMap<EdgeKey> m_edgeMap;
public:
class BoundaryEdgeIterator
@@ -2950,37 +2876,37 @@ public:
bool isDone() const
{
- return m_vertex0It.isDone() && m_vertex1It.isDone() && m_mapEdgeIndex == UINT32_MAX;
+ return m_vertex0It.isDone() && m_vertex1It.isDone() && m_edge == UINT32_MAX;
}
uint32_t edge() const
{
- return m_mesh->m_edgeMap.value(m_mapEdgeIndex);
+ return m_edge;
}
private:
void resetElement()
{
- m_mapEdgeIndex = m_mesh->m_edgeMap.get(Mesh::EdgeKey(m_vertex0It.vertex(), m_vertex1It.vertex()));
- while (m_mapEdgeIndex != UINT32_MAX) {
+ m_edge = m_mesh->m_edgeMap.get(Mesh::EdgeKey(m_vertex0It.vertex(), m_vertex1It.vertex()));
+ while (m_edge != UINT32_MAX) {
if (!isIgnoredFace())
break;
- m_mapEdgeIndex = m_mesh->m_edgeMap.getNext(m_mapEdgeIndex);
+ m_edge = m_mesh->m_edgeMap.getNext(m_edge);
}
- if (m_mapEdgeIndex == UINT32_MAX)
+ if (m_edge == UINT32_MAX)
advanceVertex1();
}
void advanceElement()
{
for (;;) {
- m_mapEdgeIndex = m_mesh->m_edgeMap.getNext(m_mapEdgeIndex);
- if (m_mapEdgeIndex == UINT32_MAX)
+ m_edge = m_mesh->m_edgeMap.getNext(m_edge);
+ if (m_edge == UINT32_MAX)
break;
if (!isIgnoredFace())
break;
}
- if (m_mapEdgeIndex == UINT32_MAX)
+ if (m_edge == UINT32_MAX)
advanceVertex1();
}
@@ -3004,14 +2930,13 @@ public:
bool isIgnoredFace() const
{
- const uint32_t edge = m_mesh->m_edgeMap.value(m_mapEdgeIndex);
- return m_mesh->m_faceIgnore[meshEdgeFace(edge)];
+ return m_mesh->m_faceIgnore[meshEdgeFace(m_edge)];
}
const Mesh *m_mesh;
ColocalVertexIterator m_vertex0It, m_vertex1It;
const uint32_t m_vertex1;
- uint32_t m_mapEdgeIndex;
+ uint32_t m_edge;
};
class FaceEdgeIterator
@@ -3334,7 +3259,7 @@ static Mesh *meshFixTJunctions(const Mesh &inputMesh, bool *duplicatedEdge, bool
if (splitEdges.isEmpty())
return nullptr;
const uint32_t faceCount = inputMesh.faceCount();
- Mesh *mesh = XA_NEW(MemTag::Mesh, Mesh, inputMesh.epsilon(), vertexCount + splitEdges.size(), faceCount);
+ Mesh *mesh = XA_NEW_ARGS(MemTag::Mesh, Mesh, inputMesh.epsilon(), vertexCount + splitEdges.size(), faceCount);
for (uint32_t v = 0; v < vertexCount; v++)
mesh->addVertex(inputMesh.position(v));
Array<uint32_t> indexArray;
@@ -3567,8 +3492,9 @@ public:
}
m_workers.resize(std::thread::hardware_concurrency() <= 1 ? 1 : std::thread::hardware_concurrency() - 1);
for (uint32_t i = 0; i < m_workers.size(); i++) {
+ new (&m_workers[i]) Worker();
m_workers[i].wakeup = false;
- m_workers[i].thread = XA_NEW(MemTag::Default, std::thread, workerThread, this, &m_workers[i]);
+ m_workers[i].thread = XA_NEW_ARGS(MemTag::Default, std::thread, workerThread, this, &m_workers[i]);
}
}
@@ -3584,6 +3510,7 @@ public:
worker.thread->join();
worker.thread->~thread();
XA_FREE(worker.thread);
+ worker.~Worker();
}
for (uint32_t i = 0; i < m_maxGroups; i++)
m_groups[i].~TaskGroup();
@@ -3770,6 +3697,7 @@ private:
struct UvMeshChart
{
+ Array<uint32_t> faces;
Array<uint32_t> indices;
uint32_t material;
};
@@ -3828,7 +3756,7 @@ public:
m_numVertices = p;
}
- void clipVerticalPlane(float offset, float clipdirection )
+ void clipVerticalPlane(float offset, float clipdirection)
{
Vector2 *v = m_vertexBuffers[m_activeVertexBuffer];
m_activeVertexBuffer ^= 1;
@@ -3877,7 +3805,7 @@ public:
computeArea();
}
- float area()
+ float area() const
{
return m_area;
}
@@ -3959,9 +3887,8 @@ struct Triangle
if ( (aC >= BK_INSIDE) && (bC >= BK_INSIDE) && (cC >= BK_INSIDE) ) {
for (float y = y0; y < y0 + BK_SIZE; y++) {
for (float x = x0; x < x0 + BK_SIZE; x++) {
- if (!cb(param, (int)x, (int)y)) {
+ if (!cb(param, (int)x, (int)y))
return false;
- }
}
}
} else { // Partially covered block
@@ -3974,17 +3901,15 @@ struct Triangle
float CX3 = CY3;
for (float x = x0; x < x0 + BK_SIZE; x++) { // @@ This is not clipping to scissor rectangle correctly.
if (CX1 >= PX_INSIDE && CX2 >= PX_INSIDE && CX3 >= PX_INSIDE) {
- if (!cb(param, (int)x, (int)y)) {
+ if (!cb(param, (int)x, (int)y))
return false;
- }
} else if ((CX1 >= PX_OUTSIDE) && (CX2 >= PX_OUTSIDE) && (CX3 >= PX_OUTSIDE)) {
// triangle partially covers pixel. do clipping.
ClippedTriangle ct(v1 - Vector2(x, y), v2 - Vector2(x, y), v3 - Vector2(x, y));
ct.clipAABox(-0.5, -0.5, 0.5, 0.5);
if (ct.area() > 0.0f) {
- if (!cb(param, (int)x, (int)y)) {
+ if (!cb(param, (int)x, (int)y))
return false;
- }
}
}
CX1 += n1.x;
@@ -4065,18 +3990,28 @@ public:
float v; // value
};
- Matrix(uint32_t d) : m_width(d) { m_array.resize(d); }
- Matrix(uint32_t w, uint32_t h) : m_width(w) { m_array.resize(h); }
- Matrix(const Matrix &m) : m_width(m.m_width) { m_array = m.m_array; }
-
- const Matrix &operator=(const Matrix &m)
+ Matrix(uint32_t d) : m_width(d)
{
- XA_ASSERT(width() == m.width());
- XA_ASSERT(height() == m.height());
- m_array = m.m_array;
- return *this;
+ m_array.resize(d);
+ for (uint32_t i = 0; i < m_array.size(); i++)
+ new (&m_array[i]) Array<Coefficient>();
+ }
+
+ Matrix(uint32_t w, uint32_t h) : m_width(w)
+ {
+ m_array.resize(h);
+ for (uint32_t i = 0; i < m_array.size(); i++)
+ new (&m_array[i]) Array<Coefficient>();
+ }
+
+ ~Matrix()
+ {
+ for (uint32_t i = 0; i < m_array.size(); i++)
+ m_array[i].~Array();
}
+ Matrix(const Matrix &m) = delete;
+ const Matrix &operator=(const Matrix &m) = delete;
uint32_t width() const { return m_width; }
uint32_t height() const { return m_array.size(); }
bool isSquare() const { return width() == height(); }
@@ -4274,428 +4209,7 @@ static void mult(const Matrix &A, const Matrix &B, Matrix &C)
} // namespace sparse
-class JacobiPreconditioner
-{
-public:
- JacobiPreconditioner(const sparse::Matrix &M, bool symmetric) : m_inverseDiagonal(M.width())
- {
- XA_ASSERT(M.isSquare());
- for (uint32_t x = 0; x < M.width(); x++) {
- float elem = M.getCoefficient(x, x);
- //XA_DEBUG_ASSERT( elem != 0.0f ); // This can be zero in the presence of zero area triangles.
- if (symmetric) {
- m_inverseDiagonal[x] = (elem != 0) ? 1.0f / sqrtf(fabsf(elem)) : 1.0f;
- } else {
- m_inverseDiagonal[x] = (elem != 0) ? 1.0f / elem : 1.0f;
- }
- }
- }
-
- void apply(const FullVector &x, FullVector &y) const
- {
- XA_DEBUG_ASSERT(x.dimension() == m_inverseDiagonal.dimension());
- XA_DEBUG_ASSERT(y.dimension() == m_inverseDiagonal.dimension());
- // @@ Wrap vector component-wise product into a separate function.
- const uint32_t D = x.dimension();
- for (uint32_t i = 0; i < D; i++) {
- y[i] = m_inverseDiagonal[i] * x[i];
- }
- }
-
-private:
- FullVector m_inverseDiagonal;
-};
-
-// Linear solvers.
-class Solver
-{
-public:
- // Solve the symmetric system: At·A·x = At·b
- static bool LeastSquaresSolver(const sparse::Matrix &A, const FullVector &b, FullVector &x, float epsilon = 1e-5f)
- {
- XA_DEBUG_ASSERT(A.width() == x.dimension());
- XA_DEBUG_ASSERT(A.height() == b.dimension());
- XA_DEBUG_ASSERT(A.height() >= A.width()); // @@ If height == width we could solve it directly...
- const uint32_t D = A.width();
- sparse::Matrix At(A.height(), A.width());
- sparse::transpose(A, At);
- FullVector Atb(D);
- sparse::mult(At, b, Atb);
- sparse::Matrix AtA(D);
- sparse::mult(At, A, AtA);
- return SymmetricSolver(AtA, Atb, x, epsilon);
- }
-
- // See section 10.4.3 in: Mesh Parameterization: Theory and Practice, Siggraph Course Notes, August 2007
- static bool LeastSquaresSolver(const sparse::Matrix &A, const FullVector &b, FullVector &x, const uint32_t *lockedParameters, uint32_t lockedCount, float epsilon = 1e-5f)
- {
- XA_DEBUG_ASSERT(A.width() == x.dimension());
- XA_DEBUG_ASSERT(A.height() == b.dimension());
- XA_DEBUG_ASSERT(A.height() >= A.width() - lockedCount);
- // @@ This is not the most efficient way of building a system with reduced degrees of freedom. It would be faster to do it on the fly.
- const uint32_t D = A.width() - lockedCount;
- XA_DEBUG_ASSERT(D > 0);
- // Compute: b - Al * xl
- FullVector b_Alxl(b);
- for (uint32_t y = 0; y < A.height(); y++) {
- const uint32_t count = A.getRow(y).size();
- for (uint32_t e = 0; e < count; e++) {
- uint32_t column = A.getRow(y)[e].x;
- bool isFree = true;
- for (uint32_t i = 0; i < lockedCount; i++) {
- isFree &= (lockedParameters[i] != column);
- }
- if (!isFree) {
- b_Alxl[y] -= x[column] * A.getRow(y)[e].v;
- }
- }
- }
- // Remove locked columns from A.
- sparse::Matrix Af(D, A.height());
- for (uint32_t y = 0; y < A.height(); y++) {
- const uint32_t count = A.getRow(y).size();
- for (uint32_t e = 0; e < count; e++) {
- uint32_t column = A.getRow(y)[e].x;
- uint32_t ix = column;
- bool isFree = true;
- for (uint32_t i = 0; i < lockedCount; i++) {
- isFree &= (lockedParameters[i] != column);
- if (column > lockedParameters[i]) ix--; // shift columns
- }
- if (isFree) {
- Af.setCoefficient(ix, y, A.getRow(y)[e].v);
- }
- }
- }
- // Remove elements from x
- FullVector xf(D);
- for (uint32_t i = 0, j = 0; i < A.width(); i++) {
- bool isFree = true;
- for (uint32_t l = 0; l < lockedCount; l++) {
- isFree &= (lockedParameters[l] != i);
- }
- if (isFree) {
- xf[j++] = x[i];
- }
- }
- // Solve reduced system.
- bool result = LeastSquaresSolver(Af, b_Alxl, xf, epsilon);
- // Copy results back to x.
- for (uint32_t i = 0, j = 0; i < A.width(); i++) {
- bool isFree = true;
- for (uint32_t l = 0; l < lockedCount; l++) {
- isFree &= (lockedParameters[l] != i);
- }
- if (isFree) {
- x[i] = xf[j++];
- }
- }
- return result;
- }
-
-private:
- /**
- * Compute the solution of the sparse linear system Ab=x using the Conjugate
- * Gradient method.
- *
- * Solving sparse linear systems:
- * (1) A·x = b
- *
- * The conjugate gradient algorithm solves (1) only in the case that A is
- * symmetric and positive definite. It is based on the idea of minimizing the
- * function
- *
- * (2) f(x) = 1/2·x·A·x - b·x
- *
- * This function is minimized when its gradient
- *
- * (3) df = A·x - b
- *
- * is zero, which is equivalent to (1). The minimization is carried out by
- * generating a succession of search directions p.k and improved minimizers x.k.
- * At each stage a quantity alfa.k is found that minimizes f(x.k + alfa.k·p.k),
- * and x.k+1 is set equal to the new point x.k + alfa.k·p.k. The p.k and x.k are
- * built up in such a way that x.k+1 is also the minimizer of f over the whole
- * vector space of directions already taken, {p.1, p.2, . . . , p.k}. After N
- * iterations you arrive at the minimizer over the entire vector space, i.e., the
- * solution to (1).
- *
- * For a really good explanation of the method see:
- *
- * "An Introduction to the Conjugate Gradient Method Without the Agonizing Pain",
- * Jonhathan Richard Shewchuk.
- *
- **/
- // Conjugate gradient with preconditioner.
- static bool ConjugateGradientSolver(const JacobiPreconditioner &preconditioner, const sparse::Matrix &A, const FullVector &b, FullVector &x, float epsilon)
- {
- XA_DEBUG_ASSERT( A.isSquare() );
- XA_DEBUG_ASSERT( A.width() == b.dimension() );
- XA_DEBUG_ASSERT( A.width() == x.dimension() );
- int i = 0;
- const int D = A.width();
- const int i_max = 4 * D; // Convergence should be linear, but in some cases, it's not.
- FullVector r(D); // residual
- FullVector p(D); // search direction
- FullVector q(D); //
- FullVector s(D); // preconditioned
- float delta_0;
- float delta_old;
- float delta_new;
- float alpha;
- float beta;
- // r = b - A·x
- sparse::copy(b, r);
- sparse::sgemv(-1, A, x, 1, r);
- // p = M^-1 · r
- preconditioner.apply(r, p);
- delta_new = sparse::dot(r, p);
- delta_0 = delta_new;
- while (i < i_max && delta_new > epsilon * epsilon * delta_0) {
- i++;
- // q = A·p
- sparse::mult(A, p, q);
- // alpha = delta_new / p·q
- alpha = delta_new / sparse::dot(p, q);
- // x = alfa·p + x
- sparse::saxpy(alpha, p, x);
- if ((i & 31) == 0) { // recompute r after 32 steps
- // r = b - A·x
- sparse::copy(b, r);
- sparse::sgemv(-1, A, x, 1, r);
- } else {
- // r = r - alfa·q
- sparse::saxpy(-alpha, q, r);
- }
- // s = M^-1 · r
- preconditioner.apply(r, s);
- delta_old = delta_new;
- delta_new = sparse::dot( r, s );
- beta = delta_new / delta_old;
- // p = s + beta·p
- sparse::scal(beta, p);
- sparse::saxpy(1, s, p);
- }
- return delta_new <= epsilon * epsilon * delta_0;
- }
-
- static bool SymmetricSolver(const sparse::Matrix &A, const FullVector &b, FullVector &x, float epsilon = 1e-5f)
- {
- XA_DEBUG_ASSERT(A.height() == A.width());
- XA_DEBUG_ASSERT(A.height() == b.dimension());
- XA_DEBUG_ASSERT(b.dimension() == x.dimension());
- JacobiPreconditioner jacobi(A, true);
- return ConjugateGradientSolver(jacobi, A, b, x, epsilon);
- }
-};
-
-namespace param {
-
-// Fast sweep in 3 directions
-static bool findApproximateDiameterVertices(Mesh *mesh, uint32_t *a, uint32_t *b)
-{
- XA_DEBUG_ASSERT(a != nullptr);
- XA_DEBUG_ASSERT(b != nullptr);
- const uint32_t vertexCount = mesh->vertexCount();
- uint32_t minVertex[3];
- uint32_t maxVertex[3];
- minVertex[0] = minVertex[1] = minVertex[2] = UINT32_MAX;
- maxVertex[0] = maxVertex[1] = maxVertex[2] = UINT32_MAX;
- for (uint32_t v = 1; v < vertexCount; v++) {
- if (mesh->isBoundaryVertex(v)) {
- minVertex[0] = minVertex[1] = minVertex[2] = v;
- maxVertex[0] = maxVertex[1] = maxVertex[2] = v;
- break;
- }
- }
- if (minVertex[0] == UINT32_MAX) {
- // Input mesh has not boundaries.
- return false;
- }
- for (uint32_t v = 1; v < vertexCount; v++) {
- if (!mesh->isBoundaryVertex(v)) {
- // Skip interior vertices.
- continue;
- }
- const Vector3 &pos = mesh->position(v);
- if (pos.x < mesh->position(minVertex[0]).x)
- minVertex[0] = v;
- else if (pos.x > mesh->position(maxVertex[0]).x)
- maxVertex[0] = v;
- if (pos.y < mesh->position(minVertex[1]).y)
- minVertex[1] = v;
- else if (pos.y > mesh->position(maxVertex[1]).y)
- maxVertex[1] = v;
- if (pos.z < mesh->position(minVertex[2]).z)
- minVertex[2] = v;
- else if (pos.z > mesh->position(maxVertex[2]).z)
- maxVertex[2] = v;
- }
- float lengths[3];
- for (int i = 0; i < 3; i++) {
- lengths[i] = length(mesh->position(minVertex[i]) - mesh->position(maxVertex[i]));
- }
- if (lengths[0] > lengths[1] && lengths[0] > lengths[2]) {
- *a = minVertex[0];
- *b = maxVertex[0];
- } else if (lengths[1] > lengths[2]) {
- *a = minVertex[1];
- *b = maxVertex[1];
- } else {
- *a = minVertex[2];
- *b = maxVertex[2];
- }
- return true;
-}
-
-// Conformal relations from Brecht Van Lommel (based on ABF):
-
-static float vec_angle_cos(const Vector3 &v1, const Vector3 &v2, const Vector3 &v3)
-{
- Vector3 d1 = v1 - v2;
- Vector3 d2 = v3 - v2;
- return clamp(dot(d1, d2) / (length(d1) * length(d2)), -1.0f, 1.0f);
-}
-
-static float vec_angle(const Vector3 &v1, const Vector3 &v2, const Vector3 &v3)
-{
- float dot = vec_angle_cos(v1, v2, v3);
- return acosf(dot);
-}
-
-static void triangle_angles(const Vector3 &v1, const Vector3 &v2, const Vector3 &v3, float *a1, float *a2, float *a3)
-{
- *a1 = vec_angle(v3, v1, v2);
- *a2 = vec_angle(v1, v2, v3);
- *a3 = kPi - *a2 - *a1;
-}
-
-static void setup_abf_relations(sparse::Matrix &A, int row, int id0, int id1, int id2, const Vector3 &p0, const Vector3 &p1, const Vector3 &p2)
-{
- // @@ IC: Wouldn't it be more accurate to return cos and compute 1-cos^2?
- // It does indeed seem to be a little bit more robust.
- // @@ Need to revisit this more carefully!
- float a0, a1, a2;
- triangle_angles(p0, p1, p2, &a0, &a1, &a2);
- float s0 = sinf(a0);
- float s1 = sinf(a1);
- float s2 = sinf(a2);
- if (s1 > s0 && s1 > s2) {
- swap(s1, s2);
- swap(s0, s1);
- swap(a1, a2);
- swap(a0, a1);
- swap(id1, id2);
- swap(id0, id1);
- } else if (s0 > s1 && s0 > s2) {
- swap(s0, s2);
- swap(s0, s1);
- swap(a0, a2);
- swap(a0, a1);
- swap(id0, id2);
- swap(id0, id1);
- }
- float c0 = cosf(a0);
- float ratio = (s2 == 0.0f) ? 1.0f : s1 / s2;
- float cosine = c0 * ratio;
- float sine = s0 * ratio;
- // Note : 2*id + 0 --> u
- // 2*id + 1 --> v
- int u0_id = 2 * id0 + 0;
- int v0_id = 2 * id0 + 1;
- int u1_id = 2 * id1 + 0;
- int v1_id = 2 * id1 + 1;
- int u2_id = 2 * id2 + 0;
- int v2_id = 2 * id2 + 1;
- // Real part
- A.setCoefficient(u0_id, 2 * row + 0, cosine - 1.0f);
- A.setCoefficient(v0_id, 2 * row + 0, -sine);
- A.setCoefficient(u1_id, 2 * row + 0, -cosine);
- A.setCoefficient(v1_id, 2 * row + 0, sine);
- A.setCoefficient(u2_id, 2 * row + 0, 1);
- // Imaginary part
- A.setCoefficient(u0_id, 2 * row + 1, sine);
- A.setCoefficient(v0_id, 2 * row + 1, cosine - 1.0f);
- A.setCoefficient(u1_id, 2 * row + 1, -sine);
- A.setCoefficient(v1_id, 2 * row + 1, -cosine);
- A.setCoefficient(v2_id, 2 * row + 1, 1);
-}
-
-static bool computeLeastSquaresConformalMap(Mesh *mesh)
-{
- // For this to work properly, mesh should not have colocals that have the same
- // attributes, unless you want the vertices to actually have different texcoords.
- const uint32_t vertexCount = mesh->vertexCount();
- const uint32_t D = 2 * vertexCount;
- const uint32_t N = 2 * mesh->faceCount();
- // N is the number of equations (one per triangle)
- // D is the number of variables (one per vertex; there are 2 pinned vertices).
- if (N < D - 4) {
- return false;
- }
- sparse::Matrix A(D, N);
- FullVector b(N);
- FullVector x(D);
- // Fill b:
- b.fill(0.0f);
- // Fill x:
- uint32_t v0, v1;
- if (!findApproximateDiameterVertices(mesh, &v0, &v1)) {
- // Mesh has no boundaries.
- return false;
- }
- if (mesh->texcoord(v0) == mesh->texcoord(v1)) {
- // LSCM expects an existing parameterization.
- return false;
- }
- for (uint32_t v = 0; v < vertexCount; v++) {
- // Initial solution.
- x[2 * v + 0] = mesh->texcoord(v).x;
- x[2 * v + 1] = mesh->texcoord(v).y;
- }
- // Fill A:
- const uint32_t faceCount = mesh->faceCount();
- for (uint32_t f = 0, t = 0; f < faceCount; f++) {
- const uint32_t vertex0 = mesh->vertexAt(f * 3 + 0);
- const uint32_t vertex1 = mesh->vertexAt(f * 3 + 1);
- const uint32_t vertex2 = mesh->vertexAt(f * 3 + 2);
- setup_abf_relations(A, t, vertex0, vertex1, vertex2, mesh->position(vertex0), mesh->position(vertex1), mesh->position(vertex2));
- t++;
- }
- const uint32_t lockedParameters[] = {
- 2 * v0 + 0,
- 2 * v0 + 1,
- 2 * v1 + 0,
- 2 * v1 + 1
- };
- // Solve
- Solver::LeastSquaresSolver(A, b, x, lockedParameters, 4, 0.000001f);
- // Map x back to texcoords:
- for (uint32_t v = 0; v < vertexCount; v++)
- mesh->texcoord(v) = Vector2(x[2 * v + 0], x[2 * v + 1]);
- return true;
-}
-
-static bool computeOrthogonalProjectionMap(Mesh *mesh)
-{
- uint32_t vertexCount = mesh->vertexCount();
- // Avoid redundant computations.
- float matrix[6];
- Fit::computeCovariance(vertexCount, &mesh->position(0), matrix);
- if (matrix[0] == 0 && matrix[3] == 0 && matrix[5] == 0)
- return false;
- float eigenValues[3];
- Vector3 eigenVectors[3];
- if (!Fit::eigenSolveSymmetric3(matrix, eigenValues, eigenVectors))
- return false;
- Vector3 axis[2];
- axis[0] = normalize(eigenVectors[0], kEpsilon);
- axis[1] = normalize(eigenVectors[1], kEpsilon);
- // Project vertices to plane.
- for (uint32_t i = 0; i < vertexCount; i++)
- mesh->texcoord(i) = Vector2(dot(axis[0], mesh->position(i)), dot(axis[1], mesh->position(i)));
- return true;
-}
+namespace segment {
// Dummy implementation of a priority queue using sort at insertion.
// - Insertion is o(n)
@@ -4729,6 +4243,7 @@ struct PriorityQueue
uint32_t pop()
{
+ XA_DEBUG_ASSERT(!pairs.isEmpty());
uint32_t f = pairs.back().face;
pairs.pop_back();
return f;
@@ -4740,12 +4255,12 @@ struct PriorityQueue
std::sort(pairs.begin(), pairs.end());
}
- void clear()
+ XA_INLINE void clear()
{
pairs.clear();
}
- uint32_t count() const
+ XA_INLINE uint32_t count() const
{
return pairs.size();
}
@@ -4771,7 +4286,7 @@ struct PriorityQueue
Array<Pair> pairs;
};
-struct ChartBuildData
+struct Chart
{
int id = -1;
Vector3 averageNormal = Vector3(0.0f);
@@ -4786,31 +4301,39 @@ struct ChartBuildData
Basis basis; // Of first face.
};
-struct AtlasBuilder
+struct Atlas
{
// @@ Hardcoded to 10?
- AtlasBuilder(const Mesh *mesh, Array<uint32_t> *meshFaces, const ChartOptions &options) : m_mesh(mesh), m_meshFaces(meshFaces), m_facesLeft(mesh->faceCount()), m_bestTriangles(10), m_options(options)
+ Atlas(const Mesh *mesh, Array<uint32_t> *meshFaces, const ChartOptions &options) : m_mesh(mesh), m_meshFaces(meshFaces), m_facesLeft(mesh->faceCount()), m_bestTriangles(10), m_options(options)
{
- XA_PROFILE_START(atlasBuilderInit)
+ XA_PROFILE_START(buildAtlasInit)
const uint32_t faceCount = m_mesh->faceCount();
if (meshFaces) {
- m_ignoreFaces.resize(faceCount, true);
+ m_ignoreFaces.resize(faceCount);
+ m_ignoreFaces.setAll(true);
for (uint32_t f = 0; f < meshFaces->size(); f++)
m_ignoreFaces[(*meshFaces)[f]] = false;
m_facesLeft = meshFaces->size();
} else {
- m_ignoreFaces.resize(faceCount, false);
+ m_ignoreFaces.resize(faceCount);
+ m_ignoreFaces.setAll(false);
}
- m_faceChartArray.resize(faceCount, -1);
- m_faceCandidateArray.resize(faceCount, (uint32_t)-1);
+ m_faceChartArray.resize(faceCount);
+ m_faceChartArray.setAll(-1);
+ m_faceCandidateCharts.resize(faceCount);
+ m_faceCandidateCosts.resize(faceCount);
m_texcoords.resize(faceCount * 3);
// @@ Floyd for the whole mesh is too slow. We could compute floyd progressively per patch as the patch grows. We need a better solution to compute most central faces.
//computeShortestPaths();
// Precompute edge lengths and face areas.
const uint32_t edgeCount = m_mesh->edgeCount();
- m_edgeLengths.resize(edgeCount, 0.0f);
- m_faceAreas.resize(m_mesh->faceCount(), 0.0f);
- m_faceNormals.resize(m_mesh->faceCount());
+ m_edgeLengths.resize(edgeCount);
+ m_edgeLengths.zeroOutMemory();
+ m_faceAreas.resize(faceCount);
+ m_faceAreas.zeroOutMemory();
+ m_faceNormals.resize(faceCount);
+ m_faceTangents.resize(faceCount);
+ m_faceBitangents.resize(faceCount);
for (uint32_t f = 0; f < faceCount; f++) {
if (m_ignoreFaces[f])
continue;
@@ -4821,15 +4344,52 @@ struct AtlasBuilder
m_faceAreas[f] = mesh->faceArea(f);
XA_DEBUG_ASSERT(m_faceAreas[f] > 0.0f);
m_faceNormals[f] = m_mesh->triangleNormal(f);
+ m_faceTangents[f] = Basis::computeTangent(m_faceNormals[f]);
+ m_faceBitangents[f] = Basis::computeBitangent(m_faceNormals[f], m_faceTangents[f]);
}
- XA_PROFILE_END(atlasBuilderInit)
+#if XA_GROW_CHARTS_COPLANAR
+ // Precompute regions of coplanar incident faces.
+ m_nextPlanarRegionFace.resize(faceCount);
+ for (uint32_t f = 0; f < faceCount; f++)
+ m_nextPlanarRegionFace[f] = f;
+ Array<uint32_t> faceStack;
+ faceStack.reserve(min(faceCount, 16u));
+ for (uint32_t f = 0; f < faceCount; f++) {
+ if (m_nextPlanarRegionFace[f] != f)
+ continue; // Already assigned.
+ if (m_ignoreFaces[f])
+ continue;
+ faceStack.clear();
+ faceStack.push_back(f);
+ for (;;) {
+ if (faceStack.isEmpty())
+ break;
+ const uint32_t face = faceStack.back();
+ faceStack.pop_back();
+ for (Mesh::FaceEdgeIterator it(m_mesh, face); !it.isDone(); it.advance()) {
+ const uint32_t oface = it.oppositeFace();
+ if (it.isBoundary() || m_ignoreFaces[oface])
+ continue;
+ if (m_nextPlanarRegionFace[oface] != oface)
+ continue; // Already assigned.
+ if (!equal(dot(m_faceNormals[face], m_faceNormals[oface]), 1.0f, kEpsilon))
+ continue; // Not coplanar.
+ const uint32_t next = m_nextPlanarRegionFace[face];
+ m_nextPlanarRegionFace[face] = oface;
+ m_nextPlanarRegionFace[oface] = next;
+ faceStack.push_back(oface);
+ }
+ }
+ }
+#endif
+ XA_PROFILE_END(buildAtlasInit)
}
- ~AtlasBuilder()
+ ~Atlas()
{
const uint32_t chartCount = m_chartArray.size();
for (uint32_t i = 0; i < chartCount; i++) {
- m_chartArray[i]->~ChartBuildData();
+ m_chartArray[i]->~Chart();
XA_FREE(m_chartArray[i]);
}
}
@@ -4838,9 +4398,11 @@ struct AtlasBuilder
uint32_t chartCount() const { return m_chartArray.size(); }
const Array<uint32_t> &chartFaces(uint32_t i) const { return m_chartArray[i]->faces; }
const Basis &chartBasis(uint32_t chartIndex) const { return m_chartArray[chartIndex]->basis; }
+ const Vector2 *faceTexcoords(uint32_t face) const { return &m_texcoords[face * 3]; }
void placeSeeds(float threshold)
{
+ XA_PROFILE_START(buildAtlasPlaceSeeds)
// Instead of using a predefiened number of seeds:
// - Add seeds one by one, growing chart until a certain treshold.
// - Undo charts and restart growing process.
@@ -4849,43 +4411,44 @@ struct AtlasBuilder
// - how do we weight the probabilities?
while (m_facesLeft > 0)
createRandomChart(threshold);
+ XA_PROFILE_END(buildAtlasPlaceSeeds)
}
// Returns true if any of the charts can grow more.
- bool growCharts(float threshold, uint32_t faceCount)
- {
- XA_PROFILE_START(atlasBuilderGrowCharts)
- // Using one global list.
- faceCount = min(faceCount, m_facesLeft);
+ bool growCharts(float threshold)
+ {
+ XA_PROFILE_START(buildAtlasGrowCharts)
+ // Build global candidate list.
+ m_faceCandidateCharts.zeroOutMemory();
+ for (uint32_t i = 0; i < m_chartArray.size(); i++)
+ addChartCandidateToGlobalCandidates(m_chartArray[i]);
+ // Add one candidate face per chart (threshold permitting).
+ const uint32_t faceCount = m_mesh->faceCount();
bool canAddAny = false;
- for (uint32_t i = 0; i < faceCount; i++) {
- const Candidate &candidate = getBestCandidate();
- if (candidate.metric > threshold) {
- XA_PROFILE_END(atlasBuilderGrowCharts)
- return false; // Can't grow more.
- }
- createFaceTexcoords(candidate.chart, candidate.face);
- if (!canAddFaceToChart(candidate.chart, candidate.face))
+ for (uint32_t f = 0; f < faceCount; f++) {
+ Chart *chart = m_faceCandidateCharts[f];
+ if (!chart || m_faceCandidateCosts[f] > threshold)
+ continue;
+ createFaceTexcoords(chart, f);
+ if (!canAddFaceToChart(chart, f))
continue;
- addFaceToChart(candidate.chart, candidate.face);
+ addFaceToChart(chart, f);
canAddAny = true;
}
- XA_PROFILE_END(atlasBuilderGrowCharts)
+ XA_PROFILE_END(buildAtlasGrowCharts)
return canAddAny && m_facesLeft != 0; // Can continue growing.
}
void resetCharts()
{
+ XA_PROFILE_START(buildAtlasResetCharts)
const uint32_t faceCount = m_mesh->faceCount();
- for (uint32_t i = 0; i < faceCount; i++) {
+ for (uint32_t i = 0; i < faceCount; i++)
m_faceChartArray[i] = -1;
- m_faceCandidateArray[i] = (uint32_t)-1;
- }
m_facesLeft = m_meshFaces ? m_meshFaces->size() : faceCount;
- m_candidateArray.clear();
const uint32_t chartCount = m_chartArray.size();
for (uint32_t i = 0; i < chartCount; i++) {
- ChartBuildData *chart = m_chartArray[i];
+ Chart *chart = m_chartArray[i];
const uint32_t seed = chart->seeds.back();
chart->area = 0.0f;
chart->boundaryLength = 0.0f;
@@ -4898,30 +4461,32 @@ struct AtlasBuilder
}
#if XA_GROW_CHARTS_COPLANAR
for (uint32_t i = 0; i < chartCount; i++) {
- ChartBuildData *chart = m_chartArray[i];
+ Chart *chart = m_chartArray[i];
growChartCoplanar(chart);
}
#endif
+ XA_PROFILE_END(buildAtlasResetCharts)
}
- void updateCandidates(ChartBuildData *chart, uint32_t f)
+ void updateChartCandidates(Chart *chart, uint32_t f)
{
// Traverse neighboring faces, add the ones that do not belong to any chart yet.
for (Mesh::FaceEdgeIterator it(m_mesh, f); !it.isDone(); it.advance()) {
if (!it.isBoundary() && !m_ignoreFaces[it.oppositeFace()] && m_faceChartArray[it.oppositeFace()] == -1)
chart->candidates.push(it.oppositeFace());
}
- }
-
- void updateProxies()
- {
- const uint32_t chartCount = m_chartArray.size();
- for (uint32_t i = 0; i < chartCount; i++)
- updateProxy(m_chartArray[i]);
+ // Re-evaluate all candidate priorities.
+ uint32_t candidateCount = chart->candidates.count();
+ for (uint32_t i = 0; i < candidateCount; i++) {
+ PriorityQueue::Pair &pair = chart->candidates.pairs[i];
+ pair.priority = evaluateCost(chart, pair.face);
+ }
+ chart->candidates.sort();
}
bool relocateSeeds()
{
+ XA_PROFILE_START(buildAtlasRelocateSeeds)
bool anySeedChanged = false;
const uint32_t chartCount = m_chartArray.size();
for (uint32_t i = 0; i < chartCount; i++) {
@@ -4929,19 +4494,22 @@ struct AtlasBuilder
anySeedChanged = true;
}
}
+ XA_PROFILE_END(buildAtlasRelocateSeeds)
return anySeedChanged;
}
void fillHoles(float threshold)
{
+ XA_PROFILE_START(buildAtlasFillHoles)
while (m_facesLeft > 0)
createRandomChart(threshold);
+ XA_PROFILE_END(buildAtlasFillHoles)
}
#if XA_MERGE_CHARTS
void mergeCharts()
{
- XA_PROFILE_START(atlasBuilderMergeCharts)
+ XA_PROFILE_START(buildAtlasMergeCharts)
Array<float> sharedBoundaryLengths;
Array<float> sharedBoundaryLengthsNoSeams;
Array<uint32_t> sharedBoundaryEdgeCountNoSeams;
@@ -4951,16 +4519,19 @@ struct AtlasBuilder
for (;;) {
bool merged = false;
for (int c = chartCount - 1; c >= 0; c--) {
- ChartBuildData *chart = m_chartArray[c];
+ Chart *chart = m_chartArray[c];
if (chart == nullptr)
continue;
float externalBoundaryLength = 0.0f;
sharedBoundaryLengths.clear();
- sharedBoundaryLengths.resize(chartCount, 0.0f);
+ sharedBoundaryLengths.resize(chartCount);
+ sharedBoundaryLengths.zeroOutMemory();
sharedBoundaryLengthsNoSeams.clear();
- sharedBoundaryLengthsNoSeams.resize(chartCount, 0.0f);
+ sharedBoundaryLengthsNoSeams.resize(chartCount);
+ sharedBoundaryLengthsNoSeams.zeroOutMemory();
sharedBoundaryEdgeCountNoSeams.clear();
- sharedBoundaryEdgeCountNoSeams.resize(chartCount, 0u);
+ sharedBoundaryEdgeCountNoSeams.resize(chartCount);
+ sharedBoundaryEdgeCountNoSeams.zeroOutMemory();
const uint32_t faceCount = chart->faces.size();
for (uint32_t i = 0; i < faceCount; i++) {
const uint32_t f = chart->faces[i];
@@ -4985,7 +4556,7 @@ struct AtlasBuilder
for (int cc = chartCount - 1; cc >= 0; cc--) {
if (cc == c)
continue;
- ChartBuildData *chart2 = m_chartArray[cc];
+ Chart *chart2 = m_chartArray[cc];
if (chart2 == nullptr)
continue;
// Compare proxies.
@@ -5013,16 +4584,19 @@ struct AtlasBuilder
continue;
merge:
// Create texcoords for chart 2 using chart 1 basis. Backup chart 2 texcoords for restoration if charts cannot be merged.
- tempTexcoords.resize(chart2->faces.size());
+ tempTexcoords.resize(chart2->faces.size() * 3);
for (uint32_t i = 0; i < chart2->faces.size(); i++) {
const uint32_t face = chart2->faces[i];
- tempTexcoords[i] = m_texcoords[face];
+ for (uint32_t j = 0; j < 3; j++)
+ tempTexcoords[i * 3 + j] = m_texcoords[face * 3 + j];
createFaceTexcoords(chart, face);
}
if (!canMergeCharts(chart, chart2)) {
// Restore chart 2 texcoords.
- for (uint32_t i = 0; i < chart2->faces.size(); i++)
- m_texcoords[chart2->faces[i]] = tempTexcoords[i];
+ for (uint32_t i = 0; i < chart2->faces.size(); i++) {
+ for (uint32_t j = 0; j < 3; j++)
+ m_texcoords[chart2->faces[i] * 3 + j] = tempTexcoords[i * 3 + j];
+ }
continue;
}
mergeChart(chart, chart2, sharedBoundaryLengthsNoSeams[cc]);
@@ -5053,14 +4627,14 @@ struct AtlasBuilder
c++;
}
}
- XA_PROFILE_END(atlasBuilderMergeCharts)
+ XA_PROFILE_END(buildAtlasMergeCharts)
}
#endif
private:
void createRandomChart(float threshold)
{
- ChartBuildData *chart = XA_NEW(MemTag::Default, ChartBuildData);
+ Chart *chart = XA_NEW(MemTag::Default, Chart);
chart->id = (int)m_chartArray.size();
m_chartArray.push_back(chart);
// Pick random face that is not used by any chart yet.
@@ -5070,15 +4644,52 @@ private:
face = 0;
}
chart->seeds.push_back(face);
- addFaceToChart(chart, face, true);
+ addFaceToChart(chart, face);
#if XA_GROW_CHARTS_COPLANAR
growChartCoplanar(chart);
#endif
// Grow the chart as much as possible within the given threshold.
- growChart(chart, threshold, m_facesLeft);
+ for (uint32_t i = 0; i < m_facesLeft; ) {
+ if (chart->candidates.count() == 0 || chart->candidates.firstPriority() > threshold)
+ break;
+ const uint32_t f = chart->candidates.pop();
+ if (m_faceChartArray[f] != -1)
+ continue;
+ createFaceTexcoords(chart, f);
+ if (!canAddFaceToChart(chart, f))
+ continue;
+ addFaceToChart(chart, f);
+ i++;
+ }
+ }
+
+ void addChartCandidateToGlobalCandidates(Chart *chart)
+ {
+ if (chart->candidates.count() == 0)
+ return;
+ const float cost = chart->candidates.firstPriority();
+ const uint32_t face = chart->candidates.pop();
+ if (m_faceChartArray[face] != -1) {
+ addChartCandidateToGlobalCandidates(chart);
+ } else if (!m_faceCandidateCharts[face]) {
+ // No candidate assigned to this face yet.
+ m_faceCandidateCharts[face] = chart;
+ m_faceCandidateCosts[face] = cost;
+ } else {
+ if (cost < m_faceCandidateCosts[face]) {
+ // This is a better candidate for this face (lower cost). The other chart can choose another candidate.
+ Chart *otherChart = m_faceCandidateCharts[face];
+ m_faceCandidateCharts[face] = chart;
+ m_faceCandidateCosts[face] = cost;
+ addChartCandidateToGlobalCandidates(otherChart);
+ } else {
+ // Existing candidate is better. This chart can choose another candidate.
+ addChartCandidateToGlobalCandidates(chart);
+ }
+ }
}
- void createFaceTexcoords(ChartBuildData *chart, uint32_t face)
+ void createFaceTexcoords(Chart *chart, uint32_t face)
{
for (uint32_t i = 0; i < 3; i++) {
const Vector3 &pos = m_mesh->position(m_mesh->vertexAt(face * 3 + i));
@@ -5086,30 +4697,71 @@ private:
}
}
- bool isChartBoundaryEdge(ChartBuildData *chart, uint32_t edge) const
+ bool isChartBoundaryEdge(const Chart *chart, uint32_t edge) const
{
const uint32_t oppositeEdge = m_mesh->oppositeEdge(edge);
const uint32_t oppositeFace = meshEdgeFace(oppositeEdge);
return oppositeEdge == UINT32_MAX || m_ignoreFaces[oppositeFace] || m_faceChartArray[oppositeFace] != chart->id;
}
- bool canAddFaceToChart(ChartBuildData *chart, uint32_t face)
+ bool edgeArraysIntersect(const uint32_t *edges1, uint32_t edges1Count, const uint32_t *edges2, uint32_t edges2Count)
+ {
+ for (uint32_t i = 0; i < edges1Count; i++) {
+ const uint32_t edge1 = edges1[i];
+ for (uint32_t j = 0; j < edges2Count; j++) {
+ const uint32_t edge2 = edges2[j];
+ const Vector2 &a1 = m_texcoords[meshEdgeIndex0(edge1)];
+ const Vector2 &a2 = m_texcoords[meshEdgeIndex1(edge1)];
+ const Vector2 &b1 = m_texcoords[meshEdgeIndex0(edge2)];
+ const Vector2 &b2 = m_texcoords[meshEdgeIndex1(edge2)];
+ if (linesIntersect(a1, a2, b1, b2, m_mesh->epsilon()))
+ return true;
+ }
+ }
+ return false;
+ }
+
+ bool isFaceFlipped(uint32_t face) const
+ {
+ const float t1 = m_texcoords[face * 3 + 0].x;
+ const float s1 = m_texcoords[face * 3 + 0].y;
+ const float t2 = m_texcoords[face * 3 + 1].x;
+ const float s2 = m_texcoords[face * 3 + 1].y;
+ const float t3 = m_texcoords[face * 3 + 2].x;
+ const float s3 = m_texcoords[face * 3 + 2].y;
+ const float parametricArea = ((s2 - s1) * (t3 - t1) - (s3 - s1) * (t2 - t1)) / 2;
+ return parametricArea < 0.0f;
+ }
+
+ void computeChartBoundaryEdges(const Chart *chart, Array<uint32_t> *dest) const
{
- // Find face edges that are on a mesh boundary or form a boundary with another chart.
- uint32_t edgesToCompare[3];
+ dest->clear();
+ for (uint32_t f = 0; f < chart->faces.size(); f++) {
+ const uint32_t face = chart->faces[f];
+ for (uint32_t i = 0; i < 3; i++) {
+ const uint32_t edge = face * 3 + i;
+ if (isChartBoundaryEdge(chart, edge))
+ dest->push_back(edge);
+ }
+ }
+ }
+
+ bool canAddFaceToChart(Chart *chart, uint32_t face)
+ {
+ // Check for flipped triangles.
+ if (isFaceFlipped(face))
+ return false;
+ // Find face edges that don't border this chart.
+ m_tempEdges1.clear();
for (uint32_t i = 0; i < 3; i++) {
const uint32_t edge = face * 3 + i;
- const uint32_t oppositeEdge = m_mesh->oppositeEdge(edge);
- const uint32_t oppositeFace = meshEdgeFace(oppositeEdge);
- if (oppositeEdge == UINT32_MAX || m_ignoreFaces[oppositeFace] || m_faceChartArray[oppositeFace] != chart->id)
- edgesToCompare[i] = edge;
- else
- edgesToCompare[i] = UINT32_MAX;
+ if (isChartBoundaryEdge(chart, edge))
+ m_tempEdges1.push_back(edge);
}
- // All edges on boundary? This can happen if the face is surrounded by the chart.
- if (edgesToCompare[0] == UINT32_MAX && edgesToCompare[1] == UINT32_MAX && edgesToCompare[2] == UINT32_MAX)
- return true;
- // Check if any valid face edge intersects the chart boundary.
+ if (m_tempEdges1.isEmpty())
+ return true; // This can happen if the face is surrounded by the chart.
+ // Get chart boundary edges, except those that border the face.
+ m_tempEdges2.clear();
for (uint32_t i = 0; i < chart->faces.size(); i++) {
const uint32_t chartFace = chart->faces[i];
for (uint32_t j = 0; j < 3; j++) {
@@ -5120,47 +4772,65 @@ private:
const uint32_t oppositeChartEdge = m_mesh->oppositeEdge(chartEdge);
if (meshEdgeFace(oppositeChartEdge) == face)
continue;
- for (uint32_t k = 0; k < 3; k++) {
- if (edgesToCompare[k] == UINT32_MAX)
- continue;
- const uint32_t e1 = chartEdge;
- const uint32_t e2 = edgesToCompare[k];
- if (linesIntersect(m_texcoords[meshEdgeIndex0(e1)], m_texcoords[meshEdgeIndex1(e1)], m_texcoords[meshEdgeIndex0(e2)], m_texcoords[meshEdgeIndex1(e2)], m_mesh->epsilon()))
- return false;
- }
+ m_tempEdges2.push_back(chartEdge);
}
}
- return true;
- }
-
- bool canMergeCharts(ChartBuildData *chart1, ChartBuildData *chart2)
- {
- for (uint32_t f1 = 0; f1 < chart1->faces.size(); f1++) {
- const uint32_t face1 = chart1->faces[f1];
- for (uint32_t i = 0; i < 3; i++) {
- const uint32_t edge1 = face1 * 3 + i;
- if (!isChartBoundaryEdge(chart1, edge1))
- continue;
- for (uint32_t f2 = 0; f2 < chart2->faces.size(); f2++) {
- const uint32_t face2 = chart2->faces[f2];
+ const bool intersect = edgeArraysIntersect(m_tempEdges1.data(), m_tempEdges1.size(), m_tempEdges2.data(), m_tempEdges2.size());
+#if 0
+ if (intersect) {
+ static std::atomic<uint32_t> count = 0;
+ char filename[256];
+ XA_SPRINTF(filename, sizeof(filename), "intersect%04u.obj", count.fetch_add(1));
+ FILE *file;
+ XA_FOPEN(file, filename, "w");
+ if (file) {
+ for (uint32_t i = 0; i < m_texcoords.size(); i++)
+ fprintf(file, "v %g %g 0.0\n", m_texcoords[i].x, m_texcoords[i].y);
+ fprintf(file, "s off\n");
+ fprintf(file, "o face\n");
+ {
+ fprintf(file, "f ");
for (uint32_t j = 0; j < 3; j++) {
- const uint32_t edge2 = face2 * 3 + j;
- if (!isChartBoundaryEdge(chart2, edge2))
- continue;
- if (linesIntersect(m_texcoords[meshEdgeIndex0(edge1)], m_texcoords[meshEdgeIndex1(edge1)], m_texcoords[meshEdgeIndex0(edge2)], m_texcoords[meshEdgeIndex1(edge2)], m_mesh->epsilon()))
- return false;
+ const uint32_t index = face * 3 + j + 1; // 1-indexed
+ fprintf(file, "%d/%d/%d%c", index, index, index, j == 2 ? '\n' : ' ');
+ }
+ }
+ fprintf(file, "s off\n");
+ fprintf(file, "o chart\n");
+ for (uint32_t i = 0; i < chart->faces.size(); i++) {
+ const uint32_t chartFace = chart->faces[i];
+ fprintf(file, "f ");
+ for (uint32_t j = 0; j < 3; j++) {
+ const uint32_t index = chartFace * 3 + j + 1; // 1-indexed
+ fprintf(file, "%d/%d/%d%c", index, index, index, j == 2 ? '\n' : ' ');
}
}
+ fclose(file);
}
}
- return true;
+#endif
+ return !intersect;
+ }
+
+ bool canMergeCharts(Chart *chart1, Chart *chart2)
+ {
+ for (uint32_t i = 0; i < chart2->faces.size(); i++) {
+ if (isFaceFlipped(chart2->faces[i]))
+ return false;
+ }
+ computeChartBoundaryEdges(chart1, &m_tempEdges1);
+ computeChartBoundaryEdges(chart2, &m_tempEdges2);
+ return !edgeArraysIntersect(m_tempEdges1.data(), m_tempEdges1.size(), m_tempEdges2.data(), m_tempEdges2.size());
}
- void addFaceToChart(ChartBuildData *chart, uint32_t f, bool recomputeProxy = false)
+ void addFaceToChart(Chart *chart, uint32_t f)
{
+ const bool firstFace = chart->faces.isEmpty();
// Use the first face normal as the chart basis.
- if (chart->faces.isEmpty()) {
- chart->basis.buildFrameForDirection(m_faceNormals[f]);
+ if (firstFace) {
+ chart->basis.normal = m_faceNormals[f];
+ chart->basis.tangent = m_faceTangents[f];
+ chart->basis.bitangent = m_faceBitangents[f];
createFaceTexcoords(chart, f);
}
// Add face to chart.
@@ -5169,74 +4839,36 @@ private:
m_faceChartArray[f] = chart->id;
m_facesLeft--;
// Update area and boundary length.
- chart->area = evaluateChartArea(chart, f);
- chart->boundaryLength = evaluateBoundaryLength(chart, f);
- chart->normalSum = evaluateChartNormalSum(chart, f);
+ chart->area = chart->area + m_faceAreas[f];
+ chart->boundaryLength = computeBoundaryLength(chart, f);
+ chart->normalSum += m_mesh->triangleNormalAreaScaled(f);
+ chart->averageNormal = normalizeSafe(chart->normalSum, Vector3(0), 0.0f);
chart->centroidSum += m_mesh->triangleCenter(f);
- if (recomputeProxy) {
- // Update proxy and candidate's priorities.
- updateProxy(chart);
- }
+ chart->centroid = chart->centroidSum / float(chart->faces.size());
// Update candidates.
- removeCandidate(f);
- updateCandidates(chart, f);
- updatePriorities(chart);
- }
-
- bool growChart(ChartBuildData *chart, float threshold, uint32_t faceCount)
- {
- // Try to add faceCount faces within threshold to chart.
- for (uint32_t i = 0; i < faceCount; ) {
- if (chart->candidates.count() == 0 || chart->candidates.firstPriority() > threshold)
- return false;
- const uint32_t f = chart->candidates.pop();
- if (m_faceChartArray[f] != -1)
- continue;
- createFaceTexcoords(chart, f);
- if (!canAddFaceToChart(chart, f))
- continue;
- addFaceToChart(chart, f);
- i++;
- }
- if (chart->candidates.count() == 0 || chart->candidates.firstPriority() > threshold)
- return false;
- return true;
+ updateChartCandidates(chart, f);
}
#if XA_GROW_CHARTS_COPLANAR
- void growChartCoplanar(ChartBuildData *chart)
+ void growChartCoplanar(Chart *chart)
{
XA_DEBUG_ASSERT(!chart->faces.isEmpty());
- const Vector3 chartNormal = m_faceNormals[chart->faces[0]];
- m_growFaces.clear();
- for (uint32_t f = 0; f < chart->faces.size(); f++)
- m_growFaces.push_back(chart->faces[f]);
- for (;;) {
- if (m_growFaces.isEmpty())
- break;
- const uint32_t face = m_growFaces.back();
- m_growFaces.pop_back();
- for (Mesh::FaceEdgeIterator it(m_mesh, face); !it.isDone(); it.advance()) {
- if (it.isBoundary() || m_ignoreFaces[it.oppositeFace()] || m_faceChartArray[it.oppositeFace()] != -1)
- continue;
- if (equal(dot(chartNormal, m_faceNormals[it.oppositeFace()]), 1.0f, kEpsilon)) {
- createFaceTexcoords(chart, it.oppositeFace());
- addFaceToChart(chart, it.oppositeFace());
- m_growFaces.push_back(it.oppositeFace());
+ for (uint32_t i = 0; i < chart->faces.size(); i++) {
+ const uint32_t chartFace = chart->faces[i];
+ uint32_t face = m_nextPlanarRegionFace[chartFace];
+ while (face != chartFace) {
+ // Not assigned to a chart?
+ if (m_faceChartArray[face] == -1) {
+ createFaceTexcoords(chart, face);
+ addFaceToChart(chart, face);
}
+ face = m_nextPlanarRegionFace[face];
}
}
}
#endif
- void updateProxy(ChartBuildData *chart) const
- {
- //#pragma message(NV_FILE_LINE "TODO: Use best fit plane instead of average normal.")
- chart->averageNormal = normalizeSafe(chart->normalSum, Vector3(0), 0.0f);
- chart->centroid = chart->centroidSum / float(chart->faces.size());
- }
-
- bool relocateSeed(ChartBuildData *chart)
+ bool relocateSeed(Chart *chart)
{
// Find the first N triangles that fit the proxy best.
const uint32_t faceCount = chart->faces.size();
@@ -5272,26 +4904,12 @@ private:
return true;
}
- void updatePriorities(ChartBuildData *chart)
- {
- // Re-evaluate candidate priorities.
- uint32_t candidateCount = chart->candidates.count();
- for (uint32_t i = 0; i < candidateCount; i++) {
- PriorityQueue::Pair &pair = chart->candidates.pairs[i];
- pair.priority = evaluatePriority(chart, pair.face);
- if (m_faceChartArray[pair.face] == -1)
- updateCandidate(chart, pair.face, pair.priority);
- }
- // Sort candidates.
- chart->candidates.sort();
- }
-
// Evaluate combined metric.
- float evaluatePriority(ChartBuildData *chart, uint32_t face) const
+ float evaluateCost(Chart *chart, uint32_t face) const
{
// Estimate boundary length and area:
- const float newChartArea = evaluateChartArea(chart, face);
- const float newBoundaryLength = evaluateBoundaryLength(chart, face);
+ const float newChartArea = chart->area + m_faceAreas[face];
+ const float newBoundaryLength = computeBoundaryLength(chart, face);
// Enforce limits strictly:
if (m_options.maxChartArea > 0.0f && newChartArea > m_options.maxChartArea)
return FLT_MAX;
@@ -5323,14 +4941,14 @@ private:
}
// Returns a value in [0-1].
- float evaluateProxyFitMetric(ChartBuildData *chart, uint32_t f) const
+ float evaluateProxyFitMetric(Chart *chart, uint32_t f) const
{
const Vector3 faceNormal = m_faceNormals[f];
// Use plane fitting metric for now:
return 1 - dot(faceNormal, chart->averageNormal); // @@ normal deviations should be weighted by face area
}
- float evaluateRoundnessMetric(ChartBuildData *chart, uint32_t /*face*/, float newBoundaryLength, float newChartArea) const
+ float evaluateRoundnessMetric(Chart *chart, uint32_t /*face*/, float newBoundaryLength, float newChartArea) const
{
float roundness = square(chart->boundaryLength) / chart->area;
float newRoundness = square(newBoundaryLength) / newChartArea;
@@ -5342,7 +4960,7 @@ private:
}
}
- float evaluateStraightnessMetric(ChartBuildData *chart, uint32_t f) const
+ float evaluateStraightnessMetric(Chart *chart, uint32_t f) const
{
float l_out = 0.0f;
float l_in = 0.0f;
@@ -5378,7 +4996,7 @@ private:
return m_faceNormals[meshEdgeFace(edge)] != m_faceNormals[meshEdgeFace(oppositeEdge)];
}
- float evaluateNormalSeamMetric(ChartBuildData *chart, uint32_t f) const
+ float evaluateNormalSeamMetric(Chart *chart, uint32_t f) const
{
float seamFactor = 0.0f;
float totalLength = 0.0f;
@@ -5414,7 +5032,7 @@ private:
return seamFactor / totalLength;
}
- float evaluateTextureSeamMetric(ChartBuildData *chart, uint32_t f) const
+ float evaluateTextureSeamMetric(Chart *chart, uint32_t f) const
{
float seamLength = 0.0f;
float totalLength = 0.0f;
@@ -5436,12 +5054,7 @@ private:
return seamLength / totalLength;
}
- float evaluateChartArea(ChartBuildData *chart, uint32_t f) const
- {
- return chart->area + m_faceAreas[f];
- }
-
- float evaluateBoundaryLength(ChartBuildData *chart, uint32_t f) const
+ float computeBoundaryLength(Chart *chart, uint32_t f) const
{
float boundaryLength = chart->boundaryLength;
// Add new edges, subtract edges shared with the chart.
@@ -5459,73 +5072,7 @@ private:
return max(0.0f, boundaryLength); // @@ Hack!
}
- Vector3 evaluateChartNormalSum(ChartBuildData *chart, uint32_t f) const
- {
- return chart->normalSum + m_mesh->triangleNormalAreaScaled(f);
- }
-
- // @@ Cleanup.
- struct Candidate {
- ChartBuildData *chart;
- uint32_t face;
- float metric;
- };
-
- // @@ Get N best candidates in one pass.
- const Candidate &getBestCandidate() const
- {
- uint32_t best = 0;
- float bestCandidateMetric = FLT_MAX;
- const uint32_t candidateCount = m_candidateArray.size();
- XA_ASSERT(candidateCount > 0);
- for (uint32_t i = 0; i < candidateCount; i++) {
- const Candidate &candidate = m_candidateArray[i];
- if (candidate.metric < bestCandidateMetric) {
- bestCandidateMetric = candidate.metric;
- best = i;
- }
- }
- return m_candidateArray[best];
- }
-
- void removeCandidate(uint32_t f)
- {
- int c = m_faceCandidateArray[f];
- if (c != -1) {
- m_faceCandidateArray[f] = (uint32_t)-1;
- if (c == int(m_candidateArray.size() - 1)) {
- m_candidateArray.pop_back();
- } else {
- // Replace with last.
- m_candidateArray[c] = m_candidateArray[m_candidateArray.size() - 1];
- m_candidateArray.pop_back();
- m_faceCandidateArray[m_candidateArray[c].face] = c;
- }
- }
- }
-
- void updateCandidate(ChartBuildData *chart, uint32_t f, float metric)
- {
- if (m_faceCandidateArray[f] == (uint32_t)-1) {
- const uint32_t index = m_candidateArray.size();
- m_faceCandidateArray[f] = index;
- m_candidateArray.resize(index + 1);
- m_candidateArray[index].face = f;
- m_candidateArray[index].chart = chart;
- m_candidateArray[index].metric = metric;
- } else {
- const uint32_t c = m_faceCandidateArray[f];
- XA_DEBUG_ASSERT(c != (uint32_t)-1);
- Candidate &candidate = m_candidateArray[c];
- XA_DEBUG_ASSERT(candidate.face == f);
- if (metric < candidate.metric || chart == candidate.chart) {
- candidate.metric = metric;
- candidate.chart = chart;
- }
- }
- }
-
- void mergeChart(ChartBuildData *owner, ChartBuildData *chart, float sharedBoundaryLength)
+ void mergeChart(Chart *owner, Chart *chart, float sharedBoundaryLength)
{
const uint32_t faceCount = chart->faces.size();
for (uint32_t i = 0; i < faceCount; i++) {
@@ -5538,10 +5085,10 @@ private:
owner->area += chart->area;
owner->boundaryLength += chart->boundaryLength - sharedBoundaryLength;
owner->normalSum += chart->normalSum;
- updateProxy(owner);
+ owner->averageNormal = normalizeSafe(owner->normalSum, Vector3(0), 0.0f);
// Delete chart.
m_chartArray[chart->id] = nullptr;
- chart->~ChartBuildData();
+ chart->~Chart();
XA_FREE(chart);
}
@@ -5551,18 +5098,448 @@ private:
Array<float> m_edgeLengths;
Array<float> m_faceAreas;
Array<Vector3> m_faceNormals;
+ Array<Vector3> m_faceTangents;
+ Array<Vector3> m_faceBitangents;
Array<Vector2> m_texcoords;
- Array<uint32_t> m_growFaces;
uint32_t m_facesLeft;
Array<int> m_faceChartArray;
- Array<ChartBuildData *> m_chartArray;
- Array<Candidate> m_candidateArray;
- Array<uint32_t> m_faceCandidateArray; // Map face index to candidate index.
+ Array<Chart *> m_chartArray;
PriorityQueue m_bestTriangles;
KISSRng m_rand;
ChartOptions m_options;
+ Array<Chart *> m_faceCandidateCharts;
+ Array<float> m_faceCandidateCosts;
+#if XA_GROW_CHARTS_COPLANAR
+ Array<uint32_t> m_nextPlanarRegionFace;
+#endif
+ Array<uint32_t> m_tempEdges1, m_tempEdges2;
+};
+
+} // namespace segment
+
+namespace param {
+
+class JacobiPreconditioner
+{
+public:
+ JacobiPreconditioner(const sparse::Matrix &M, bool symmetric) : m_inverseDiagonal(M.width())
+ {
+ XA_ASSERT(M.isSquare());
+ for (uint32_t x = 0; x < M.width(); x++) {
+ float elem = M.getCoefficient(x, x);
+ //XA_DEBUG_ASSERT( elem != 0.0f ); // This can be zero in the presence of zero area triangles.
+ if (symmetric) {
+ m_inverseDiagonal[x] = (elem != 0) ? 1.0f / sqrtf(fabsf(elem)) : 1.0f;
+ } else {
+ m_inverseDiagonal[x] = (elem != 0) ? 1.0f / elem : 1.0f;
+ }
+ }
+ }
+
+ void apply(const FullVector &x, FullVector &y) const
+ {
+ XA_DEBUG_ASSERT(x.dimension() == m_inverseDiagonal.dimension());
+ XA_DEBUG_ASSERT(y.dimension() == m_inverseDiagonal.dimension());
+ // @@ Wrap vector component-wise product into a separate function.
+ const uint32_t D = x.dimension();
+ for (uint32_t i = 0; i < D; i++) {
+ y[i] = m_inverseDiagonal[i] * x[i];
+ }
+ }
+
+private:
+ FullVector m_inverseDiagonal;
};
+// Linear solvers.
+class Solver
+{
+public:
+ // Solve the symmetric system: At·A·x = At·b
+ static bool LeastSquaresSolver(const sparse::Matrix &A, const FullVector &b, FullVector &x, float epsilon = 1e-5f)
+ {
+ XA_DEBUG_ASSERT(A.width() == x.dimension());
+ XA_DEBUG_ASSERT(A.height() == b.dimension());
+ XA_DEBUG_ASSERT(A.height() >= A.width()); // @@ If height == width we could solve it directly...
+ const uint32_t D = A.width();
+ sparse::Matrix At(A.height(), A.width());
+ sparse::transpose(A, At);
+ FullVector Atb(D);
+ sparse::mult(At, b, Atb);
+ sparse::Matrix AtA(D);
+ sparse::mult(At, A, AtA);
+ return SymmetricSolver(AtA, Atb, x, epsilon);
+ }
+
+ // See section 10.4.3 in: Mesh Parameterization: Theory and Practice, Siggraph Course Notes, August 2007
+ static bool LeastSquaresSolver(const sparse::Matrix &A, const FullVector &b, FullVector &x, const uint32_t *lockedParameters, uint32_t lockedCount, float epsilon = 1e-5f)
+ {
+ XA_DEBUG_ASSERT(A.width() == x.dimension());
+ XA_DEBUG_ASSERT(A.height() == b.dimension());
+ XA_DEBUG_ASSERT(A.height() >= A.width() - lockedCount);
+ // @@ This is not the most efficient way of building a system with reduced degrees of freedom. It would be faster to do it on the fly.
+ const uint32_t D = A.width() - lockedCount;
+ XA_DEBUG_ASSERT(D > 0);
+ // Compute: b - Al * xl
+ FullVector b_Alxl(b);
+ for (uint32_t y = 0; y < A.height(); y++) {
+ const uint32_t count = A.getRow(y).size();
+ for (uint32_t e = 0; e < count; e++) {
+ uint32_t column = A.getRow(y)[e].x;
+ bool isFree = true;
+ for (uint32_t i = 0; i < lockedCount; i++) {
+ isFree &= (lockedParameters[i] != column);
+ }
+ if (!isFree) {
+ b_Alxl[y] -= x[column] * A.getRow(y)[e].v;
+ }
+ }
+ }
+ // Remove locked columns from A.
+ sparse::Matrix Af(D, A.height());
+ for (uint32_t y = 0; y < A.height(); y++) {
+ const uint32_t count = A.getRow(y).size();
+ for (uint32_t e = 0; e < count; e++) {
+ uint32_t column = A.getRow(y)[e].x;
+ uint32_t ix = column;
+ bool isFree = true;
+ for (uint32_t i = 0; i < lockedCount; i++) {
+ isFree &= (lockedParameters[i] != column);
+ if (column > lockedParameters[i]) ix--; // shift columns
+ }
+ if (isFree) {
+ Af.setCoefficient(ix, y, A.getRow(y)[e].v);
+ }
+ }
+ }
+ // Remove elements from x
+ FullVector xf(D);
+ for (uint32_t i = 0, j = 0; i < A.width(); i++) {
+ bool isFree = true;
+ for (uint32_t l = 0; l < lockedCount; l++) {
+ isFree &= (lockedParameters[l] != i);
+ }
+ if (isFree) {
+ xf[j++] = x[i];
+ }
+ }
+ // Solve reduced system.
+ bool result = LeastSquaresSolver(Af, b_Alxl, xf, epsilon);
+ // Copy results back to x.
+ for (uint32_t i = 0, j = 0; i < A.width(); i++) {
+ bool isFree = true;
+ for (uint32_t l = 0; l < lockedCount; l++) {
+ isFree &= (lockedParameters[l] != i);
+ }
+ if (isFree) {
+ x[i] = xf[j++];
+ }
+ }
+ return result;
+ }
+
+private:
+ /**
+ * Compute the solution of the sparse linear system Ab=x using the Conjugate
+ * Gradient method.
+ *
+ * Solving sparse linear systems:
+ * (1) A·x = b
+ *
+ * The conjugate gradient algorithm solves (1) only in the case that A is
+ * symmetric and positive definite. It is based on the idea of minimizing the
+ * function
+ *
+ * (2) f(x) = 1/2·x·A·x - b·x
+ *
+ * This function is minimized when its gradient
+ *
+ * (3) df = A·x - b
+ *
+ * is zero, which is equivalent to (1). The minimization is carried out by
+ * generating a succession of search directions p.k and improved minimizers x.k.
+ * At each stage a quantity alfa.k is found that minimizes f(x.k + alfa.k·p.k),
+ * and x.k+1 is set equal to the new point x.k + alfa.k·p.k. The p.k and x.k are
+ * built up in such a way that x.k+1 is also the minimizer of f over the whole
+ * vector space of directions already taken, {p.1, p.2, . . . , p.k}. After N
+ * iterations you arrive at the minimizer over the entire vector space, i.e., the
+ * solution to (1).
+ *
+ * For a really good explanation of the method see:
+ *
+ * "An Introduction to the Conjugate Gradient Method Without the Agonizing Pain",
+ * Jonhathan Richard Shewchuk.
+ *
+ **/
+ // Conjugate gradient with preconditioner.
+ static bool ConjugateGradientSolver(const JacobiPreconditioner &preconditioner, const sparse::Matrix &A, const FullVector &b, FullVector &x, float epsilon)
+ {
+ XA_DEBUG_ASSERT( A.isSquare() );
+ XA_DEBUG_ASSERT( A.width() == b.dimension() );
+ XA_DEBUG_ASSERT( A.width() == x.dimension() );
+ int i = 0;
+ const int D = A.width();
+ const int i_max = 4 * D; // Convergence should be linear, but in some cases, it's not.
+ FullVector r(D); // residual
+ FullVector p(D); // search direction
+ FullVector q(D); //
+ FullVector s(D); // preconditioned
+ float delta_0;
+ float delta_old;
+ float delta_new;
+ float alpha;
+ float beta;
+ // r = b - A·x
+ sparse::copy(b, r);
+ sparse::sgemv(-1, A, x, 1, r);
+ // p = M^-1 · r
+ preconditioner.apply(r, p);
+ delta_new = sparse::dot(r, p);
+ delta_0 = delta_new;
+ while (i < i_max && delta_new > epsilon * epsilon * delta_0) {
+ i++;
+ // q = A·p
+ sparse::mult(A, p, q);
+ // alpha = delta_new / p·q
+ alpha = delta_new / sparse::dot(p, q);
+ // x = alfa·p + x
+ sparse::saxpy(alpha, p, x);
+ if ((i & 31) == 0) { // recompute r after 32 steps
+ // r = b - A·x
+ sparse::copy(b, r);
+ sparse::sgemv(-1, A, x, 1, r);
+ } else {
+ // r = r - alfa·q
+ sparse::saxpy(-alpha, q, r);
+ }
+ // s = M^-1 · r
+ preconditioner.apply(r, s);
+ delta_old = delta_new;
+ delta_new = sparse::dot( r, s );
+ beta = delta_new / delta_old;
+ // p = s + beta·p
+ sparse::scal(beta, p);
+ sparse::saxpy(1, s, p);
+ }
+ return delta_new <= epsilon * epsilon * delta_0;
+ }
+
+ static bool SymmetricSolver(const sparse::Matrix &A, const FullVector &b, FullVector &x, float epsilon = 1e-5f)
+ {
+ XA_DEBUG_ASSERT(A.height() == A.width());
+ XA_DEBUG_ASSERT(A.height() == b.dimension());
+ XA_DEBUG_ASSERT(b.dimension() == x.dimension());
+ JacobiPreconditioner jacobi(A, true);
+ return ConjugateGradientSolver(jacobi, A, b, x, epsilon);
+ }
+};
+
+// Fast sweep in 3 directions
+static bool findApproximateDiameterVertices(Mesh *mesh, uint32_t *a, uint32_t *b)
+{
+ XA_DEBUG_ASSERT(a != nullptr);
+ XA_DEBUG_ASSERT(b != nullptr);
+ const uint32_t vertexCount = mesh->vertexCount();
+ uint32_t minVertex[3];
+ uint32_t maxVertex[3];
+ minVertex[0] = minVertex[1] = minVertex[2] = UINT32_MAX;
+ maxVertex[0] = maxVertex[1] = maxVertex[2] = UINT32_MAX;
+ for (uint32_t v = 1; v < vertexCount; v++) {
+ if (mesh->isBoundaryVertex(v)) {
+ minVertex[0] = minVertex[1] = minVertex[2] = v;
+ maxVertex[0] = maxVertex[1] = maxVertex[2] = v;
+ break;
+ }
+ }
+ if (minVertex[0] == UINT32_MAX) {
+ // Input mesh has not boundaries.
+ return false;
+ }
+ for (uint32_t v = 1; v < vertexCount; v++) {
+ if (!mesh->isBoundaryVertex(v)) {
+ // Skip interior vertices.
+ continue;
+ }
+ const Vector3 &pos = mesh->position(v);
+ if (pos.x < mesh->position(minVertex[0]).x)
+ minVertex[0] = v;
+ else if (pos.x > mesh->position(maxVertex[0]).x)
+ maxVertex[0] = v;
+ if (pos.y < mesh->position(minVertex[1]).y)
+ minVertex[1] = v;
+ else if (pos.y > mesh->position(maxVertex[1]).y)
+ maxVertex[1] = v;
+ if (pos.z < mesh->position(minVertex[2]).z)
+ minVertex[2] = v;
+ else if (pos.z > mesh->position(maxVertex[2]).z)
+ maxVertex[2] = v;
+ }
+ float lengths[3];
+ for (int i = 0; i < 3; i++) {
+ lengths[i] = length(mesh->position(minVertex[i]) - mesh->position(maxVertex[i]));
+ }
+ if (lengths[0] > lengths[1] && lengths[0] > lengths[2]) {
+ *a = minVertex[0];
+ *b = maxVertex[0];
+ } else if (lengths[1] > lengths[2]) {
+ *a = minVertex[1];
+ *b = maxVertex[1];
+ } else {
+ *a = minVertex[2];
+ *b = maxVertex[2];
+ }
+ return true;
+}
+
+// Conformal relations from Brecht Van Lommel (based on ABF):
+
+static float vec_angle_cos(const Vector3 &v1, const Vector3 &v2, const Vector3 &v3)
+{
+ Vector3 d1 = v1 - v2;
+ Vector3 d2 = v3 - v2;
+ return clamp(dot(d1, d2) / (length(d1) * length(d2)), -1.0f, 1.0f);
+}
+
+static float vec_angle(const Vector3 &v1, const Vector3 &v2, const Vector3 &v3)
+{
+ float dot = vec_angle_cos(v1, v2, v3);
+ return acosf(dot);
+}
+
+static void triangle_angles(const Vector3 &v1, const Vector3 &v2, const Vector3 &v3, float *a1, float *a2, float *a3)
+{
+ *a1 = vec_angle(v3, v1, v2);
+ *a2 = vec_angle(v1, v2, v3);
+ *a3 = kPi - *a2 - *a1;
+}
+
+static void setup_abf_relations(sparse::Matrix &A, int row, int id0, int id1, int id2, const Vector3 &p0, const Vector3 &p1, const Vector3 &p2)
+{
+ // @@ IC: Wouldn't it be more accurate to return cos and compute 1-cos^2?
+ // It does indeed seem to be a little bit more robust.
+ // @@ Need to revisit this more carefully!
+ float a0, a1, a2;
+ triangle_angles(p0, p1, p2, &a0, &a1, &a2);
+ float s0 = sinf(a0);
+ float s1 = sinf(a1);
+ float s2 = sinf(a2);
+ if (s1 > s0 && s1 > s2) {
+ swap(s1, s2);
+ swap(s0, s1);
+ swap(a1, a2);
+ swap(a0, a1);
+ swap(id1, id2);
+ swap(id0, id1);
+ } else if (s0 > s1 && s0 > s2) {
+ swap(s0, s2);
+ swap(s0, s1);
+ swap(a0, a2);
+ swap(a0, a1);
+ swap(id0, id2);
+ swap(id0, id1);
+ }
+ float c0 = cosf(a0);
+ float ratio = (s2 == 0.0f) ? 1.0f : s1 / s2;
+ float cosine = c0 * ratio;
+ float sine = s0 * ratio;
+ // Note : 2*id + 0 --> u
+ // 2*id + 1 --> v
+ int u0_id = 2 * id0 + 0;
+ int v0_id = 2 * id0 + 1;
+ int u1_id = 2 * id1 + 0;
+ int v1_id = 2 * id1 + 1;
+ int u2_id = 2 * id2 + 0;
+ int v2_id = 2 * id2 + 1;
+ // Real part
+ A.setCoefficient(u0_id, 2 * row + 0, cosine - 1.0f);
+ A.setCoefficient(v0_id, 2 * row + 0, -sine);
+ A.setCoefficient(u1_id, 2 * row + 0, -cosine);
+ A.setCoefficient(v1_id, 2 * row + 0, sine);
+ A.setCoefficient(u2_id, 2 * row + 0, 1);
+ // Imaginary part
+ A.setCoefficient(u0_id, 2 * row + 1, sine);
+ A.setCoefficient(v0_id, 2 * row + 1, cosine - 1.0f);
+ A.setCoefficient(u1_id, 2 * row + 1, -sine);
+ A.setCoefficient(v1_id, 2 * row + 1, -cosine);
+ A.setCoefficient(v2_id, 2 * row + 1, 1);
+}
+
+static bool computeLeastSquaresConformalMap(Mesh *mesh)
+{
+ // For this to work properly, mesh should not have colocals that have the same
+ // attributes, unless you want the vertices to actually have different texcoords.
+ const uint32_t vertexCount = mesh->vertexCount();
+ const uint32_t D = 2 * vertexCount;
+ const uint32_t N = 2 * mesh->faceCount();
+ // N is the number of equations (one per triangle)
+ // D is the number of variables (one per vertex; there are 2 pinned vertices).
+ if (N < D - 4) {
+ return false;
+ }
+ sparse::Matrix A(D, N);
+ FullVector b(N);
+ FullVector x(D);
+ // Fill b:
+ b.fill(0.0f);
+ // Fill x:
+ uint32_t v0, v1;
+ if (!findApproximateDiameterVertices(mesh, &v0, &v1)) {
+ // Mesh has no boundaries.
+ return false;
+ }
+ if (mesh->texcoord(v0) == mesh->texcoord(v1)) {
+ // LSCM expects an existing parameterization.
+ return false;
+ }
+ for (uint32_t v = 0; v < vertexCount; v++) {
+ // Initial solution.
+ x[2 * v + 0] = mesh->texcoord(v).x;
+ x[2 * v + 1] = mesh->texcoord(v).y;
+ }
+ // Fill A:
+ const uint32_t faceCount = mesh->faceCount();
+ for (uint32_t f = 0, t = 0; f < faceCount; f++) {
+ const uint32_t vertex0 = mesh->vertexAt(f * 3 + 0);
+ const uint32_t vertex1 = mesh->vertexAt(f * 3 + 1);
+ const uint32_t vertex2 = mesh->vertexAt(f * 3 + 2);
+ setup_abf_relations(A, t, vertex0, vertex1, vertex2, mesh->position(vertex0), mesh->position(vertex1), mesh->position(vertex2));
+ t++;
+ }
+ const uint32_t lockedParameters[] = {
+ 2 * v0 + 0,
+ 2 * v0 + 1,
+ 2 * v1 + 0,
+ 2 * v1 + 1
+ };
+ // Solve
+ Solver::LeastSquaresSolver(A, b, x, lockedParameters, 4, 0.000001f);
+ // Map x back to texcoords:
+ for (uint32_t v = 0; v < vertexCount; v++)
+ mesh->texcoord(v) = Vector2(x[2 * v + 0], x[2 * v + 1]);
+ return true;
+}
+
+static bool computeOrthogonalProjectionMap(Mesh *mesh)
+{
+ uint32_t vertexCount = mesh->vertexCount();
+ // Avoid redundant computations.
+ float matrix[6];
+ Fit::computeCovariance(vertexCount, &mesh->position(0), matrix);
+ if (matrix[0] == 0 && matrix[3] == 0 && matrix[5] == 0)
+ return false;
+ float eigenValues[3];
+ Vector3 eigenVectors[3];
+ if (!Fit::eigenSolveSymmetric3(matrix, eigenValues, eigenVectors))
+ return false;
+ Vector3 axis[2];
+ axis[0] = normalize(eigenVectors[0], kEpsilon);
+ axis[1] = normalize(eigenVectors[1], kEpsilon);
+ // Project vertices to plane.
+ for (uint32_t i = 0; i < vertexCount; i++)
+ mesh->texcoord(i) = Vector2(dot(axis[0], mesh->position(i)), dot(axis[1], mesh->position(i)));
+ return true;
+}
+
// Estimate quality of existing parameterization.
struct ParameterizationQuality
{
@@ -5578,15 +5555,15 @@ struct ParameterizationQuality
bool boundaryIntersection = false;
};
-static ParameterizationQuality calculateParameterizationQuality(const Mesh *mesh, Array<uint32_t> *flippedFaces)
+static ParameterizationQuality calculateParameterizationQuality(const Mesh *mesh, uint32_t faceCount, Array<uint32_t> *flippedFaces)
{
XA_DEBUG_ASSERT(mesh != nullptr);
ParameterizationQuality quality;
- const uint32_t faceCount = mesh->faceCount();
uint32_t firstBoundaryEdge = UINT32_MAX;
for (uint32_t e = 0; e < mesh->edgeCount(); e++) {
if (mesh->isBoundaryEdge(e)) {
firstBoundaryEdge = e;
+ break;
}
}
XA_DEBUG_ASSERT(firstBoundaryEdge != UINT32_MAX);
@@ -5681,7 +5658,8 @@ static ParameterizationQuality calculateParameterizationQuality(const Mesh *mesh
// If more than half the triangles are flipped, reverse the flipped / not flipped classification.
quality.flippedTriangleCount = quality.totalTriangleCount - quality.flippedTriangleCount;
if (flippedFaces) {
- Array<uint32_t> temp(*flippedFaces);
+ Array<uint32_t> temp;
+ flippedFaces->copyTo(temp);
flippedFaces->clear();
for (uint32_t f = 0; f < faceCount; f++) {
bool match = false;
@@ -5732,28 +5710,36 @@ struct ChartWarningFlags
class Chart
{
public:
- Chart(const Mesh *originalMesh, const Array<uint32_t> &faceArray, const Basis &basis, uint32_t meshId, uint32_t chartGroupId, uint32_t chartId) : m_basis(basis), m_mesh(nullptr), m_unifiedMesh(nullptr), m_isDisk(false), m_isOrtho(false), m_isPlanar(false), m_warningFlags(0), m_closedHolesCount(0), m_fixedTJunctionsCount(0), m_faceArray(faceArray)
+ Chart(const segment::Atlas *atlas, const Mesh *originalMesh, uint32_t chartIndex, uint32_t meshId, uint32_t chartGroupId, uint32_t chartId) : m_mesh(nullptr), m_unifiedMesh(nullptr), m_isDisk(false), m_isOrtho(false), m_isPlanar(false), m_warningFlags(0), m_closedHolesCount(0), m_fixedTJunctionsCount(0)
{
XA_UNUSED(meshId);
XA_UNUSED(chartGroupId);
XA_UNUSED(chartId);
+ m_basis = atlas->chartBasis(chartIndex);
+ atlas->chartFaces(chartIndex).copyTo(m_faceArray);
// Copy face indices.
- m_mesh = XA_NEW(MemTag::Mesh, Mesh, originalMesh->epsilon(), faceArray.size() * 3, faceArray.size());
- m_unifiedMesh = XA_NEW(MemTag::Mesh, Mesh, originalMesh->epsilon(), faceArray.size() * 3, faceArray.size());
+ m_mesh = XA_NEW_ARGS(MemTag::Mesh, Mesh, originalMesh->epsilon(), m_faceArray.size() * 3, m_faceArray.size());
+ m_unifiedMesh = XA_NEW_ARGS(MemTag::Mesh, Mesh, originalMesh->epsilon(), m_faceArray.size() * 3, m_faceArray.size());
Array<uint32_t> chartMeshIndices;
- chartMeshIndices.resize(originalMesh->vertexCount(), (uint32_t)~0);
+ chartMeshIndices.resize(originalMesh->vertexCount());
+ chartMeshIndices.setAll(UINT32_MAX);
Array<uint32_t> unifiedMeshIndices;
- unifiedMeshIndices.resize(originalMesh->vertexCount(), (uint32_t)~0);
+ unifiedMeshIndices.resize(originalMesh->vertexCount());
+ unifiedMeshIndices.setAll(UINT32_MAX);
// Add vertices.
- const uint32_t faceCount = faceArray.size();
+ const uint32_t faceCount = m_initialFaceCount = m_faceArray.size();
for (uint32_t f = 0; f < faceCount; f++) {
for (uint32_t i = 0; i < 3; i++) {
- const uint32_t vertex = originalMesh->vertexAt(faceArray[f] * 3 + i);
+ const uint32_t vertex = originalMesh->vertexAt(m_faceArray[f] * 3 + i);
const uint32_t unifiedVertex = originalMesh->firstColocal(vertex);
if (unifiedMeshIndices[unifiedVertex] == (uint32_t)~0) {
unifiedMeshIndices[unifiedVertex] = m_unifiedMesh->vertexCount();
XA_DEBUG_ASSERT(equal(originalMesh->position(vertex), originalMesh->position(unifiedVertex), originalMesh->epsilon()));
+#if XA_SKIP_PARAMETERIZATION
+ m_unifiedMesh->addVertex(originalMesh->position(vertex), Vector3(0.0f), atlas->faceTexcoords(m_faceArray[f])[i]);
+#else
m_unifiedMesh->addVertex(originalMesh->position(vertex));
+#endif
}
if (chartMeshIndices[vertex] == (uint32_t)~0) {
chartMeshIndices[vertex] = m_mesh->vertexCount();
@@ -5767,7 +5753,7 @@ public:
for (uint32_t f = 0; f < faceCount; f++) {
uint32_t indices[3], unifiedIndices[3];
for (uint32_t i = 0; i < 3; i++) {
- const uint32_t vertex = originalMesh->vertexAt(faceArray[f] * 3 + i);
+ const uint32_t vertex = originalMesh->vertexAt(m_faceArray[f] * 3 + i);
indices[i] = chartMeshIndices[vertex];
unifiedIndices[i] = unifiedMeshIndices[originalMesh->firstColocal(vertex)];
}
@@ -5810,6 +5796,7 @@ public:
m_unifiedMesh = fixedUnifiedMesh;
m_unifiedMesh->createBoundaries();
m_unifiedMesh->linkBoundaries();
+ m_initialFaceCount = m_unifiedMesh->faceCount(); // Fixing t-junctions rewrites faces.
}
// See if there are any holes that need closing.
Array<uint32_t> boundaryLoops;
@@ -5825,7 +5812,7 @@ public:
// - Use minimal spanning trees or seamster.
Array<uint32_t> holeFaceCounts;
XA_PROFILE_START(closeChartMeshHoles)
- failed = !meshCloseHoles(m_unifiedMesh, boundaryLoops, basis.normal, holeFaceCounts);
+ failed = !meshCloseHoles(m_unifiedMesh, boundaryLoops, m_basis.normal, holeFaceCounts);
XA_PROFILE_END(closeChartMeshHoles)
m_unifiedMesh->createBoundaries();
m_unifiedMesh->linkBoundaries();
@@ -5907,7 +5894,7 @@ public:
void evaluateOrthoParameterizationQuality()
{
XA_PROFILE_START(parameterizeChartsEvaluateQuality)
- m_paramQuality = calculateParameterizationQuality(m_unifiedMesh, nullptr);
+ m_paramQuality = calculateParameterizationQuality(m_unifiedMesh, m_initialFaceCount, nullptr);
XA_PROFILE_END(parameterizeChartsEvaluateQuality)
// Use orthogonal parameterization if quality is acceptable.
if (!m_paramQuality.boundaryIntersection && m_paramQuality.geometricArea > 0.0f && m_paramQuality.stretchMetric <= 1.1f && m_paramQuality.maxStretchMetric <= 1.25f)
@@ -5918,9 +5905,9 @@ public:
{
XA_PROFILE_START(parameterizeChartsEvaluateQuality)
#if XA_DEBUG_EXPORT_OBJ_INVALID_PARAMETERIZATION
- m_paramQuality = calculateParameterizationQuality(m_unifiedMesh, &m_paramFlippedFaces);
+ m_paramQuality = calculateParameterizationQuality(m_unifiedMesh, m_initialFaceCount, &m_paramFlippedFaces);
#else
- m_paramQuality = calculateParameterizationQuality(m_unifiedMesh, nullptr);
+ m_paramQuality = calculateParameterizationQuality(m_unifiedMesh, m_initialFaceCount, nullptr);
#endif
XA_PROFILE_END(parameterizeChartsEvaluateQuality)
}
@@ -5961,6 +5948,7 @@ private:
Mesh *m_unifiedMesh;
bool m_isDisk, m_isOrtho, m_isPlanar;
uint32_t m_warningFlags;
+ uint32_t m_initialFaceCount; // Before fixing T-junctions and/or closing holes.
uint32_t m_closedHolesCount, m_fixedTJunctionsCount;
// List of faces of the original mesh that belong to this chart.
@@ -5979,9 +5967,9 @@ private:
struct CreateChartTaskArgs
{
+ const segment::Atlas *atlas;
const Mesh *mesh;
- const Array<uint32_t> *faceArray;
- const Basis *basis;
+ uint32_t chartIndex; // In the atlas.
uint32_t meshId;
uint32_t chartGroupId;
uint32_t chartId;
@@ -5992,7 +5980,7 @@ static void runCreateChartTask(void *userData)
{
XA_PROFILE_START(createChartMeshesThread)
auto args = (CreateChartTaskArgs *)userData;
- *(args->chart) = XA_NEW(MemTag::Default, Chart, args->mesh, *(args->faceArray), *(args->basis), args->meshId, args->chartGroupId, args->chartId);
+ *(args->chart) = XA_NEW_ARGS(MemTag::Default, Chart, args->atlas, args->mesh, args->chartIndex, args->meshId, args->chartGroupId, args->chartId);
XA_PROFILE_END(createChartMeshesThread)
}
@@ -6043,10 +6031,11 @@ public:
}
// Only initial meshes have face groups and ignored faces. The only flag we care about is HasNormals.
const uint32_t faceCount = m_faceToSourceFaceMap.size();
- m_mesh = XA_NEW(MemTag::Mesh, Mesh, sourceMesh->epsilon(), faceCount * 3, faceCount, sourceMesh->flags() & MeshFlags::HasNormals);
+ m_mesh = XA_NEW_ARGS(MemTag::Mesh, Mesh, sourceMesh->epsilon(), faceCount * 3, faceCount, sourceMesh->flags() & MeshFlags::HasNormals);
XA_DEBUG_ASSERT(faceCount > 0);
Array<uint32_t> meshIndices;
- meshIndices.resize(sourceMesh->vertexCount(), (uint32_t)~0);
+ meshIndices.resize(sourceMesh->vertexCount());
+ meshIndices.setAll((uint32_t)~0);
for (uint32_t f = 0; f < faceCount; f++) {
const uint32_t face = m_faceToSourceFaceMap[f];
for (uint32_t i = 0; i < 3; i++) {
@@ -6183,22 +6172,22 @@ public:
chartFaces.resize(m_mesh->faceCount());
for (uint32_t i = 0; i < chartFaces.size(); i++)
chartFaces[i] = i;
- Chart *chart = XA_NEW(MemTag::Default, Chart, m_mesh, chartFaces, m_sourceId, m_id, 0);
+ Chart *chart = XA_NEW_ARGS(MemTag::Default, Chart, m_mesh, chartFaces, m_sourceId, m_id, 0);
m_chartArray.push_back(chart);
#else
- XA_PROFILE_START(atlasBuilder)
- AtlasBuilder builder(m_mesh, nullptr, options);
- runAtlasBuilder(builder, options);
- XA_PROFILE_END(atlasBuilder)
- const uint32_t chartCount = builder.chartCount();
+ XA_PROFILE_START(buildAtlas)
+ segment::Atlas atlas(m_mesh, nullptr, options);
+ buildAtlas(atlas, options);
+ XA_PROFILE_END(buildAtlas)
+ const uint32_t chartCount = atlas.chartCount();
m_chartArray.resize(chartCount);
Array<CreateChartTaskArgs> taskArgs;
taskArgs.resize(chartCount);
for (uint32_t i = 0; i < chartCount; i++) {
CreateChartTaskArgs &args = taskArgs[i];
+ args.atlas = &atlas;
args.mesh = m_mesh;
- args.faceArray = &builder.chartFaces(i);
- args.basis = &builder.chartBasis(i);
+ args.chartIndex = i;
args.meshId = m_sourceId;
args.chartGroupId = m_id;
args.chartId = i;
@@ -6239,6 +6228,16 @@ public:
void parameterizeCharts(TaskScheduler *taskScheduler, ParameterizeFunc func)
{
const uint32_t chartCount = m_chartArray.size();
+#if XA_SKIP_PARAMETERIZATION
+ XA_UNUSED(taskScheduler);
+ XA_UNUSED(func);
+ for (uint32_t i = 0; i < chartCount; i++) {
+ Chart *chart = m_chartArray[i];
+ chart->evaluateOrthoParameterizationQuality();
+ chart->evaluateParameterizationQuality();
+ chart->transferParameterization();
+ }
+#else
Array<ParameterizeChartTaskArgs> taskArgs;
taskArgs.resize(chartCount);
TaskGroupHandle taskGroup = taskScheduler->createTaskGroup(chartCount);
@@ -6279,10 +6278,10 @@ public:
options.maxChartArea = invalidChartArea * 0.2f;
options.maxThreshold = 0.25f;
options.maxIterations = 3;
- AtlasBuilder builder(m_mesh, &meshFaces, options);
- runAtlasBuilder(builder, options);
- for (uint32_t j = 0; j < builder.chartCount(); j++) {
- Chart *chart = XA_NEW(MemTag::Default, Chart, m_mesh, builder.chartFaces(j), builder.chartBasis(j), m_sourceId, m_id, m_chartArray.size());
+ segment::Atlas atlas(m_mesh, &meshFaces, options);
+ buildAtlas(atlas, options);
+ for (uint32_t j = 0; j < atlas.chartCount(); j++) {
+ Chart *chart = XA_NEW_ARGS(MemTag::Default, Chart, &atlas, m_mesh, j, m_sourceId, m_id, m_chartArray.size());
m_chartArray.push_back(chart);
m_paramAddedChartsCount++;
}
@@ -6325,46 +6324,41 @@ public:
XA_FREE(chart);
m_paramDeletedChartsCount++;
}
-#endif
+#endif // XA_RECOMPUTE_CHARTS
+#endif // XA_SKIP_PARAMETERIZATION
}
private:
- void runAtlasBuilder(AtlasBuilder &builder, const ChartOptions &options)
+ void buildAtlas(segment::Atlas &atlas, const ChartOptions &options)
{
- if (builder.facesLeft() == 0)
+ if (atlas.facesLeft() == 0)
return;
- // This seems a reasonable estimate.
- XA_PROFILE_START(atlasBuilderCreateInitialCharts)
// Create initial charts greedely.
- builder.placeSeeds(options.maxThreshold * 0.5f);
+ atlas.placeSeeds(options.maxThreshold * 0.5f);
if (options.maxIterations == 0) {
- XA_DEBUG_ASSERT(builder.facesLeft() == 0);
- XA_PROFILE_END(atlasBuilderCreateInitialCharts)
+ XA_DEBUG_ASSERT(atlas.facesLeft() == 0);
return;
}
- builder.updateProxies();
- builder.relocateSeeds();
- builder.resetCharts();
- XA_PROFILE_END(atlasBuilderCreateInitialCharts)
+ atlas.relocateSeeds();
+ atlas.resetCharts();
// Restart process growing charts in parallel.
uint32_t iteration = 0;
while (true) {
- if (!builder.growCharts(options.maxThreshold, options.growFaceCount)) {
+ if (!atlas.growCharts(options.maxThreshold)) {
// If charts cannot grow more: fill holes, merge charts, relocate seeds and start new iteration.
- builder.fillHoles(options.maxThreshold * 0.5f);
- builder.updateProxies();
+ atlas.fillHoles(options.maxThreshold * 0.5f);
#if XA_MERGE_CHARTS
- builder.mergeCharts();
+ atlas.mergeCharts();
#endif
if (++iteration == options.maxIterations)
break;
- if (!builder.relocateSeeds())
+ if (!atlas.relocateSeeds())
break;
- builder.resetCharts();
+ atlas.resetCharts();
}
}
// Make sure no holes are left!
- XA_DEBUG_ASSERT(builder.facesLeft() == 0);
+ XA_DEBUG_ASSERT(atlas.facesLeft() == 0);
}
void removeChart(const Chart *chart)
@@ -6400,7 +6394,7 @@ static void runCreateChartGroupTask(void *userData)
{
XA_PROFILE_START(addMeshCreateChartGroupsThread)
auto args = (CreateChartGroupTaskArgs *)userData;
- *(args->chartGroup) = XA_NEW(MemTag::Default, ChartGroup, args->groupId, args->mesh, args->faceGroup);
+ *(args->chartGroup) = XA_NEW_ARGS(MemTag::Default, ChartGroup, args->groupId, args->mesh, args->faceGroup);
XA_PROFILE_END(addMeshCreateChartGroupsThread)
}
@@ -6448,7 +6442,7 @@ static void runParameterizeChartsJob(void *userData)
class Atlas
{
public:
- Atlas() : m_chartsComputed(false), m_chartsParameterized(false) {}
+ Atlas() : m_meshCount(0), m_chartsComputed(false), m_chartsParameterized(false) {}
~Atlas()
{
@@ -6460,6 +6454,8 @@ public:
bool chartsComputed() const { return m_chartsComputed; }
bool chartsParameterized() const { return m_chartsParameterized; }
+ uint32_t chartGroupCount() const { return m_chartGroups.size(); }
+ const ChartGroup *chartGroupAt(uint32_t index) const { return m_chartGroups[index]; }
uint32_t chartGroupCount(uint32_t mesh) const
{
@@ -6483,26 +6479,6 @@ public:
return nullptr;
}
- uint32_t chartCount() const
- {
- uint32_t count = 0;
- for (uint32_t i = 0; i < m_chartGroups.size(); i++)
- count += m_chartGroups[i]->chartCount();
- return count;
- }
-
- Chart *chartAt(uint32_t i)
- {
- for (uint32_t c = 0; c < m_chartGroups.size(); c++) {
- uint32_t count = m_chartGroups[c]->chartCount();
- if (i < count) {
- return m_chartGroups[c]->chartAt(i);
- }
- i -= count;
- }
- return nullptr;
- }
-
// This function is thread safe.
void addMesh(TaskScheduler *taskScheduler, const Mesh *mesh)
{
@@ -6548,9 +6524,32 @@ public:
m_chartGroups.push_back(chartGroups[g]);
m_chartGroupSourceMeshes.push_back(mesh->id());
}
+ m_meshCount++;
m_addMeshMutex.unlock();
}
+ // Chart id/index is determined by depth-first hierarchy of mesh -> chart group -> chart.
+ // For chart index to be consistent here, chart groups needs to sorted by mesh index. Since addMesh is called by multithreaded tasks, order is indeterminate, so chart groups need to be explicitly sorted after all meshes are added.
+ void sortChartGroups()
+ {
+ Array<ChartGroup *> oldChartGroups;
+ oldChartGroups.resize(m_chartGroups.size());
+ memcpy(oldChartGroups.data(), m_chartGroups.data(), sizeof(ChartGroup *) * m_chartGroups.size());
+ Array<uint32_t> oldChartGroupSourceMeshes;
+ oldChartGroupSourceMeshes.resize(m_chartGroupSourceMeshes.size());
+ memcpy(oldChartGroupSourceMeshes.data(), m_chartGroupSourceMeshes.data(), sizeof(uint32_t) * m_chartGroupSourceMeshes.size());
+ uint32_t current = 0;
+ for (uint32_t i = 0; i < m_meshCount; i++) {
+ for (uint32_t j = 0; j < oldChartGroups.size(); j++) {
+ if (oldChartGroupSourceMeshes[j] == i) {
+ m_chartGroups[current] = oldChartGroups[j];
+ m_chartGroupSourceMeshes[current] = oldChartGroupSourceMeshes[j];
+ current++;
+ }
+ }
+ }
+ }
+
bool computeCharts(TaskScheduler *taskScheduler, const ChartOptions &options, ProgressFunc progressFunc, void *progressUserData)
{
m_chartsComputed = false;
@@ -6629,37 +6628,18 @@ public:
taskScheduler->wait(&taskGroup);
if (progress.cancel)
return false;
- // Save original texcoords so PackCharts can be called multiple times (packing overwrites the texcoords).
- const uint32_t nCharts = chartCount();
- m_originalChartTexcoords.resize(nCharts);
- for (uint32_t i = 0; i < nCharts; i++) {
- const Mesh *mesh = chartAt(i)->mesh();
- m_originalChartTexcoords[i].resize(mesh->vertexCount());
- for (uint32_t j = 0; j < mesh->vertexCount(); j++)
- m_originalChartTexcoords[i][j] = mesh->texcoord(j);
- }
m_chartsParameterized = true;
return true;
}
- void restoreOriginalChartTexcoords()
- {
- const uint32_t nCharts = chartCount();
- for (uint32_t i = 0; i < nCharts; i++) {
- Mesh *mesh = chartAt(i)->mesh();
- for (uint32_t j = 0; j < mesh->vertexCount(); j++)
- mesh->texcoord(j) = m_originalChartTexcoords[i][j];
- }
- }
-
private:
std::mutex m_addMeshMutex;
+ uint32_t m_meshCount;
bool m_chartsComputed;
bool m_chartsParameterized;
Array<ChartGroup *> m_chartGroups;
RadixSort m_chartGroupsRadix; // By mesh indexCount.
Array<uint32_t> m_chartGroupSourceMeshes;
- Array<Array<Vector2> > m_originalChartTexcoords;
};
} // namespace param
@@ -6733,10 +6713,10 @@ public:
memcpy(&data[y * width], &m_data[y * m_width], min(m_width, width) * sizeof(uint32_t));
m_width = width;
m_height = height;
- swap(m_data, data);
+ data.moveTo(m_data);
}
- void addChart(uint32_t chartIndex, const BitImage *image, bool imageHasPadding, int atlas_w, int atlas_h, int offset_x, int offset_y)
+ void addChart(uint32_t chartIndex, const BitImage *image, const BitImage *imageBilinear, const BitImage *imagePadding, int atlas_w, int atlas_h, int offset_x, int offset_y)
{
const int w = image->width();
const int h = image->height();
@@ -6746,23 +6726,27 @@ public:
continue;
for (int x = 0; x < w; x++) {
const int xx = x + offset_x;
- if (xx >= 0 && xx < atlas_w && yy < atlas_h && image->bitAt(x, y)) {
+ if (xx >= 0 && xx < atlas_w && yy < atlas_h) {
const uint32_t dataOffset = xx + yy * m_width;
- if (m_data[dataOffset] != 0)
- continue;
- uint32_t value = chartIndex | kImageHasChartIndexBit;
- if (imageHasPadding)
- value |= kImageIsPaddingBit;
- m_data[dataOffset] = value;
+ if (image->bitAt(x, y)) {
+ XA_DEBUG_ASSERT(m_data[dataOffset] == 0);
+ m_data[dataOffset] = chartIndex | kImageHasChartIndexBit;
+ } else if (imageBilinear && imageBilinear->bitAt(x, y)) {
+ XA_DEBUG_ASSERT(m_data[dataOffset] == 0);
+ m_data[dataOffset] = chartIndex | kImageHasChartIndexBit | kImageIsBilinearBit;
+ } else if (imagePadding && imagePadding->bitAt(x, y)) {
+ XA_DEBUG_ASSERT(m_data[dataOffset] == 0);
+ m_data[dataOffset] = chartIndex | kImageHasChartIndexBit | kImageIsPaddingBit;
+ }
}
}
}
}
- void copyTo(uint32_t *dest, uint32_t destWidth, uint32_t destHeight) const
+ void copyTo(uint32_t *dest, uint32_t destWidth, uint32_t destHeight, int padding) const
{
for (uint32_t y = 0; y < destHeight; y++)
- memcpy(&dest[y * destWidth], &m_data[y * m_width], destWidth * sizeof(uint32_t));
+ memcpy(&dest[y * destWidth], &m_data[padding + (y + padding) * m_width], destWidth * sizeof(uint32_t));
}
#if XA_DEBUG_EXPORT_ATLAS_IMAGES
@@ -6777,20 +6761,26 @@ public:
if (x >= m_width)
continue;
const uint32_t data = m_data[x + y * m_width];
- if (!(data & kImageHasChartIndexBit))
+ uint8_t *bgr = &image[(x + y * width) * 3];
+ if (data == 0) {
+ bgr[0] = bgr[1] = bgr[2] = 0;
continue;
+ }
const uint32_t chartIndex = data & kImageChartIndexMask;
- uint8_t *color = &image[(x + y * width) * 3];
if (data & kImageIsPaddingBit) {
- color[0] = 255;
- color[1] = 0;
- color[2] = 255;
+ bgr[0] = 0;
+ bgr[1] = 0;
+ bgr[2] = 255;
+ } else if (data & kImageIsBilinearBit) {
+ bgr[0] = 0;
+ bgr[1] = 255;
+ bgr[2] = 0;
} else {
const int mix = 192;
srand((unsigned int)chartIndex);
- color[0] = uint8_t((rand() % 255 + mix) * 0.5f);
- color[1] = uint8_t((rand() % 255 + mix) * 0.5f);
- color[2] = uint8_t((rand() % 255 + mix) * 0.5f);
+ bgr[0] = uint8_t((rand() % 255 + mix) * 0.5f);
+ bgr[1] = uint8_t((rand() % 255 + mix) * 0.5f);
+ bgr[2] = uint8_t((rand() % 255 + mix) * 0.5f);
}
}
}
@@ -6817,11 +6807,61 @@ struct Chart
bool allowRotate;
// bounding box
Vector2 majorAxis, minorAxis, minCorner, maxCorner;
+ // UvMeshChart only
+ Array<uint32_t> faces;
Vector2 &uniqueVertexAt(uint32_t v) { return uniqueVertices.isEmpty() ? vertices[v] : vertices[uniqueVertices[v]]; }
uint32_t uniqueVertexCount() const { return uniqueVertices.isEmpty() ? vertexCount : uniqueVertices.size(); }
};
+struct AddChartTaskArgs
+{
+ param::Chart *paramChart;
+ Chart *chart; // out
+};
+
+static void runAddChartTask(void *userData)
+{
+ XA_PROFILE_START(packChartsAddChartsThread)
+ auto args = (AddChartTaskArgs *)userData;
+ param::Chart *paramChart = args->paramChart;
+ XA_PROFILE_START(packChartsAddChartsRestoreTexcoords)
+ paramChart->transferParameterization();
+ XA_PROFILE_END(packChartsAddChartsRestoreTexcoords)
+ Mesh *mesh = paramChart->mesh();
+ Chart *chart = args->chart = XA_NEW(MemTag::Default, Chart);
+ chart->atlasIndex = -1;
+ chart->material = 0;
+ chart->indexCount = mesh->indexCount();
+ chart->indices = mesh->indices();
+ chart->parametricArea = paramChart->computeParametricArea();
+ if (chart->parametricArea < kAreaEpsilon) {
+ // When the parametric area is too small we use a rough approximation to prevent divisions by very small numbers.
+ const Vector2 bounds = paramChart->computeParametricBounds();
+ chart->parametricArea = bounds.x * bounds.y;
+ }
+ chart->surfaceArea = paramChart->computeSurfaceArea();
+ chart->vertices = mesh->texcoords();
+ chart->vertexCount = mesh->vertexCount();
+ chart->allowRotate = true;
+ // Compute list of boundary vertices.
+ Array<Vector2> boundary;
+ boundary.reserve(16);
+ for (uint32_t v = 0; v < chart->vertexCount; v++) {
+ if (mesh->isBoundaryVertex(v))
+ boundary.push_back(mesh->texcoord(v));
+ }
+ XA_DEBUG_ASSERT(boundary.size() > 0);
+ // Compute bounding box of chart.
+ static thread_local BoundingBox2D boundingBox;
+ boundingBox.compute(boundary.data(), boundary.size(), mesh->texcoords(), mesh->vertexCount());
+ chart->majorAxis = boundingBox.majorAxis();
+ chart->minorAxis = boundingBox.minorAxis();
+ chart->minCorner = boundingBox.minCorner();
+ chart->maxCorner = boundingBox.maxCorner();
+ XA_PROFILE_END(packChartsAddChartsThread)
+}
+
struct FindChartLocationBruteForceTaskArgs
{
std::atomic<bool> *finished; // One of the tasks found a location that doesn't expand the atlas.
@@ -6830,7 +6870,8 @@ struct FindChartLocationBruteForceTaskArgs
const BitImage *chartBitImage;
const BitImage *chartBitImageRotated;
int w, h;
- bool blockAligned, resizableAtlas, allowRotate;
+ bool blockAligned, allowRotate;
+ uint32_t maxResolution;
// out
bool best_insideAtlas;
int best_metric, best_x, best_y, best_w, best_h, best_r;
@@ -6845,6 +6886,8 @@ static void runFindChartLocationBruteForceTask(void *userData)
return;
// Try two different orientations.
for (int r = 0; r < 2; r++) {
+ if (args->finished->load())
+ break;
int cw = args->chartBitImage->width();
int ch = args->chartBitImage->height();
if (r == 1) {
@@ -6855,8 +6898,8 @@ static void runFindChartLocationBruteForceTask(void *userData)
}
const int y = args->startPosition.y;
const int stepSize = args->blockAligned ? 4 : 1;
- for (int x = args->startPosition.x; x <= args->w + stepSize; x += stepSize) { // + 1 not really necessary here.
- if (!args->resizableAtlas && (x > (int)args->atlasBitImage->width() - cw || y > (int)args->atlasBitImage->height() - ch))
+ for (int x = args->startPosition.x; x <= args->w + stepSize; x += stepSize) {
+ if (args->maxResolution > 0 && (x > (int)args->maxResolution - cw || y > (int)args->maxResolution - ch))
continue;
if (args->finished->load())
break;
@@ -6891,6 +6934,10 @@ struct Atlas
{
~Atlas()
{
+ for (uint32_t i = 0; i < m_atlasImages.size(); i++) {
+ m_atlasImages[i]->~AtlasImage();
+ XA_FREE(m_atlasImages[i]);
+ }
for (uint32_t i = 0; i < m_bitImages.size(); i++) {
m_bitImages[i]->~BitImage();
XA_FREE(m_bitImages[i]);
@@ -6910,39 +6957,44 @@ struct Atlas
const Array<AtlasImage *> &getImages() const { return m_atlasImages; }
float getUtilization(uint32_t atlas) const { return m_utilization[atlas]; }
- void addChart(param::Chart *paramChart)
+ void addCharts(TaskScheduler *taskScheduler, param::Atlas *paramAtlas)
{
- Mesh *mesh = paramChart->mesh();
- Chart *chart = XA_NEW(MemTag::Default, Chart);
- chart->atlasIndex = -1;
- chart->material = 0;
- chart->indexCount = mesh->indexCount();
- chart->indices = mesh->indices();
- chart->parametricArea = paramChart->computeParametricArea();
- if (chart->parametricArea < kAreaEpsilon) {
- // When the parametric area is too small we use a rough approximation to prevent divisions by very small numbers.
- const Vector2 bounds = paramChart->computeParametricBounds();
- chart->parametricArea = bounds.x * bounds.y;
- }
- chart->surfaceArea = paramChart->computeSurfaceArea();
- chart->vertices = mesh->texcoords();
- chart->vertexCount = mesh->vertexCount();
- chart->allowRotate = true;
- // Compute list of boundary vertices.
- Array<Vector2> boundary;
- boundary.reserve(16);
- for (uint32_t v = 0; v < chart->vertexCount; v++) {
- if (mesh->isBoundaryVertex(v))
- boundary.push_back(mesh->texcoord(v));
+ // Count charts.
+ uint32_t chartCount = 0;
+ const uint32_t chartGroupsCount = paramAtlas->chartGroupCount();
+ for (uint32_t i = 0; i < chartGroupsCount; i++) {
+ const param::ChartGroup *chartGroup = paramAtlas->chartGroupAt(i);
+ if (chartGroup->isVertexMap())
+ continue;
+ chartCount += chartGroup->chartCount();
}
- XA_DEBUG_ASSERT(boundary.size() > 0);
- // Compute bounding box of chart.
- m_boundingBox.compute(boundary.data(), boundary.size(), mesh->texcoords(), mesh->vertexCount());
- chart->majorAxis = m_boundingBox.majorAxis();
- chart->minorAxis = m_boundingBox.minorAxis();
- chart->minCorner = m_boundingBox.minCorner();
- chart->maxCorner = m_boundingBox.maxCorner();
- m_charts.push_back(chart);
+ if (chartCount == 0)
+ return;
+ // Run one task per chart.
+ Array<AddChartTaskArgs> taskArgs;
+ taskArgs.resize(chartCount);
+ TaskGroupHandle taskGroup = taskScheduler->createTaskGroup(chartCount);
+ uint32_t chartIndex = 0;
+ for (uint32_t i = 0; i < chartGroupsCount; i++) {
+ const param::ChartGroup *chartGroup = paramAtlas->chartGroupAt(i);
+ if (chartGroup->isVertexMap())
+ continue;
+ const uint32_t count = chartGroup->chartCount();
+ for (uint32_t j = 0; j < count; j++) {
+ AddChartTaskArgs &args = taskArgs[chartIndex];
+ args.paramChart = chartGroup->chartAt(j);
+ Task task;
+ task.userData = &taskArgs[chartIndex];
+ task.func = runAddChartTask;
+ taskScheduler->run(taskGroup, task);
+ chartIndex++;
+ }
+ }
+ taskScheduler->wait(&taskGroup);
+ // Get task output.
+ m_charts.resize(chartCount);
+ for (uint32_t i = 0; i < chartCount; i++)
+ m_charts[i] = taskArgs[i].chart;
}
void addUvMeshCharts(UvMeshInstance *mesh)
@@ -6950,6 +7002,7 @@ struct Atlas
BitArray vertexUsed(mesh->texcoords.size());
Array<Vector2> boundary;
boundary.reserve(16);
+ BoundingBox2D boundingBox;
for (uint32_t c = 0; c < mesh->mesh->charts.size(); c++) {
UvMeshChart *uvChart = mesh->mesh->charts[c];
Chart *chart = XA_NEW(MemTag::Default, Chart);
@@ -6960,6 +7013,8 @@ struct Atlas
chart->vertices = mesh->texcoords.data();
chart->vertexCount = mesh->texcoords.size();
chart->allowRotate = mesh->rotateCharts;
+ chart->faces.resize(uvChart->faces.size());
+ memcpy(chart->faces.data(), uvChart->faces.data(), sizeof(uint32_t) * uvChart->faces.size());
// Find unique vertices.
vertexUsed.clearAll();
for (uint32_t i = 0; i < chart->indexCount; i++) {
@@ -6997,11 +7052,11 @@ struct Atlas
boundary.push_back(chart->uniqueVertexAt(v));
XA_DEBUG_ASSERT(boundary.size() > 0);
// Compute bounding box of chart.
- m_boundingBox.compute(boundary.data(), boundary.size(), boundary.data(), boundary.size());
- chart->majorAxis = m_boundingBox.majorAxis();
- chart->minorAxis = m_boundingBox.minorAxis();
- chart->minCorner = m_boundingBox.minCorner();
- chart->maxCorner = m_boundingBox.maxCorner();
+ boundingBox.compute(boundary.data(), boundary.size(), boundary.data(), boundary.size());
+ chart->majorAxis = boundingBox.majorAxis();
+ chart->minorAxis = boundingBox.minorAxis();
+ chart->minCorner = boundingBox.minCorner();
+ chart->maxCorner = boundingBox.maxCorner();
m_charts.push_back(chart);
}
}
@@ -7022,8 +7077,10 @@ struct Atlas
}
return true;
}
- uint32_t resolution = options.resolution;
+ // Estimate resolution and/or texels per unit if not specified.
m_texelsPerUnit = options.texelsPerUnit;
+ uint32_t resolution = options.resolution > 0 ? options.resolution + options.padding * 2 : 0;
+ const uint32_t maxResolution = m_texelsPerUnit > 0.0f ? resolution : 0;
if (resolution <= 0 || m_texelsPerUnit <= 0) {
if (resolution <= 0 && m_texelsPerUnit <= 0)
resolution = 1024;
@@ -7049,15 +7106,11 @@ struct Atlas
float minChartPerimeter = FLT_MAX, maxChartPerimeter = 0.0f;
for (uint32_t c = 0; c < chartCount; c++) {
Chart *chart = m_charts[c];
- //chartOrderArray[c] = chart.surfaceArea;
// Compute chart scale
float scale = (chart->surfaceArea / chart->parametricArea) * m_texelsPerUnit;
- if (chart->parametricArea == 0) { // < kAreaEpsilon)
+ if (chart->parametricArea == 0.0f)
scale = 0;
- }
XA_ASSERT(isFinite(scale));
- // Sort charts by perimeter. @@ This is sometimes producing somewhat unexpected results. Is this right?
- //chartOrderArray[c] = ((chart->maxCorner.x - chart->minCorner.x) + (chart->maxCorner.y - chart->minCorner.y)) * scale;
// Translate, rotate and scale vertices. Compute extents.
Vector2 minCorner(FLT_MAX, FLT_MAX);
if (!chart->allowRotate) {
@@ -7077,58 +7130,59 @@ struct Atlas
texcoord -= minCorner;
}
texcoord *= scale;
- XA_DEBUG_ASSERT(texcoord.x >= 0 && texcoord.y >= 0);
+ XA_DEBUG_ASSERT(texcoord.x >= 0.0f && texcoord.y >= 0.0f);
XA_DEBUG_ASSERT(isFinite(texcoord.x) && isFinite(texcoord.y));
extents = max(extents, texcoord);
}
XA_DEBUG_ASSERT(extents.x >= 0 && extents.y >= 0);
- // Limit chart size.
- const float maxChartSize = (float)options.maxChartSize;
- if (extents.x > maxChartSize || extents.y > maxChartSize) {
- const float limit = max(extents.x, extents.y);
- scale = maxChartSize / (limit + 1.0f);
- for (uint32_t i = 0; i < chart->uniqueVertexCount(); i++)
- chart->uniqueVertexAt(i) *= scale;
- extents *= scale;
- XA_DEBUG_ASSERT(extents.x <= maxChartSize && extents.y <= maxChartSize);
- }
- // Scale the charts to use the entire texel area available. So, if the width is 0.1 we could scale it to 1 without increasing the lightmap usage and making a better
- // use of it. In many cases this also improves the look of the seams, since vertices on the chart boundaries have more chances of being aligned with the texel centers.
- float scale_x = 1.0f;
- float scale_y = 1.0f;
- float divide_x = 1.0f;
- float divide_y = 1.0f;
- if (extents.x > 0) {
- int cw = ftoi_ceil(extents.x);
- if (options.blockAlign) {
- // Align all chart extents to 4x4 blocks, but taking padding into account.
- cw = align(cw + 2, 4) - 2;
+ // Scale the charts to use the entire texel area available. So, if the width is 0.1 we could scale it to 1 without increasing the lightmap usage and making a better use of it. In many cases this also improves the look of the seams, since vertices on the chart boundaries have more chances of being aligned with the texel centers.
+ if (extents.x > 0.0f && extents.y > 0.0f) {
+ // Block align: align all chart extents to 4x4 blocks, but taking padding and texel center offset into account.
+ const int blockAlignSizeOffset = options.padding * 2 + 1;
+ int width = ftoi_ceil(extents.x);
+ if (options.blockAlign)
+ width = align(width + blockAlignSizeOffset, 4) - blockAlignSizeOffset;
+ int height = ftoi_ceil(extents.y);
+ if (options.blockAlign)
+ height = align(height + blockAlignSizeOffset, 4) - blockAlignSizeOffset;
+ for (uint32_t v = 0; v < chart->uniqueVertexCount(); v++) {
+ Vector2 &texcoord = chart->uniqueVertexAt(v);
+ texcoord.x = texcoord.x / extents.x * (float)width;
+ texcoord.y = texcoord.y / extents.y * (float)height;
}
- scale_x = (float(cw) - kEpsilon);
- divide_x = extents.x;
- extents.x = float(cw);
- }
- if (extents.y > 0) {
- int ch = ftoi_ceil(extents.y);
- if (options.blockAlign) {
- // Align all chart extents to 4x4 blocks, but taking padding into account.
- ch = align(ch + 2, 4) - 2;
+ extents.x = (float)width;
+ extents.y = (float)height;
+ }
+ // Limit chart size, either to PackOptions::maxChartSize or maxResolution (if set), whichever is smaller.
+ // If limiting chart size to maxResolution, print a warning, since that may not be desirable to the user.
+ uint32_t maxChartSize = options.maxChartSize;
+ bool warnChartResized = false;
+ if (maxResolution > 0 && (maxChartSize == 0 || maxResolution < maxChartSize)) {
+ maxChartSize = maxResolution - options.padding * 2; // Don't include padding.
+ warnChartResized = true;
+ }
+ if (maxChartSize > 0) {
+ const float realMaxChartSize = (float)maxChartSize - 1.0f; // Aligning to texel centers increases texel footprint by 1.
+ if (extents.x > realMaxChartSize || extents.y > realMaxChartSize) {
+ if (warnChartResized)
+ XA_PRINT(" Resizing chart %u from %gx%g to %ux%u to fit atlas\n", c, extents.x, extents.y, maxChartSize, maxChartSize);
+ scale = realMaxChartSize / max(extents.x, extents.y);
+ for (uint32_t i = 0; i < chart->uniqueVertexCount(); i++) {
+ Vector2 &texcoord = chart->uniqueVertexAt(i);
+ texcoord = min(texcoord * scale, Vector2(realMaxChartSize));
+ }
}
- scale_y = (float(ch) - kEpsilon);
- divide_y = extents.y;
- extents.y = float(ch);
}
+ // Align to texel centers and add padding offset.
+ extents.x = extents.y = 0.0f;
for (uint32_t v = 0; v < chart->uniqueVertexCount(); v++) {
Vector2 &texcoord = chart->uniqueVertexAt(v);
- texcoord.x /= divide_x;
- texcoord.y /= divide_y;
- texcoord.x *= scale_x;
- texcoord.y *= scale_y;
- XA_ASSERT(isFinite(texcoord.x) && isFinite(texcoord.y));
+ texcoord.x += 0.5f + options.padding;
+ texcoord.y += 0.5f + options.padding;
+ extents = max(extents, texcoord);
}
chartExtents[c] = extents;
- // Sort charts by perimeter.
- chartOrderArray[c] = extents.x + extents.y;
+ chartOrderArray[c] = extents.x + extents.y; // Use perimeter for chart sort key.
minChartPerimeter = min(minChartPerimeter, chartOrderArray[c]);
maxChartPerimeter = max(maxChartPerimeter, chartOrderArray[c]);
}
@@ -7147,9 +7201,14 @@ struct Atlas
#else
const bool createImage = options.createImage;
#endif
- BitImage chartBitImage, chartBitImageRotated;
- int atlasWidth = 0, atlasHeight = 0;
- const bool resizableAtlas = !(options.resolution > 0 && options.texelsPerUnit > 0.0f);
+ // chartImage: result from conservative rasterization
+ // chartImageBilinear: chartImage plus any texels that would be sampled by bilinear filtering.
+ // chartImagePadding: either chartImage or chartImageBilinear depending on options, with a dilate filter applied options.padding times.
+ // Rotated versions swap x and y.
+ BitImage chartImage, chartImageBilinear, chartImagePadding;
+ BitImage chartImageRotated, chartImageBilinearRotated, chartImagePaddingRotated;
+ Array<Vector2i> atlasSizes;
+ atlasSizes.push_back(Vector2i(0, 0));
int progress = 0;
for (uint32_t i = 0; i < chartCount; i++) {
uint32_t c = ranks[chartCount - i - 1]; // largest chart first
@@ -7167,29 +7226,46 @@ struct Atlas
// V V V
// 0 1 2
XA_PROFILE_START(packChartsRasterize)
- // Leave room for padding.
- chartBitImage.resize(ftoi_ceil(chartExtents[c].x) + 1 + options.padding * 2, ftoi_ceil(chartExtents[c].y) + 1 + options.padding * 2, true);
+ // Resize and clear (discard = true) chart images.
+ // Leave room for padding at extents.
+ chartImage.resize(ftoi_ceil(chartExtents[c].x) + options.padding, ftoi_ceil(chartExtents[c].y) + options.padding, true);
if (chart->allowRotate)
- chartBitImageRotated.resize(chartBitImage.height(), chartBitImage.width(), true);
+ chartImageRotated.resize(chartImage.height(), chartImage.width(), true);
+ if (options.bilinear) {
+ chartImageBilinear.resize(chartImage.width(), chartImage.height(), true);
+ if (chart->allowRotate)
+ chartImageBilinearRotated.resize(chartImage.height(), chartImage.width(), true);
+ }
// Rasterize chart faces.
const uint32_t faceCount = chart->indexCount / 3;
for (uint32_t f = 0; f < faceCount; f++) {
- // Offset vertices by padding.
Vector2 vertices[3];
for (uint32_t v = 0; v < 3; v++)
- vertices[v] = chart->vertices[chart->indices[f * 3 + v]] + Vector2(0.5f) + Vector2(float(options.padding));
+ vertices[v] = chart->vertices[chart->indices[f * 3 + v]];
DrawTriangleCallbackArgs args;
- args.chartBitImage = &chartBitImage;
- args.chartBitImageRotated = chart->allowRotate ? &chartBitImageRotated : nullptr;
- raster::drawTriangle(Vector2((float)chartBitImage.width(), (float)chartBitImage.height()), vertices, drawTriangleCallback, &args);
- }
- // Expand chart by padding pixels. (dilation)
- BitImage chartBitImageNoPadding(chartBitImage), chartBitImageNoPaddingRotated(chartBitImageRotated);
+ args.chartBitImage = &chartImage;
+ args.chartBitImageRotated = chart->allowRotate ? &chartImageRotated : nullptr;
+ raster::drawTriangle(Vector2((float)chartImage.width(), (float)chartImage.height()), vertices, drawTriangleCallback, &args);
+ }
+ // Expand chart by pixels sampled by bilinear interpolation.
+ if (options.bilinear)
+ bilinearExpand(chart, &chartImage, &chartImageBilinear, chart->allowRotate ? &chartImageBilinearRotated : nullptr);
+ // Expand chart by padding pixels (dilation).
if (options.padding > 0) {
+ // Copy into the same BitImage instances for every chart to avoid reallocating BitImage buffers (largest chart is packed first).
XA_PROFILE_START(packChartsDilate)
- chartBitImage.dilate(options.padding);
- if (chart->allowRotate)
- chartBitImageRotated.dilate(options.padding);
+ if (options.bilinear)
+ chartImageBilinear.copyTo(chartImagePadding);
+ else
+ chartImage.copyTo(chartImagePadding);
+ chartImagePadding.dilate(options.padding);
+ if (chart->allowRotate) {
+ if (options.bilinear)
+ chartImageBilinearRotated.copyTo(chartImagePaddingRotated);
+ else
+ chartImageRotated.copyTo(chartImagePaddingRotated);
+ chartImagePaddingRotated.dilate(options.padding);
+ }
XA_PROFILE_END(packChartsDilate)
}
XA_PROFILE_END(packChartsRasterize)
@@ -7203,6 +7279,17 @@ struct Atlas
}
}
// Find a location to place the chart in the atlas.
+ BitImage *chartImageToPack, *chartImageToPackRotated;
+ if (options.padding > 0) {
+ chartImageToPack = &chartImagePadding;
+ chartImageToPackRotated = &chartImagePaddingRotated;
+ } else if (options.bilinear) {
+ chartImageToPack = &chartImageBilinear;
+ chartImageToPackRotated = &chartImageBilinearRotated;
+ } else {
+ chartImageToPack = &chartImage;
+ chartImageToPackRotated = &chartImageRotated;
+ }
uint32_t currentAtlas = 0;
int best_x = 0, best_y = 0;
int best_cw = 0, best_ch = 0;
@@ -7210,27 +7297,24 @@ struct Atlas
for (;;)
{
bool firstChartInBitImage = false;
+ XA_UNUSED(firstChartInBitImage);
if (currentAtlas + 1 > m_bitImages.size()) {
// Chart doesn't fit in the current bitImage, create a new one.
- BitImage *bi = XA_NEW(MemTag::Default, BitImage);
- bi->resize(resolution, resolution, true);
+ BitImage *bi = XA_NEW_ARGS(MemTag::Default, BitImage, resolution, resolution);
m_bitImages.push_back(bi);
+ atlasSizes.push_back(Vector2i(0, 0));
firstChartInBitImage = true;
if (createImage)
- m_atlasImages.push_back(XA_NEW(MemTag::Default, AtlasImage, resolution, resolution));
+ m_atlasImages.push_back(XA_NEW_ARGS(MemTag::Default, AtlasImage, resolution, resolution));
// Start positions are per-atlas, so create a new one of those too.
chartStartPositions.push_back(Vector2i(0, 0));
}
XA_PROFILE_START(packChartsFindLocation)
- const bool foundLocation = findChartLocation(taskScheduler, chartStartPositions[currentAtlas], options.bruteForce, m_bitImages[currentAtlas], &chartBitImage, &chartBitImageRotated, atlasWidth, atlasHeight, &best_x, &best_y, &best_cw, &best_ch, &best_r, options.blockAlign, resizableAtlas, chart->allowRotate);
+ const bool foundLocation = findChartLocation(taskScheduler, chartStartPositions[currentAtlas], options.bruteForce, m_bitImages[currentAtlas], chartImageToPack, chartImageToPackRotated, atlasSizes[currentAtlas].x, atlasSizes[currentAtlas].y, &best_x, &best_y, &best_cw, &best_ch, &best_r, options.blockAlign, maxResolution, chart->allowRotate);
XA_PROFILE_END(packChartsFindLocation)
- if (firstChartInBitImage && !foundLocation) {
- // Chart doesn't fit in an empty, newly allocated bitImage. texelsPerUnit must be too large for the resolution.
- XA_ASSERT(true && "chart doesn't fit");
- break;
- }
- if (resizableAtlas) {
- XA_DEBUG_ASSERT(foundLocation);
+ XA_DEBUG_ASSERT(!(firstChartInBitImage && !foundLocation)); // Chart doesn't fit in an empty, newly allocated bitImage. Shouldn't happen, since charts are resized if they are too big to fit in the atlas.
+ if (maxResolution == 0) {
+ XA_DEBUG_ASSERT(foundLocation); // The atlas isn't limited to a fixed resolution, a chart location should be found on the first attempt.
break;
}
if (foundLocation)
@@ -7241,7 +7325,7 @@ struct Atlas
// Update brute force start location.
if (options.bruteForce) {
// Reset start location if the chart expanded the atlas.
- if (best_x + best_cw > atlasWidth || best_y + best_ch > atlasHeight) {
+ if (best_x + best_cw > atlasSizes[currentAtlas].x || best_y + best_ch > atlasSizes[currentAtlas].y) {
for (uint32_t j = 0; j < chartStartPositions.size(); j++)
chartStartPositions[j] = Vector2i(0, 0);
}
@@ -7250,28 +7334,37 @@ struct Atlas
}
}
// Update parametric extents.
- atlasWidth = max(atlasWidth, best_x + best_cw);
- atlasHeight = max(atlasHeight, best_y + best_ch);
- if (resizableAtlas) {
- // Resize bitImage if necessary.
- if (uint32_t(atlasWidth) > m_bitImages[0]->width() || uint32_t(atlasHeight) > m_bitImages[0]->height()) {
- m_bitImages[0]->resize(nextPowerOfTwo(uint32_t(atlasWidth)), nextPowerOfTwo(uint32_t(atlasHeight)), false);
+ atlasSizes[currentAtlas].x = max(atlasSizes[currentAtlas].x, best_x + best_cw);
+ atlasSizes[currentAtlas].y = max(atlasSizes[currentAtlas].y, best_y + best_ch);
+ // Resize bitImage if necessary.
+ // If maxResolution > 0, the bitImage is always set to maxResolutionIncludingPadding on creation and doesn't need to be dynamically resized.
+ if (maxResolution == 0) {
+ const uint32_t w = (uint32_t)atlasSizes[currentAtlas].x;
+ const uint32_t h = (uint32_t)atlasSizes[currentAtlas].y;
+ if (w > m_bitImages[0]->width() || h > m_bitImages[0]->height()) {
+ m_bitImages[0]->resize(nextPowerOfTwo(w), nextPowerOfTwo(h), false);
if (createImage)
m_atlasImages[0]->resize(m_bitImages[0]->width(), m_bitImages[0]->height());
}
} else {
- atlasWidth = min((int)options.resolution, atlasWidth);
- atlasHeight = min((int)options.resolution, atlasHeight);
+ XA_DEBUG_ASSERT(atlasSizes[currentAtlas].x <= (int)maxResolution);
+ XA_DEBUG_ASSERT(atlasSizes[currentAtlas].y <= (int)maxResolution);
}
XA_PROFILE_START(packChartsBlit)
- addChart(m_bitImages[currentAtlas], &chartBitImage, &chartBitImageRotated, atlasWidth, atlasHeight, best_x, best_y, best_r);
+ addChart(m_bitImages[currentAtlas], chartImageToPack, chartImageToPackRotated, atlasSizes[currentAtlas].x, atlasSizes[currentAtlas].y, best_x, best_y, best_r);
XA_PROFILE_END(packChartsBlit)
if (createImage) {
- m_atlasImages[currentAtlas]->addChart(c, best_r == 0 ? &chartBitImageNoPadding : &chartBitImageNoPaddingRotated, false, atlasWidth, atlasHeight, best_x, best_y);
- m_atlasImages[currentAtlas]->addChart(c, best_r == 0 ? &chartBitImage : &chartBitImageRotated, true, atlasWidth, atlasHeight, best_x, best_y);
+ if (best_r == 0) {
+ m_atlasImages[currentAtlas]->addChart(c, &chartImage, options.bilinear ? &chartImageBilinear : nullptr, options.padding > 0 ? &chartImagePadding : nullptr, atlasSizes[currentAtlas].x, atlasSizes[currentAtlas].y, best_x, best_y);
+ } else {
+ m_atlasImages[currentAtlas]->addChart(c, &chartImageRotated, options.bilinear ? &chartImageBilinearRotated : nullptr, options.padding > 0 ? &chartImagePaddingRotated : nullptr, atlasSizes[currentAtlas].x, atlasSizes[currentAtlas].y, best_x, best_y);
+ }
}
chart->atlasIndex = (int32_t)currentAtlas;
- // Translate and rotate chart texture coordinates.
+ // Modify texture coordinates:
+ // - rotate if the chart should be rotated
+ // - translate to chart location
+ // - translate to remove padding from top and left atlas edges (unless block aligned)
for (uint32_t v = 0; v < chart->uniqueVertexCount(); v++) {
Vector2 &texcoord = chart->uniqueVertexAt(v);
Vector2 t = texcoord;
@@ -7279,8 +7372,12 @@ struct Atlas
XA_DEBUG_ASSERT(chart->allowRotate);
swap(t.x, t.y);
}
- texcoord.x = best_x + t.x + 0.5f;
- texcoord.y = best_y + t.y + 0.5f;
+ texcoord.x = best_x + t.x;
+ texcoord.y = best_y + t.y;
+ if (!options.blockAlign) {
+ texcoord.x -= (float)options.padding;
+ texcoord.y -= (float)options.padding;
+ }
XA_ASSERT(texcoord.x >= 0 && texcoord.y >= 0);
XA_ASSERT(isFinite(texcoord.x) && isFinite(texcoord.y));
}
@@ -7293,21 +7390,35 @@ struct Atlas
}
}
}
- if (resizableAtlas) {
- m_width = max(0, atlasWidth - (int)options.padding * 2);
- m_height = max(0, atlasHeight - (int)options.padding * 2);
+ if (options.blockAlign) {
+ if (maxResolution == 0) {
+ m_width = max(0, atlasSizes[0].x);
+ m_height = max(0, atlasSizes[0].y);
+ } else {
+ m_width = m_height = maxResolution;
+ }
} else {
- m_width = m_height = options.resolution;
+ // Remove padding from outer edges.
+ if (maxResolution == 0) {
+ m_width = max(0, atlasSizes[0].x - (int)options.padding * 2);
+ m_height = max(0, atlasSizes[0].y - (int)options.padding * 2);
+ } else {
+ m_width = m_height = maxResolution - (int)options.padding * 2;
+ }
}
XA_PRINT(" %dx%d resolution\n", m_width, m_height);
m_utilization.resize(m_bitImages.size());
for (uint32_t i = 0; i < m_utilization.size(); i++) {
- uint32_t count = 0;
- for (uint32_t y = 0; y < m_height; y++) {
- for (uint32_t x = 0; x < m_width; x++)
- count += m_bitImages[i]->bitAt(x, y);
+ if (m_width == 0 || m_height == 0)
+ m_utilization[i] = 0.0f;
+ else {
+ uint32_t count = 0;
+ for (uint32_t y = 0; y < m_height; y++) {
+ for (uint32_t x = 0; x < m_width; x++)
+ count += m_bitImages[i]->bitAt(x, y);
+ }
+ m_utilization[i] = float(count) / (m_width * m_height);
}
- m_utilization[i] = float(count) / (m_width * m_height);
if (m_utilization.size() > 1) {
XA_PRINT(" %u: %f%% utilization\n", i, m_utilization[i] * 100.0f);
}
@@ -7334,27 +7445,33 @@ private:
// is occupied at this point. At the end we have many small charts and a large atlas with sparse holes. Finding those holes randomly is slow. A better approach would be to
// start stacking large charts as if they were tetris pieces. Once charts get small try to place them randomly. It may be interesting to try a intermediate strategy, first try
// along one axis and then try exhaustively along that axis.
- bool findChartLocation(TaskScheduler *taskScheduler, const Vector2i &startPosition, bool bruteForce, const BitImage *atlasBitImage, const BitImage *chartBitImage, const BitImage *chartBitImageRotated, int w, int h, int *best_x, int *best_y, int *best_w, int *best_h, int *best_r, bool blockAligned, bool resizableAtlas, bool allowRotate)
+ bool findChartLocation(TaskScheduler *taskScheduler, const Vector2i &startPosition, bool bruteForce, const BitImage *atlasBitImage, const BitImage *chartBitImage, const BitImage *chartBitImageRotated, int w, int h, int *best_x, int *best_y, int *best_w, int *best_h, int *best_r, bool blockAligned, uint32_t maxResolution, bool allowRotate)
{
const int attempts = 4096;
if (bruteForce || attempts >= w * h)
- return findChartLocation_bruteForce(taskScheduler, startPosition, atlasBitImage, chartBitImage, chartBitImageRotated, w, h, best_x, best_y, best_w, best_h, best_r, blockAligned, resizableAtlas, allowRotate);
- return findChartLocation_random(atlasBitImage, chartBitImage, chartBitImageRotated, w, h, best_x, best_y, best_w, best_h, best_r, attempts, blockAligned, resizableAtlas, allowRotate);
+ return findChartLocation_bruteForce(taskScheduler, startPosition, atlasBitImage, chartBitImage, chartBitImageRotated, w, h, best_x, best_y, best_w, best_h, best_r, blockAligned, maxResolution, allowRotate);
+ return findChartLocation_random(atlasBitImage, chartBitImage, chartBitImageRotated, w, h, best_x, best_y, best_w, best_h, best_r, attempts, blockAligned, maxResolution, allowRotate);
}
- bool findChartLocation_bruteForce(TaskScheduler *taskScheduler, const Vector2i &startPosition, const BitImage *atlasBitImage, const BitImage *chartBitImage, const BitImage *chartBitImageRotated, int w, int h, int *best_x, int *best_y, int *best_w, int *best_h, int *best_r, bool blockAligned, bool resizableAtlas, bool allowRotate)
+ bool findChartLocation_bruteForce(TaskScheduler *taskScheduler, const Vector2i &startPosition, const BitImage *atlasBitImage, const BitImage *chartBitImage, const BitImage *chartBitImageRotated, int w, int h, int *best_x, int *best_y, int *best_w, int *best_h, int *best_r, bool blockAligned, uint32_t maxResolution, bool allowRotate)
{
const int stepSize = blockAligned ? 4 : 1;
+ const int chartMinHeight = min(chartBitImage->height(), chartBitImageRotated->height());
uint32_t taskCount = 0;
- for (int y = startPosition.y; y <= h + stepSize; y += stepSize)
+ for (int y = startPosition.y; y <= h + stepSize; y += stepSize) {
+ if (maxResolution > 0 && y > (int)maxResolution - chartMinHeight)
+ break;
taskCount++;
- Array<FindChartLocationBruteForceTaskArgs> taskArgs;
- taskArgs.resize(taskCount);
+ }
+ m_bruteForceTaskArgs.clear();
+ m_bruteForceTaskArgs.resize(taskCount);
TaskGroupHandle taskGroup = taskScheduler->createTaskGroup(taskCount);
std::atomic<bool> finished(false); // One of the tasks found a location that doesn't expand the atlas.
uint32_t i = 0;
for (int y = startPosition.y; y <= h + stepSize; y += stepSize) {
- FindChartLocationBruteForceTaskArgs &args = taskArgs[i];
+ if (maxResolution > 0 && y > (int)maxResolution - chartMinHeight)
+ break;
+ FindChartLocationBruteForceTaskArgs &args = m_bruteForceTaskArgs[i];
args.finished = &finished;
args.startPosition = Vector2i(y == startPosition.y ? startPosition.x : 0, y);
args.atlasBitImage = atlasBitImage;
@@ -7363,10 +7480,10 @@ private:
args.w = w;
args.h = h;
args.blockAligned = blockAligned;
- args.resizableAtlas = resizableAtlas;
args.allowRotate = allowRotate;
+ args.maxResolution = maxResolution;
Task task;
- task.userData = &taskArgs[i];
+ task.userData = &m_bruteForceTaskArgs[i];
task.func = runFindChartLocationBruteForceTask;
taskScheduler->run(taskGroup, task);
i++;
@@ -7376,7 +7493,7 @@ private:
int best_metric = INT_MAX;
bool best_insideAtlas = false;
for (i = 0; i < taskCount; i++) {
- FindChartLocationBruteForceTaskArgs &args = taskArgs[i];
+ FindChartLocationBruteForceTaskArgs &args = m_bruteForceTaskArgs[i];
if (args.best_metric > best_metric)
continue;
// A location that doesn't expand the atlas is always preferred.
@@ -7396,7 +7513,7 @@ private:
return best_metric != INT_MAX;
}
- bool findChartLocation_random(const BitImage *atlasBitImage, const BitImage *chartBitImage, const BitImage *chartBitImageRotated, int w, int h, int *best_x, int *best_y, int *best_w, int *best_h, int *best_r, int minTrialCount, bool blockAligned, bool resizableAtlas, bool allowRotate)
+ bool findChartLocation_random(const BitImage *atlasBitImage, const BitImage *chartBitImage, const BitImage *chartBitImageRotated, int w, int h, int *best_x, int *best_y, int *best_w, int *best_h, int *best_r, int minTrialCount, bool blockAligned, uint32_t maxResolution, bool allowRotate)
{
bool result = false;
const int BLOCK_SIZE = 4;
@@ -7410,16 +7527,17 @@ private:
// + 1 to extend atlas in case atlas full. We may want to use a higher number to increase probability of extending atlas.
int xRange = w + 1;
int yRange = h + 1;
- if (!resizableAtlas) {
- xRange = min(xRange, (int)atlasBitImage->width() - cw);
- yRange = min(yRange, (int)atlasBitImage->height() - ch);
+ // Clamp to max resolution.
+ if (maxResolution > 0) {
+ xRange = min(xRange, (int)maxResolution - cw);
+ yRange = min(yRange, (int)maxResolution - ch);
}
int x = m_rand.getRange(xRange);
int y = m_rand.getRange(yRange);
if (blockAligned) {
x = align(x, BLOCK_SIZE);
y = align(y, BLOCK_SIZE);
- if (!resizableAtlas && (x > (int)atlasBitImage->width() - cw || y > (int)atlasBitImage->height() - ch))
+ if (maxResolution > 0 && (x > (int)maxResolution - cw || y > (int)maxResolution - ch))
continue; // Block alignment pushed the chart outside the atlas.
}
// Early out.
@@ -7475,10 +7593,68 @@ private:
}
}
+ void bilinearExpand(const Chart *chart, BitImage *source, BitImage *dest, BitImage *destRotated) const
+ {
+ const int xOffsets[] = { -1, 0, 1, -1, 1, -1, 0, 1 };
+ const int yOffsets[] = { -1, -1, -1, 0, 0, 1, 1, 1 };
+ for (uint32_t y = 0; y < source->height(); y++) {
+ for (uint32_t x = 0; x < source->width(); x++) {
+ // Copy pixels from source.
+ if (source->bitAt(x, y))
+ goto setPixel;
+ // Empty pixel. If none of of the surrounding pixels are set, this pixel can't be sampled by bilinear interpolation.
+ {
+ uint32_t s = 0;
+ for (; s < 8; s++) {
+ const int sx = (int)x + xOffsets[s];
+ const int sy = (int)y + yOffsets[s];
+ if (sx < 0 || sy < 0 || sx >= (int)source->width() || sy >= (int)source->height())
+ continue;
+ if (source->bitAt((uint32_t)sx, (uint32_t)sy))
+ break;
+ }
+ if (s == 8)
+ continue;
+ }
+ // If a 2x2 square centered on the pixels centroid intersects the triangle, this pixel will be sampled by bilinear interpolation.
+ // See "Precomputed Global Illumination in Frostbite (GDC 2018)" page 95
+ for (uint32_t f = 0; f < chart->indexCount / 3; f++) {
+ const Vector2 centroid((float)x + 0.5f, (float)y + 0.5f);
+ Vector2 vertices[3];
+ for (uint32_t i = 0; i < 3; i++)
+ vertices[i] = chart->vertices[chart->indices[f * 3 + i]];
+ // Test for triangle vertex in square bounds.
+ for (uint32_t i = 0; i < 3; i++) {
+ const Vector2 &v = vertices[i];
+ if (v.x > centroid.x - 1.0f && v.x < centroid.x + 1.0f && v.y > centroid.y - 1.0f && v.y < centroid.y + 1.0f)
+ goto setPixel;
+ }
+ // Test for triangle edge intersection with square edge.
+ const Vector2 squareVertices[4] = {
+ Vector2(centroid.x - 1.0f, centroid.y - 1.0f),
+ Vector2(centroid.x + 1.0f, centroid.y - 1.0f),
+ Vector2(centroid.x + 1.0f, centroid.y + 1.0f),
+ Vector2(centroid.x - 1.0f, centroid.y + 1.0f)
+ };
+ for (uint32_t i = 0; i < 3; i++) {
+ for (uint32_t j = 0; j < 4; j++) {
+ if (linesIntersect(vertices[i], vertices[(i + 1) % 3], squareVertices[j], squareVertices[(j + 1) % 4], 0.0f))
+ goto setPixel;
+ }
+ }
+ }
+ continue;
+ setPixel:
+ dest->setBitAt(x, y);
+ if (destRotated)
+ destRotated->setBitAt(y, x);
+ }
+ }
+ }
+
struct DrawTriangleCallbackArgs
{
- BitImage *chartBitImage;
- BitImage *chartBitImageRotated;
+ BitImage *chartBitImage, *chartBitImageRotated;
};
static bool drawTriangleCallback(void *param, int x, int y)
@@ -7493,8 +7669,8 @@ private:
Array<AtlasImage *> m_atlasImages;
Array<float> m_utilization;
Array<BitImage *> m_bitImages;
- BoundingBox2D m_boundingBox;
Array<Chart *> m_charts;
+ Array<FindChartLocationBruteForceTaskArgs> m_bruteForceTaskArgs;
RadixSort m_radix;
uint32_t m_width = 0;
uint32_t m_height = 0;
@@ -7534,8 +7710,8 @@ static void DestroyOutputMeshes(Context *ctx)
for (int i = 0; i < (int)ctx->atlas.meshCount; i++) {
Mesh &mesh = ctx->atlas.meshes[i];
for (uint32_t j = 0; j < mesh.chartCount; j++) {
- if (mesh.chartArray[j].indexArray)
- XA_FREE(mesh.chartArray[j].indexArray);
+ if (mesh.chartArray[j].faceArray)
+ XA_FREE(mesh.chartArray[j].faceArray);
}
if (mesh.chartArray)
XA_FREE(mesh.chartArray);
@@ -7715,18 +7891,19 @@ AddMeshError::Enum AddMesh(Atlas *atlas, const MeshDecl &meshDecl, uint32_t mesh
#endif
// Don't know how many times AddMesh will be called, so progress needs to adjusted each time.
if (!ctx->addMeshProgress) {
- ctx->addMeshProgress = XA_NEW(internal::MemTag::Default, internal::Progress, ProgressCategory::AddMesh, ctx->progressFunc, ctx->progressUserData, 1);
+ ctx->addMeshProgress = XA_NEW_ARGS(internal::MemTag::Default, internal::Progress, ProgressCategory::AddMesh, ctx->progressFunc, ctx->progressUserData, 1);
}
else {
ctx->addMeshProgress->setMaxValue(internal::max(ctx->meshCount + 1, meshCountHint));
}
- bool decoded = (meshDecl.indexCount <= 0);
- uint32_t indexCount = decoded ? meshDecl.vertexCount : meshDecl.indexCount;
+ XA_PROFILE_START(addMeshCopyData)
+ const bool hasIndices = meshDecl.indexCount > 0;
+ const uint32_t indexCount = hasIndices ? meshDecl.indexCount : meshDecl.vertexCount;
XA_PRINT("Adding mesh %d: %u vertices, %u triangles\n", ctx->meshCount, meshDecl.vertexCount, indexCount / 3);
// Expecting triangle faces.
if ((indexCount % 3) != 0)
return AddMeshError::InvalidIndexCount;
- if (!decoded) {
+ if (hasIndices) {
// Check if any index is out of range.
for (uint32_t i = 0; i < indexCount; i++) {
const uint32_t index = DecodeIndex(meshDecl.indexFormat, meshDecl.indexData, meshDecl.indexOffset, i);
@@ -7737,7 +7914,7 @@ AddMeshError::Enum AddMesh(Atlas *atlas, const MeshDecl &meshDecl, uint32_t mesh
uint32_t meshFlags = internal::MeshFlags::HasFaceGroups | internal::MeshFlags::HasIgnoredFaces;
if (meshDecl.vertexNormalData)
meshFlags |= internal::MeshFlags::HasNormals;
- internal::Mesh *mesh = XA_NEW(internal::MemTag::Mesh, internal::Mesh, meshDecl.epsilon, meshDecl.vertexCount, indexCount / 3, meshFlags, ctx->meshCount);
+ internal::Mesh *mesh = XA_NEW_ARGS(internal::MemTag::Mesh, internal::Mesh, meshDecl.epsilon, meshDecl.vertexCount, indexCount / 3, meshFlags, ctx->meshCount);
for (uint32_t i = 0; i < meshDecl.vertexCount; i++) {
internal::Vector3 normal(0.0f);
internal::Vector2 texcoord(0.0f);
@@ -7750,7 +7927,7 @@ AddMeshError::Enum AddMesh(Atlas *atlas, const MeshDecl &meshDecl, uint32_t mesh
for (uint32_t i = 0; i < indexCount / 3; i++) {
uint32_t tri[3];
for (int j = 0; j < 3; j++)
- tri[j] = decoded ? i * 3 + j : DecodeIndex(meshDecl.indexFormat, meshDecl.indexData, meshDecl.indexOffset, i * 3 + j);
+ tri[j] = hasIndices ? DecodeIndex(meshDecl.indexFormat, meshDecl.indexData, meshDecl.indexOffset, i * 3 + j) : i * 3 + j;
bool ignore = false;
// Check for degenerate or zero length edges.
for (int j = 0; j < 3; j++) {
@@ -7769,10 +7946,37 @@ AddMeshError::Enum AddMesh(Atlas *atlas, const MeshDecl &meshDecl, uint32_t mesh
break;
}
}
+ // Ignore faces with any nan vertex attributes.
+ if (!ignore) {
+ for (int j = 0; j < 3; j++) {
+ const internal::Vector3 &pos = mesh->position(tri[j]);
+ if (internal::isNan(pos.x) || internal::isNan(pos.y) || internal::isNan(pos.z)) {
+ XA_PRINT(" NAN position in face: %d\n", i);
+ ignore = true;
+ break;
+ }
+ if (meshDecl.vertexNormalData) {
+ const internal::Vector3 &normal = mesh->normal(tri[j]);
+ if (internal::isNan(normal.x) || internal::isNan(normal.y) || internal::isNan(normal.z)) {
+ XA_PRINT(" NAN normal in face: %d\n", i);
+ ignore = true;
+ break;
+ }
+ }
+ if (meshDecl.vertexUvData) {
+ const internal::Vector2 &uv = mesh->texcoord(tri[j]);
+ if (internal::isNan(uv.x) || internal::isNan(uv.y)) {
+ XA_PRINT(" NAN texture coordinate in face: %d\n", i);
+ ignore = true;
+ break;
+ }
+ }
+ }
+ }
const internal::Vector3 &a = mesh->position(tri[0]);
const internal::Vector3 &b = mesh->position(tri[1]);
const internal::Vector3 &c = mesh->position(tri[2]);
- // Check for zero area faces. Don't bother if a degenerate or zero length edge was already detected.
+ // Check for zero area faces.
float area = 0.0f;
if (!ignore) {
area = internal::length(internal::cross(b - a, c - a)) * 0.5f;
@@ -7791,6 +7995,7 @@ AddMeshError::Enum AddMesh(Atlas *atlas, const MeshDecl &meshDecl, uint32_t mesh
ignore = true;
mesh->addFace(tri[0], tri[1], tri[2], ignore);
}
+ XA_PROFILE_END(addMeshCopyData)
if (ctx->addMeshTaskGroup.value == UINT32_MAX)
ctx->addMeshTaskGroup = ctx->taskScheduler->createTaskGroup();
AddMeshTaskArgs *taskArgs = XA_NEW(internal::MemTag::Default, AddMeshTaskArgs); // The task frees this.
@@ -7818,11 +8023,13 @@ void AddMeshJoin(Atlas *atlas)
ctx->addMeshProgress->~Progress();
XA_FREE(ctx->addMeshProgress);
ctx->addMeshProgress = nullptr;
+ ctx->paramAtlas.sortChartGroups();
#if XA_PROFILE
XA_PRINT("Added %u meshes\n", ctx->meshCount);
internal::s_profile.addMeshReal = clock() - internal::s_profile.addMeshReal;
#endif
XA_PROFILE_PRINT_AND_RESET(" Total (real): ", addMeshReal)
+ XA_PROFILE_PRINT_AND_RESET(" Copy data: ", addMeshCopyData)
XA_PROFILE_PRINT_AND_RESET(" Total (thread): ", addMeshThread)
XA_PROFILE_PRINT_AND_RESET(" Create colocals: ", addMeshCreateColocals)
XA_PROFILE_PRINT_AND_RESET(" Create face groups: ", addMeshCreateFaceGroups)
@@ -7880,8 +8087,13 @@ AddMeshError::Enum AddUvMesh(Atlas *atlas, const UvMeshDecl &decl)
}
internal::UvMeshInstance *meshInstance = XA_NEW(internal::MemTag::Default, internal::UvMeshInstance);
meshInstance->texcoords.resize(decl.vertexCount);
- for (uint32_t i = 0; i < decl.vertexCount; i++)
- meshInstance->texcoords[i] = *((const internal::Vector2 *)&((const uint8_t *)decl.vertexUvData)[decl.vertexStride * i]);
+ for (uint32_t i = 0; i < decl.vertexCount; i++) {
+ internal::Vector2 texcoord = *((const internal::Vector2 *)&((const uint8_t *)decl.vertexUvData)[decl.vertexStride * i]);
+ // Set nan values to 0.
+ if (internal::isNan(texcoord.x) || internal::isNan(texcoord.y))
+ texcoord.x = texcoord.y = 0.0f;
+ meshInstance->texcoords[i] = texcoord;
+ }
meshInstance->rotateCharts = decl.rotateCharts;
// See if this is an instance of an already existing mesh.
internal::UvMesh *mesh = nullptr;
@@ -7902,13 +8114,12 @@ AddMeshError::Enum AddUvMesh(Atlas *atlas, const UvMeshDecl &decl)
for (uint32_t i = 0; i < mesh->vertexToChartMap.size(); i++)
mesh->vertexToChartMap[i] = UINT32_MAX;
// Calculate charts (incident faces).
- internal::HashMap<internal::Vector2, uint32_t> vertexToFaceMap(internal::MemTag::Default, indexCount);
+ internal::HashMap<internal::Vector2> vertexToFaceMap(internal::MemTag::Default, indexCount); // Face is index / 3
const uint32_t faceCount = indexCount / 3;
for (uint32_t i = 0; i < indexCount; i++)
- vertexToFaceMap.add(meshInstance->texcoords[mesh->indices[i]], i / 3);
+ vertexToFaceMap.add(meshInstance->texcoords[mesh->indices[i]]);
internal::BitArray faceAssigned(faceCount);
faceAssigned.clearAll();
- internal::Array<uint32_t> chartFaces;
for (uint32_t f = 0; f < faceCount; f++) {
if (faceAssigned.bitAt(f))
continue;
@@ -7917,34 +8128,33 @@ AddMeshError::Enum AddUvMesh(Atlas *atlas, const UvMeshDecl &decl)
chart->material = decl.faceMaterialData ? decl.faceMaterialData[f] : 0;
// Walk incident faces and assign them to the chart.
faceAssigned.setBitAt(f);
- chartFaces.clear();
- chartFaces.push_back(f);
+ chart->faces.push_back(f);
for (;;) {
bool newFaceAssigned = false;
- const uint32_t faceCount2 = chartFaces.size();
+ const uint32_t faceCount2 = chart->faces.size();
for (uint32_t f2 = 0; f2 < faceCount2; f2++) {
- const uint32_t face = chartFaces[f2];
+ const uint32_t face = chart->faces[f2];
for (uint32_t i = 0; i < 3; i++) {
const internal::Vector2 &texcoord = meshInstance->texcoords[meshInstance->mesh->indices[face * 3 + i]];
- uint32_t mapFaceIndex = vertexToFaceMap.get(texcoord);
- while (mapFaceIndex != UINT32_MAX) {
- const uint32_t face2 = vertexToFaceMap.value(mapFaceIndex);
+ uint32_t mapIndex = vertexToFaceMap.get(texcoord);
+ while (mapIndex != UINT32_MAX) {
+ const uint32_t face2 = mapIndex / 3; // 3 vertices added per face.
// Materials must match.
if (!faceAssigned.bitAt(face2) && (!decl.faceMaterialData || decl.faceMaterialData[face] == decl.faceMaterialData[face2])) {
faceAssigned.setBitAt(face2);
- chartFaces.push_back(face2);
+ chart->faces.push_back(face2);
newFaceAssigned = true;
}
- mapFaceIndex = vertexToFaceMap.getNext(mapFaceIndex);
+ mapIndex = vertexToFaceMap.getNext(mapIndex);
}
}
}
if (!newFaceAssigned)
break;
}
- for (uint32_t i = 0; i < chartFaces.size(); i++) {
+ for (uint32_t i = 0; i < chart->faces.size(); i++) {
for (uint32_t j = 0; j < 3; j++) {
- const uint32_t vertex = meshInstance->mesh->indices[chartFaces[i] * 3 + j];
+ const uint32_t vertex = meshInstance->mesh->indices[chart->faces[i] * 3 + j];
chart->indices.push_back(vertex);
mesh->vertexToChartMap[vertex] = mesh->charts.size();
}
@@ -8019,11 +8229,14 @@ void ComputeCharts(Atlas *atlas, ChartOptions chartOptions)
XA_PRINT(" %u charts\n", chartCount);
XA_PROFILE_PRINT_AND_RESET(" Total (real): ", computeChartsReal)
XA_PROFILE_PRINT_AND_RESET(" Total (thread): ", computeChartsThread)
- XA_PROFILE_PRINT_AND_RESET(" Atlas builder: ", atlasBuilder)
- XA_PROFILE_PRINT_AND_RESET(" Init: ", atlasBuilderInit)
- XA_PROFILE_PRINT_AND_RESET(" Create initial charts: ", atlasBuilderCreateInitialCharts)
- XA_PROFILE_PRINT_AND_RESET(" Grow charts: ", atlasBuilderGrowCharts)
- XA_PROFILE_PRINT_AND_RESET(" Merge charts: ", atlasBuilderMergeCharts)
+ XA_PROFILE_PRINT_AND_RESET(" Build atlas: ", buildAtlas)
+ XA_PROFILE_PRINT_AND_RESET(" Init: ", buildAtlasInit)
+ XA_PROFILE_PRINT_AND_RESET(" Place seeds: ", buildAtlasPlaceSeeds)
+ XA_PROFILE_PRINT_AND_RESET(" Relocate seeds: ", buildAtlasRelocateSeeds)
+ XA_PROFILE_PRINT_AND_RESET(" Reset charts: ", buildAtlasResetCharts)
+ XA_PROFILE_PRINT_AND_RESET(" Grow charts: ", buildAtlasGrowCharts)
+ XA_PROFILE_PRINT_AND_RESET(" Merge charts: ", buildAtlasMergeCharts)
+ XA_PROFILE_PRINT_AND_RESET(" Fill holes: ", buildAtlasFillHoles)
XA_PROFILE_PRINT_AND_RESET(" Create chart meshes (real): ", createChartMeshesReal)
XA_PROFILE_PRINT_AND_RESET(" Create chart meshes (thread): ", createChartMeshesThread)
XA_PROFILE_PRINT_AND_RESET(" Fix t-junctions: ", fixChartMeshTJunctions)
@@ -8087,7 +8300,7 @@ void ParameterizeCharts(Atlas *atlas, ParameterizeFunc func)
XA_PRINT(" %u planar charts, %u ortho charts, %u other\n", planarChartsCount, orthoChartsCount, chartCount - (planarChartsCount + orthoChartsCount));
if (chartsDeletedCount > 0) {
XA_PRINT(" %u charts deleted due to invalid parameterizations, %u new charts added\n", chartsDeletedCount, chartsAddedCount);
- XA_PRINT(" %u charts\n", ctx->paramAtlas.chartCount());
+ XA_PRINT(" %u charts\n", chartCount);
}
uint32_t chartIndex = 0, invalidParamCount = 0;
for (uint32_t i = 0; i < ctx->meshCount; i++) {
@@ -8192,16 +8405,15 @@ void PackCharts(Atlas *atlas, PackOptions packOptions)
}
atlas->meshCount = 0;
// Pack charts.
+ XA_PROFILE_START(packChartsAddCharts)
internal::pack::Atlas packAtlas;
if (!ctx->uvMeshInstances.isEmpty()) {
for (uint32_t i = 0; i < ctx->uvMeshInstances.size(); i++)
packAtlas.addUvMeshCharts(ctx->uvMeshInstances[i]);
}
- else if (ctx->paramAtlas.chartCount() > 0) {
- ctx->paramAtlas.restoreOriginalChartTexcoords();
- for (uint32_t i = 0; i < ctx->paramAtlas.chartCount(); i++)
- packAtlas.addChart(ctx->paramAtlas.chartAt(i));
- }
+ else
+ packAtlas.addCharts(ctx->taskScheduler, &ctx->paramAtlas);
+ XA_PROFILE_END(packChartsAddCharts)
XA_PROFILE_START(packCharts)
if (!packAtlas.packCharts(ctx->taskScheduler, packOptions, ctx->progressFunc, ctx->progressUserData))
return;
@@ -8220,9 +8432,12 @@ void PackCharts(Atlas *atlas, PackOptions packOptions)
if (packOptions.createImage) {
atlas->image = XA_ALLOC_ARRAY(internal::MemTag::Default, uint32_t, atlas->atlasCount * atlas->width * atlas->height);
for (uint32_t i = 0; i < atlas->atlasCount; i++)
- packAtlas.getImages()[i]->copyTo(&atlas->image[atlas->width * atlas->height * i], atlas->width, atlas->height);
+ packAtlas.getImages()[i]->copyTo(&atlas->image[atlas->width * atlas->height * i], atlas->width, atlas->height, packOptions.blockAlign ? 0 : packOptions.padding);
}
XA_PROFILE_PRINT_AND_RESET(" Total: ", packCharts)
+ XA_PROFILE_PRINT_AND_RESET(" Add charts (real): ", packChartsAddCharts)
+ XA_PROFILE_PRINT_AND_RESET(" Add charts (thread): ", packChartsAddChartsThread)
+ XA_PROFILE_PRINT_AND_RESET(" Restore texcoords: ", packChartsAddChartsRestoreTexcoords)
XA_PROFILE_PRINT_AND_RESET(" Rasterize: ", packChartsRasterize)
XA_PROFILE_PRINT_AND_RESET(" Dilate (padding): ", packChartsDilate)
XA_PROFILE_PRINT_AND_RESET(" Find location (real): ", packChartsFindLocation)
@@ -8230,6 +8445,7 @@ void PackCharts(Atlas *atlas, PackOptions packOptions)
XA_PROFILE_PRINT_AND_RESET(" Blit: ", packChartsBlit)
XA_PRINT_MEM_USAGE
XA_PRINT("Building output meshes\n");
+ XA_PROFILE_START(buildOutputMeshes)
int progress = 0;
if (ctx->progressFunc) {
if (!ctx->progressFunc(ProgressCategory::BuildOutputMeshes, 0, ctx->progressUserData))
@@ -8265,8 +8481,7 @@ void PackCharts(Atlas *atlas, PackOptions packOptions)
outputMesh.chartArray = XA_ALLOC_ARRAY(internal::MemTag::Default, Chart, outputMesh.chartCount);
XA_PRINT(" mesh %u: %u vertices, %u triangles, %u charts\n", i, outputMesh.vertexCount, outputMesh.indexCount / 3, outputMesh.chartCount);
// Copy mesh data.
- uint32_t firstVertex = 0;
- uint32_t meshChartIndex = 0;
+ uint32_t firstVertex = 0, meshChartIndex = 0;
for (uint32_t cg = 0; cg < ctx->paramAtlas.chartGroupCount(i); cg++) {
const internal::param::ChartGroup *chartGroup = ctx->paramAtlas.chartGroupAt(i, cg);
if (chartGroup->isVertexMap()) {
@@ -8315,16 +8530,14 @@ void PackCharts(Atlas *atlas, PackOptions packOptions)
outputChart->flags = 0;
if (chart->paramQuality().boundaryIntersection || chart->paramQuality().flippedTriangleCount > 0)
outputChart->flags |= ChartFlags::Invalid;
- outputChart->indexCount = mesh->faceCount() * 3;
- outputChart->indexArray = XA_ALLOC_ARRAY(internal::MemTag::Default, uint32_t, outputChart->indexCount);
- for (uint32_t f = 0; f < mesh->faceCount(); f++) {
- for (uint32_t j = 0; j < 3; j++)
- outputChart->indexArray[3 * f + j] = firstVertex + mesh->vertexAt(f * 3 + j);
- }
+ outputChart->faceCount = mesh->faceCount();
+ outputChart->faceArray = XA_ALLOC_ARRAY(internal::MemTag::Default, uint32_t, outputChart->faceCount);
+ for (uint32_t f = 0; f < outputChart->faceCount; f++)
+ outputChart->faceArray[f] = chartGroup->mapFaceToSourceFace(chart->mapFaceToSourceFace(f));
outputChart->material = 0;
meshChartIndex++;
chartIndex++;
- firstVertex += chart->mesh()->vertexCount();
+ firstVertex += mesh->vertexCount();
}
}
}
@@ -8378,10 +8591,11 @@ void PackCharts(Atlas *atlas, PackOptions packOptions)
const internal::pack::Chart *chart = packAtlas.getChart(chartIndex);
XA_DEBUG_ASSERT(chart->atlasIndex >= 0);
outputChart->atlasIndex = (uint32_t)chart->atlasIndex;
- outputChart->indexCount = chart->indexCount;
- outputChart->indexArray = XA_ALLOC_ARRAY(internal::MemTag::Default, uint32_t, outputChart->indexCount);
+ outputChart->faceCount = chart->faces.size();
+ outputChart->faceArray = XA_ALLOC_ARRAY(internal::MemTag::Default, uint32_t, outputChart->faceCount);
outputChart->material = chart->material;
- memcpy(outputChart->indexArray, chart->indices, chart->indexCount * sizeof(uint32_t));
+ for (uint32_t f = 0; f < outputChart->faceCount; f++)
+ outputChart->faceArray[f] = chart->faces[f];
chartIndex++;
}
if (ctx->progressFunc) {
@@ -8396,6 +8610,8 @@ void PackCharts(Atlas *atlas, PackOptions packOptions)
}
if (ctx->progressFunc && progress != 100)
ctx->progressFunc(ProgressCategory::BuildOutputMeshes, 100, ctx->progressUserData);
+ XA_PROFILE_END(buildOutputMeshes)
+ XA_PROFILE_PRINT_AND_RESET(" Total: ", buildOutputMeshes)
XA_PRINT_MEM_USAGE
}
@@ -8430,9 +8646,10 @@ void SetProgressCallback(Atlas *atlas, ProgressFunc progressFunc, void *progress
ctx->progressUserData = progressUserData;
}
-void SetRealloc(ReallocFunc reallocFunc)
+void SetAlloc(ReallocFunc reallocFunc, FreeFunc freeFunc)
{
internal::s_realloc = reallocFunc;
+ internal::s_free = freeFunc;
}
void SetPrint(PrintFunc print, bool verbose)
diff --git a/thirdparty/xatlas/xatlas.h b/thirdparty/xatlas/xatlas.h
index c123e800b4..7be165e7e5 100644
--- a/thirdparty/xatlas/xatlas.h
+++ b/thirdparty/xatlas/xatlas.h
@@ -48,8 +48,8 @@ struct Chart
{
uint32_t atlasIndex; // Sub-atlas index.
uint32_t flags;
- uint32_t *indexArray;
- uint32_t indexCount;
+ uint32_t *faceArray;
+ uint32_t faceCount;
uint32_t material;
};
@@ -73,9 +73,10 @@ struct Mesh
uint32_t vertexCount;
};
-static const uint32_t kImageChartIndexMask = 0x3FFFFFFF;
-static const uint32_t kImageHasChartIndexBit = 0x40000000;
-static const uint32_t kImageIsPaddingBit = 0x80000000;
+static const uint32_t kImageChartIndexMask = 0x1FFFFFFF;
+static const uint32_t kImageHasChartIndexBit = 0x80000000;
+static const uint32_t kImageIsBilinearBit = 0x40000000;
+static const uint32_t kImageIsPaddingBit = 0x20000000;
// Empty on creation. Populated after charts are packed.
struct Atlas
@@ -173,7 +174,6 @@ struct ChartOptions
float textureSeamMetricWeight = 0.5f;
float maxThreshold = 2.0f; // If total of all metrics * weights > maxThreshold, don't grow chart. Lower values result in more charts.
- uint32_t growFaceCount = 32; // Grow this many faces at a time.
uint32_t maxIterations = 1; // Number of iterations of the chart growing and seeding phases. Higher values result in better charts.
};
@@ -188,12 +188,24 @@ void ParameterizeCharts(Atlas *atlas, ParameterizeFunc func = nullptr);
struct PackOptions
{
+ // Leave space around charts for texels that would be sampled by bilinear filtering.
+ bool bilinear = true;
+
+ // Align charts to 4x4 blocks. Also improves packing speed, since there are fewer possible chart locations to consider.
+ bool blockAlign = false;
+
// Slower, but gives the best result. If false, use random chart placement.
bool bruteForce = false;
// Create Atlas::image
bool createImage = false;
+ // Charts larger than this will be scaled down. 0 means no limit.
+ uint32_t maxChartSize = 0;
+
+ // Number of pixels to pad charts with.
+ uint32_t padding = 0;
+
// Unit to texel scale. e.g. a 1x1 quad with texelsPerUnit of 32 will take up approximately 32x32 texels in the atlas.
// If 0, an estimated value will be calculated to approximately match the given resolution.
// If resolution is also 0, the estimated value will approximately match a 1024x1024 atlas.
@@ -203,15 +215,6 @@ struct PackOptions
// If not 0, and texelsPerUnit is not 0, generate one or more atlases with that exact resolution.
// If not 0, and texelsPerUnit is 0, texelsPerUnit is estimated to approximately match the resolution.
uint32_t resolution = 0;
-
- // Charts larger than this will be scaled down.
- uint32_t maxChartSize = 1024;
-
- // Align charts to 4x4 blocks. Also improves packing speed, since there are fewer possible chart locations to consider.
- bool blockAlign = false;
-
- // Number of pixels to pad charts with.
- uint32_t padding = 0;
};
// Call after ParameterizeCharts. Can be called multiple times to re-pack charts with different options.
@@ -240,7 +243,8 @@ void SetProgressCallback(Atlas *atlas, ProgressFunc progressFunc = nullptr, void
// Custom memory allocation.
typedef void *(*ReallocFunc)(void *, size_t);
-void SetRealloc(ReallocFunc reallocFunc);
+typedef void (*FreeFunc)(void *);
+void SetAlloc(ReallocFunc reallocFunc, FreeFunc freeFunc = nullptr);
// Custom print function.
typedef int (*PrintFunc)(const char *, ...);