diff options
Diffstat (limited to 'modules')
29 files changed, 3415 insertions, 516 deletions
diff --git a/modules/bullet/area_bullet.cpp b/modules/bullet/area_bullet.cpp index 01f068780d..edbd9565b8 100644 --- a/modules/bullet/area_bullet.cpp +++ b/modules/bullet/area_bullet.cpp @@ -174,7 +174,7 @@ void AreaBullet::do_reload_body() { void AreaBullet::set_space(SpaceBullet *p_space) { // Clear the old space if there is one if (space) { - overlappingObjects.clear(); + clear_overlaps(false); isScratched = false; // Remove this object form the physics world diff --git a/modules/bullet/rigid_body_bullet.cpp b/modules/bullet/rigid_body_bullet.cpp index 8ff27cda30..717c99c738 100644 --- a/modules/bullet/rigid_body_bullet.cpp +++ b/modules/bullet/rigid_body_bullet.cpp @@ -320,6 +320,7 @@ void RigidBodyBullet::set_space(SpaceBullet *p_space) { // Clear the old space if there is one if (space) { can_integrate_forces = false; + isScratchedSpaceOverrideModificator = false; // Remove all eventual constraints assert_no_constraints(); diff --git a/modules/enet/doc_classes/NetworkedMultiplayerENet.xml b/modules/enet/doc_classes/NetworkedMultiplayerENet.xml index 860da32a22..c908af7479 100644 --- a/modules/enet/doc_classes/NetworkedMultiplayerENet.xml +++ b/modules/enet/doc_classes/NetworkedMultiplayerENet.xml @@ -128,7 +128,7 @@ Enforce ordered packets when using [constant NetworkedMultiplayerPeer.TRANSFER_MODE_UNRELIABLE] (thus behaving similarly to [constant NetworkedMultiplayerPeer.TRANSFER_MODE_UNRELIABLE_ORDERED]). This is the only way to use ordering with the RPC system. </member> <member name="channel_count" type="int" setter="set_channel_count" getter="get_channel_count" default="3"> - The number of channels to be used by ENet. Channels are used to separate different kinds of data. In reliable or ordered mode, for example, the packet delivery order is ensured on a per channel basis. + The number of channels to be used by ENet. Channels are used to separate different kinds of data. In reliable or ordered mode, for example, the packet delivery order is ensured on a per-channel basis. This is done to combat latency and reduces ordering restrictions on packets. The delivery status of a packet in one channel won't stall the delivery of other packets in another channel. </member> <member name="compression_mode" type="int" setter="set_compression_mode" getter="get_compression_mode" enum="NetworkedMultiplayerENet.CompressionMode" default="0"> The compression method used for network packets. These have different tradeoffs of compression speed versus bandwidth, you may need to test which one works best for your use case if you use compression at all. @@ -141,7 +141,7 @@ Enable or disable the server feature that notifies clients of other peers' connection/disconnection, and relays messages between them. When this option is [code]false[/code], clients won't be automatically notified of other peers and won't be able to send them packets through the server. </member> <member name="transfer_channel" type="int" setter="set_transfer_channel" getter="get_transfer_channel" default="-1"> - Set the default channel to be used to transfer data. By default, this value is [code]-1[/code] which means that ENet will only use 2 channels, one for reliable and one for unreliable packets. Channel [code]0[/code] is reserved, and cannot be used. Setting this member to any value between [code]0[/code] and [member channel_count] (excluded) will force ENet to use that channel for sending data. + Set the default channel to be used to transfer data. By default, this value is [code]-1[/code] which means that ENet will only use 2 channels: one for reliable packets, and one for unreliable packets. The channel [code]0[/code] is reserved and cannot be used. Setting this member to any value between [code]0[/code] and [member channel_count] (excluded) will force ENet to use that channel for sending data. See [member channel_count] for more information about ENet channels. </member> <member name="transfer_mode" type="int" setter="set_transfer_mode" getter="get_transfer_mode" override="true" enum="NetworkedMultiplayerPeer.TransferMode" default="2" /> <member name="use_dtls" type="bool" setter="set_dtls_enabled" getter="is_dtls_enabled" default="false"> diff --git a/modules/gdnative/nativescript/api_generator.cpp b/modules/gdnative/nativescript/api_generator.cpp index 62f2ec5024..ae0b5222f0 100644 --- a/modules/gdnative/nativescript/api_generator.cpp +++ b/modules/gdnative/nativescript/api_generator.cpp @@ -139,7 +139,7 @@ static String get_type_name(const PropertyInfo &info) { } /* - * Some comparison helper functions we need + * Some comparison helper functions we need */ struct MethodInfoComparator { diff --git a/modules/gdscript/editor/gdscript_highlighter.cpp b/modules/gdscript/editor/gdscript_highlighter.cpp index d0f27b632b..43d0116125 100644 --- a/modules/gdscript/editor/gdscript_highlighter.cpp +++ b/modules/gdscript/editor/gdscript_highlighter.cpp @@ -29,26 +29,14 @@ /*************************************************************************/ #include "gdscript_highlighter.h" +#include "../gdscript.h" #include "../gdscript_tokenizer.h" #include "editor/editor_settings.h" -#include "scene/gui/text_edit.h" - -inline bool _is_symbol(CharType c) { - return is_symbol(c); -} - -static bool _is_text_char(CharType c) { - return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_'; -} static bool _is_char(CharType c) { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_'; } -static bool _is_number(CharType c) { - return (c >= '0' && c <= '9'); -} - static bool _is_hex_symbol(CharType c) { return ((c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')); } @@ -57,8 +45,8 @@ static bool _is_bin_symbol(CharType c) { return (c == '0' || c == '1'); } -Map<int, TextEdit::HighlighterInfo> GDScriptSyntaxHighlighter::_get_line_syntax_highlighting(int p_line) { - Map<int, TextEdit::HighlighterInfo> color_map; +Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting(int p_line) { + Dictionary color_map; Type next_type = NONE; Type current_type = NONE; @@ -82,36 +70,136 @@ Map<int, TextEdit::HighlighterInfo> GDScriptSyntaxHighlighter::_get_line_syntax_ Color keyword_color; Color color; - int in_region = text_editor->_is_line_in_region(p_line); - int deregion = 0; + color_region_cache[p_line] = -1; + int in_region = -1; + if (p_line != 0) { + if (!color_region_cache.has(p_line - 1)) { + get_line_syntax_highlighting(p_line - 1); + } + in_region = color_region_cache[p_line - 1]; + } - const Map<int, TextEdit::Text::ColorRegionInfo> cri_map = text_editor->_get_line_color_region_info(p_line); - const String &str = text_editor->get_line(p_line); + const String &str = text_edit->get_line(p_line); + const int line_length = str.length(); Color prev_color; for (int j = 0; j < str.length(); j++) { - TextEdit::HighlighterInfo highlighter_info; + Dictionary highlighter_info; - if (deregion > 0) { - deregion--; - if (deregion == 0) { - in_region = -1; + color = font_color; + bool is_char = !is_symbol(str[j]); + bool is_a_symbol = is_symbol(str[j]); + bool is_number = (str[j] >= '0' && str[j] <= '9'); + + /* color regions */ + if (is_a_symbol || in_region != -1) { + int from = j; + for (; from < line_length; from++) { + if (str[from] == '\\') { + from++; + continue; + } + break; } - } - if (deregion != 0) { - if (color != prev_color) { - prev_color = color; - highlighter_info.color = color; - color_map[j] = highlighter_info; - } - continue; - } + if (from != line_length) { + /* check if we are in entering a region */ + if (in_region == -1) { + for (int c = 0; c < color_regions.size(); c++) { + /* check there is enough room */ + int chars_left = line_length - from; + int start_key_length = color_regions[c].start_key.length(); + int end_key_length = color_regions[c].end_key.length(); + if (chars_left < start_key_length) { + continue; + } + + /* search the line */ + bool match = true; + const CharType *start_key = color_regions[c].start_key.c_str(); + for (int k = 0; k < start_key_length; k++) { + if (start_key[k] != str[from + k]) { + match = false; + break; + } + } + if (!match) { + continue; + } + in_region = c; + from += start_key_length; + + /* check if it's the whole line */ + if (end_key_length == 0 || color_regions[c].line_only || from + end_key_length > line_length) { + prev_color = color_regions[in_region].color; + highlighter_info["color"] = color_regions[c].color; + color_map[j] = highlighter_info; + + j = line_length; + if (!color_regions[c].line_only) { + color_region_cache[p_line] = c; + } + } + break; + } - color = font_color; + if (j == line_length) { + continue; + } + } - bool is_char = _is_text_char(str[j]); - bool is_symbol = _is_symbol(str[j]); - bool is_number = _is_number(str[j]); + /* if we are in one find the end key */ + if (in_region != -1) { + /* check there is enough room */ + int chars_left = line_length - from; + int end_key_length = color_regions[in_region].end_key.length(); + if (chars_left < end_key_length) { + continue; + } + + /* search the line */ + int region_end_index = -1; + const CharType *end_key = color_regions[in_region].start_key.c_str(); + for (; from < line_length; from++) { + if (!is_a_symbol) { + continue; + } + + if (str[from] == '\\') { + from++; + continue; + } + + for (int k = 0; k < end_key_length; k++) { + if (end_key[k] == str[from + k]) { + region_end_index = from; + break; + } + } + + if (region_end_index != -1) { + break; + } + } + + prev_color = color_regions[in_region].color; + highlighter_info["color"] = color_regions[in_region].color; + color_map[j] = highlighter_info; + + previous_type = REGION; + previous_text = ""; + previous_column = j; + j = from; + if (region_end_index == -1) { + color_region_cache[p_line] = in_region; + } + + in_region = -1; + prev_is_char = false; + prev_is_number = false; + continue; + } + } + } // allow ABCDEF in hex notation if (is_hex_notation && (_is_hex_symbol(str[j]) || is_number)) { @@ -133,7 +221,7 @@ Map<int, TextEdit::HighlighterInfo> GDScriptSyntaxHighlighter::_get_line_syntax_ // check for dot or underscore or 'x' for hex notation in floating point number or 'e' for scientific notation if ((str[j] == '.' || str[j] == 'x' || str[j] == 'b' || str[j] == '_' || str[j] == 'e') && !in_word && prev_is_number && !is_number) { is_number = true; - is_symbol = false; + is_a_symbol = false; is_char = false; if (str[j] == 'x' && str[j - 1] == '0') { @@ -151,43 +239,26 @@ Map<int, TextEdit::HighlighterInfo> GDScriptSyntaxHighlighter::_get_line_syntax_ is_number = false; } - if (is_symbol && str[j] != '.' && in_word) { + if (is_a_symbol && str[j] != '.' && in_word) { in_word = false; } - if (is_symbol && cri_map.has(j)) { - const TextEdit::Text::ColorRegionInfo &cri = cri_map[j]; - - if (in_region == -1) { - if (!cri.end) { - in_region = cri.region; - } - } else { - TextEdit::ColorRegion cr = text_editor->_get_color_region(cri.region); - if (in_region == cri.region && !cr.line_only) { //ignore otherwise - if (cri.end || cr.eq) { - deregion = cr.eq ? cr.begin_key.length() : cr.end_key.length(); - } - } - } - } - if (!is_char) { in_keyword = false; } - if (in_region == -1 && !in_keyword && is_char && !prev_is_char) { + if (!in_keyword && is_char && !prev_is_char) { int to = j; - while (to < str.length() && _is_text_char(str[to])) { + while (to < str.length() && !is_symbol(str[to])) { to++; } String word = str.substr(j, to - j); Color col = Color(); - if (text_editor->has_keyword_color(word)) { - col = text_editor->get_keyword_color(word); - } else if (text_editor->has_member_color(word)) { - col = text_editor->get_member_color(word); + if (keywords.has(word)) { + col = keywords[word]; + } else if (member_keywords.has(word)) { + col = member_keywords[word]; for (int k = j - 1; k >= 0; k--) { if (str[k] == '.') { col = Color(); //member indexing not allowed @@ -206,7 +277,7 @@ Map<int, TextEdit::HighlighterInfo> GDScriptSyntaxHighlighter::_get_line_syntax_ if (!in_function_name && in_word && !in_keyword) { int k = j; - while (k < str.length() && !_is_symbol(str[k]) && str[k] != '\t' && str[k] != ' ') { + while (k < str.length() && !is_symbol(str[k]) && str[k] != '\t' && str[k] != ' ') { k++; } @@ -224,7 +295,7 @@ Map<int, TextEdit::HighlighterInfo> GDScriptSyntaxHighlighter::_get_line_syntax_ if (!in_function_name && !in_member_variable && !in_keyword && !is_number && in_word) { int k = j; - while (k > 0 && !_is_symbol(str[k]) && str[k] != '\t' && str[k] != ' ') { + while (k > 0 && !is_symbol(str[k]) && str[k] != '\t' && str[k] != ' ') { k--; } @@ -233,7 +304,7 @@ Map<int, TextEdit::HighlighterInfo> GDScriptSyntaxHighlighter::_get_line_syntax_ } } - if (is_symbol) { + if (is_a_symbol) { if (in_function_name) { in_function_args = true; } @@ -270,14 +341,11 @@ Map<int, TextEdit::HighlighterInfo> GDScriptSyntaxHighlighter::_get_line_syntax_ if (!in_node_path && in_region == -1 && str[j] == '$') { in_node_path = true; - } else if (in_region != -1 || (is_symbol && str[j] != '/')) { + } else if (in_region != -1 || (is_a_symbol && str[j] != '/')) { in_node_path = false; } - if (in_region >= 0) { - next_type = REGION; - color = text_editor->_get_color_region(in_region).color; - } else if (in_node_path) { + if (in_node_path) { next_type = NODE_PATH; color = node_path_color; } else if (in_keyword) { @@ -294,7 +362,7 @@ Map<int, TextEdit::HighlighterInfo> GDScriptSyntaxHighlighter::_get_line_syntax_ } else { color = function_color; } - } else if (is_symbol) { + } else if (is_a_symbol) { next_type = SYMBOL; color = symbol_color; } else if (is_number) { @@ -335,32 +403,133 @@ Map<int, TextEdit::HighlighterInfo> GDScriptSyntaxHighlighter::_get_line_syntax_ if (color != prev_color) { prev_color = color; - highlighter_info.color = color; + highlighter_info["color"] = color; color_map[j] = highlighter_info; } } return color_map; } -String GDScriptSyntaxHighlighter::get_name() const { +String GDScriptSyntaxHighlighter::_get_name() const { return "GDScript"; } -List<String> GDScriptSyntaxHighlighter::get_supported_languages() { - List<String> languages; +Array GDScriptSyntaxHighlighter::_get_supported_languages() const { + Array languages; languages.push_back("GDScript"); return languages; } void GDScriptSyntaxHighlighter::_update_cache() { - font_color = text_editor->get_theme_color("font_color"); - symbol_color = text_editor->get_theme_color("symbol_color"); - function_color = text_editor->get_theme_color("function_color"); - number_color = text_editor->get_theme_color("number_color"); - member_color = text_editor->get_theme_color("member_variable_color"); + keywords.clear(); + member_keywords.clear(); + color_regions.clear(); + color_region_cache.clear(); + + font_color = text_edit->get_theme_color("font_color"); + symbol_color = EDITOR_GET("text_editor/highlighting/symbol_color"); + function_color = EDITOR_GET("text_editor/highlighting/function_color"); + number_color = EDITOR_GET("text_editor/highlighting/number_color"); + member_color = EDITOR_GET("text_editor/highlighting/member_variable_color"); + + /* Engine types. */ + const Color types_color = EDITOR_GET("text_editor/highlighting/engine_type_color"); + List<StringName> types; + ClassDB::get_class_list(&types); + for (List<StringName>::Element *E = types.front(); E; E = E->next()) { + String n = E->get(); + if (n.begins_with("_")) { + n = n.substr(1, n.length()); + } + keywords[n] = types_color; + } + + /* User types. */ + const Color usertype_color = EDITOR_GET("text_editor/highlighting/user_type_color"); + List<StringName> global_classes; + ScriptServer::get_global_class_list(&global_classes); + for (List<StringName>::Element *E = global_classes.front(); E; E = E->next()) { + keywords[String(E->get())] = usertype_color; + } + + /* Autoloads. */ + Map<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list(); + for (Map<StringName, ProjectSettings::AutoloadInfo>::Element *E = autoloads.front(); E; E = E->next()) { + const ProjectSettings::AutoloadInfo &info = E->value(); + if (info.is_singleton) { + keywords[info.name] = usertype_color; + } + } - const String text_editor_color_theme = EditorSettings::get_singleton()->get("text_editor/theme/color_theme"); - const bool default_theme = text_editor_color_theme == "Default"; + const GDScriptLanguage *gdscript = GDScriptLanguage::get_singleton(); + + /* Core types. */ + const Color basetype_color = EDITOR_GET("text_editor/highlighting/base_type_color"); + List<String> core_types; + gdscript->get_core_type_words(&core_types); + for (List<String>::Element *E = core_types.front(); E; E = E->next()) { + keywords[E->get()] = basetype_color; + } + + /* Reserved words. */ + const Color keyword_color = EDITOR_GET("text_editor/highlighting/keyword_color"); + List<String> keyword_list; + gdscript->get_reserved_words(&keyword_list); + for (List<String>::Element *E = keyword_list.front(); E; E = E->next()) { + keywords[E->get()] = keyword_color; + } + + /* Comments */ + const Color comment_color = EDITOR_GET("text_editor/highlighting/comment_color"); + List<String> comments; + gdscript->get_comment_delimiters(&comments); + for (List<String>::Element *E = comments.front(); E; E = E->next()) { + String comment = E->get(); + String beg = comment.get_slice(" ", 0); + String end = comment.get_slice_count(" ") > 1 ? comment.get_slice(" ", 1) : String(); + add_color_region(beg, end, comment_color, end == ""); + } + + /* Strings */ + const Color string_color = EDITOR_GET("text_editor/highlighting/string_color"); + List<String> strings; + gdscript->get_string_delimiters(&strings); + for (List<String>::Element *E = strings.front(); E; E = E->next()) { + String string = E->get(); + String beg = string.get_slice(" ", 0); + String end = string.get_slice_count(" ") > 1 ? string.get_slice(" ", 1) : String(); + add_color_region(beg, end, string_color, end == ""); + } + + const Ref<Script> script = _get_edited_resource(); + if (script.is_valid()) { + /* Member types. */ + const Color member_variable_color = EDITOR_GET("text_editor/highlighting/member_variable_color"); + StringName instance_base = script->get_instance_base_type(); + if (instance_base != StringName()) { + List<PropertyInfo> plist; + ClassDB::get_property_list(instance_base, &plist); + for (List<PropertyInfo>::Element *E = plist.front(); E; E = E->next()) { + String name = E->get().name; + if (E->get().usage & PROPERTY_USAGE_CATEGORY || E->get().usage & PROPERTY_USAGE_GROUP || E->get().usage & PROPERTY_USAGE_SUBGROUP) { + continue; + } + if (name.find("/") != -1) { + continue; + } + member_keywords[name] = member_variable_color; + } + + List<String> clist; + ClassDB::get_integer_constant_list(instance_base, &clist); + for (List<String>::Element *E = clist.front(); E; E = E->next()) { + member_keywords[E->get()] = member_variable_color; + } + } + } + + const String text_edit_color_theme = EditorSettings::get_singleton()->get("text_editor/theme/color_theme"); + const bool default_theme = text_edit_color_theme == "Default"; if (default_theme || EditorSettings::get_singleton()->is_dark_theme()) { function_definition_color = Color(0.4, 0.9, 1.0); @@ -372,7 +541,7 @@ void GDScriptSyntaxHighlighter::_update_cache() { EDITOR_DEF("text_editor/highlighting/gdscript/function_definition_color", function_definition_color); EDITOR_DEF("text_editor/highlighting/gdscript/node_path_color", node_path_color); - if (text_editor_color_theme == "Adaptive" || default_theme) { + if (text_edit_color_theme == "Adaptive" || default_theme) { EditorSettings::get_singleton()->set_initial_value( "text_editor/highlighting/gdscript/function_definition_color", function_definition_color, @@ -388,6 +557,31 @@ void GDScriptSyntaxHighlighter::_update_cache() { type_color = EDITOR_GET("text_editor/highlighting/base_type_color"); } -SyntaxHighlighter *GDScriptSyntaxHighlighter::create() { - return memnew(GDScriptSyntaxHighlighter); +void GDScriptSyntaxHighlighter::add_color_region(const String &p_start_key, const String &p_end_key, const Color &p_color, bool p_line_only) { + for (int i = 0; i < p_start_key.length(); i++) { + ERR_FAIL_COND_MSG(!is_symbol(p_start_key[i]), "color regions must start with a symbol"); + } + + if (p_end_key.length() > 0) { + for (int i = 0; i < p_end_key.length(); i++) { + ERR_FAIL_COND_MSG(!is_symbol(p_end_key[i]), "color regions must end with a symbol"); + } + } + + for (int i = 0; i < color_regions.size(); i++) { + ERR_FAIL_COND_MSG(color_regions[i].start_key == p_start_key, "color region with start key '" + p_start_key + "' already exists."); + } + + ColorRegion color_region; + color_region.color = p_color; + color_region.start_key = p_start_key; + color_region.end_key = p_end_key; + color_region.line_only = p_line_only; + color_regions.push_back(color_region); +} + +Ref<EditorSyntaxHighlighter> GDScriptSyntaxHighlighter::_create() const { + Ref<GDScriptSyntaxHighlighter> syntax_highlighter; + syntax_highlighter.instance(); + return syntax_highlighter; } diff --git a/modules/gdscript/editor/gdscript_highlighter.h b/modules/gdscript/editor/gdscript_highlighter.h index e652fb1471..49357f3d2e 100644 --- a/modules/gdscript/editor/gdscript_highlighter.h +++ b/modules/gdscript/editor/gdscript_highlighter.h @@ -31,10 +31,25 @@ #ifndef GDSCRIPT_HIGHLIGHTER_H #define GDSCRIPT_HIGHLIGHTER_H +#include "editor/plugins/script_editor_plugin.h" #include "scene/gui/text_edit.h" -class GDScriptSyntaxHighlighter : public SyntaxHighlighter { +class GDScriptSyntaxHighlighter : public EditorSyntaxHighlighter { + GDCLASS(GDScriptSyntaxHighlighter, EditorSyntaxHighlighter) + private: + struct ColorRegion { + Color color; + String start_key; + String end_key; + bool line_only; + }; + Vector<ColorRegion> color_regions; + Map<int, int> color_region_cache; + + Dictionary keywords; + Dictionary member_keywords; + enum Type { NONE, REGION, @@ -59,14 +74,16 @@ private: Color node_path_color; Color type_color; + void add_color_region(const String &p_start_key, const String &p_end_key, const Color &p_color, bool p_line_only = false); + public: - static SyntaxHighlighter *create(); + virtual void _update_cache() override; + virtual Dictionary _get_line_syntax_highlighting(int p_line) override; - virtual void _update_cache(); - virtual Map<int, TextEdit::HighlighterInfo> _get_line_syntax_highlighting(int p_line); + virtual String _get_name() const override; + virtual Array _get_supported_languages() const override; - virtual String get_name() const; - virtual List<String> get_supported_languages(); + virtual Ref<EditorSyntaxHighlighter> _create() const override; }; #endif // GDSCRIPT_HIGHLIGHTER_H diff --git a/modules/gdscript/register_types.cpp b/modules/gdscript/register_types.cpp index 884946efc1..6c4e529922 100644 --- a/modules/gdscript/register_types.cpp +++ b/modules/gdscript/register_types.cpp @@ -142,6 +142,12 @@ static void _editor_init() { gd_export.instance(); EditorExport::get_singleton()->add_export_plugin(gd_export); +#ifdef TOOLS_ENABLED + Ref<GDScriptSyntaxHighlighter> gdscript_syntax_highlighter; + gdscript_syntax_highlighter.instance(); + ScriptEditor::get_singleton()->register_syntax_highlighter(gdscript_syntax_highlighter); +#endif + #ifndef GDSCRIPT_NO_LSP register_lsp_types(); GDScriptLanguageServer *lsp_plugin = memnew(GDScriptLanguageServer); @@ -166,7 +172,6 @@ void register_gdscript_types() { ResourceSaver::add_resource_format_saver(resource_saver_gd); #ifdef TOOLS_ENABLED - ScriptEditor::register_create_syntax_highlighter_function(GDScriptSyntaxHighlighter::create); EditorNode::add_init_callback(_editor_init); gdscript_translation_parser_plugin.instance(); diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp index 22e4d84e98..7d3ae31588 100644 --- a/modules/mono/csharp_script.cpp +++ b/modules/mono/csharp_script.cpp @@ -1963,7 +1963,7 @@ MonoObject *CSharpInstance::_internal_new_managed() { bool die = _unreference_owner_unsafe(); // Not ok for the owner to die here. If there is a situation where this can happen, it will be considered a bug. - CRASH_COND(die == true); + CRASH_COND(die); owner = nullptr; @@ -2298,7 +2298,7 @@ CSharpInstance::~CSharpInstance() { // Unreference the owner here, before the new "instance binding" references it. // Otherwise, the unsafe reference debug checks will incorrectly detect a bug. bool die = _unreference_owner_unsafe(); - CRASH_COND(die == true); // `owner_keep_alive` holds a reference, so it can't die + CRASH_COND(die); // `owner_keep_alive` holds a reference, so it can't die void *data = owner->get_script_instance_binding(CSharpLanguage::get_singleton()->get_language_index()); CRASH_COND(data == nullptr); @@ -3136,7 +3136,7 @@ CSharpInstance *CSharpScript::_create_instance(const Variant **p_args, int p_arg bool die = instance->_unreference_owner_unsafe(); // Not ok for the owner to die here. If there is a situation where this can happen, it will be considered a bug. - CRASH_COND(die == true); + CRASH_COND(die); p_owner->set_script_instance(nullptr); r_error.error = Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL; diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging.CLI/Program.cs b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging.CLI/Program.cs index 99a55c471b..4db71500da 100644 --- a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging.CLI/Program.cs +++ b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging.CLI/Program.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Reflection; diff --git a/modules/mono/editor/GodotTools/GodotTools.OpenVisualStudio/Program.cs b/modules/mono/editor/GodotTools/GodotTools.OpenVisualStudio/Program.cs index affb2a47e7..ce2b378623 100644 --- a/modules/mono/editor/GodotTools/GodotTools.OpenVisualStudio/Program.cs +++ b/modules/mono/editor/GodotTools/GodotTools.OpenVisualStudio/Program.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.IO; using System.Runtime.InteropServices; using System.Runtime.InteropServices.ComTypes; diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/AABB.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/AABB.cs index 6a4f785551..3aecce50f5 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/AABB.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/AABB.cs @@ -14,6 +14,10 @@ using real_t = System.Single; namespace Godot { + /// <summary> + /// Axis-Aligned Bounding Box. AABB consists of a position, a size, and + /// several utility functions. It is typically used for fast overlap tests. + /// </summary> [Serializable] [StructLayout(LayoutKind.Sequential)] public struct AABB : IEquatable<AABB> @@ -21,24 +25,55 @@ namespace Godot private Vector3 _position; private Vector3 _size; + /// <summary> + /// Beginning corner. Typically has values lower than End. + /// </summary> + /// <value>Directly uses a private field.</value> public Vector3 Position { get { return _position; } set { _position = value; } } + /// <summary> + /// Size from Position to End. Typically all components are positive. + /// If the size is negative, you can use <see cref="Abs"/> to fix it. + /// </summary> + /// <value>Directly uses a private field.</value> public Vector3 Size { get { return _size; } set { _size = value; } } + /// <summary> + /// Ending corner. This is calculated as <see cref="Position"/> plus + /// <see cref="Size"/>. Setting this value will change the size. + /// </summary> + /// <value>Getting is equivalent to `value = Position + Size`, setting is equivalent to `Size = value - Position`.</value> public Vector3 End { get { return _position + _size; } set { _size = value - _position; } } + /// <summary> + /// Returns an AABB with equivalent position and size, modified so that + /// the most-negative corner is the origin and the size is positive. + /// </summary> + /// <returns>The modified AABB.</returns> + public AABB Abs() + { + Vector3 end = End; + Vector3 topLeft = new Vector3(Mathf.Min(_position.x, end.x), Mathf.Min(_position.y, end.y), Mathf.Min(_position.z, end.z)); + return new AABB(topLeft, _size.Abs()); + } + + /// <summary> + /// Returns true if this AABB completely encloses another one. + /// </summary> + /// <param name="with">The other AABB that may be enclosed.</param> + /// <returns>A bool for whether or not this AABB encloses `b`.</returns> public bool Encloses(AABB with) { Vector3 src_min = _position; @@ -54,33 +89,59 @@ namespace Godot src_max.z > dst_max.z; } + /// <summary> + /// Returns this AABB expanded to include a given point. + /// </summary> + /// <param name="point">The point to include.</param> + /// <returns>The expanded AABB.</returns> public AABB Expand(Vector3 point) { Vector3 begin = _position; Vector3 end = _position + _size; if (point.x < begin.x) + { begin.x = point.x; + } if (point.y < begin.y) + { begin.y = point.y; + } if (point.z < begin.z) + { begin.z = point.z; + } if (point.x > end.x) + { end.x = point.x; + } if (point.y > end.y) + { end.y = point.y; + } if (point.z > end.z) + { end.z = point.z; + } return new AABB(begin, end - begin); } + /// <summary> + /// Returns the area of the AABB. + /// </summary> + /// <returns>The area.</returns> public real_t GetArea() { return _size.x * _size.y * _size.z; } + /// <summary> + /// Gets the position of one of the 8 endpoints of the AABB. + /// </summary> + /// <param name="idx">Which endpoint to get.</param> + /// <returns>An endpoint of the AABB.</returns> public Vector3 GetEndpoint(int idx) { switch (idx) @@ -106,6 +167,10 @@ namespace Godot } } + /// <summary> + /// Returns the normalized longest axis of the AABB. + /// </summary> + /// <returns>A vector representing the normalized longest axis of the AABB.</returns> public Vector3 GetLongestAxis() { var axis = new Vector3(1f, 0f, 0f); @@ -125,6 +190,10 @@ namespace Godot return axis; } + /// <summary> + /// Returns the <see cref="Vector3.Axis"/> index of the longest axis of the AABB. + /// </summary> + /// <returns>A <see cref="Vector3.Axis"/> index for which axis is longest.</returns> public Vector3.Axis GetLongestAxisIndex() { var axis = Vector3.Axis.X; @@ -144,6 +213,10 @@ namespace Godot return axis; } + /// <summary> + /// Returns the scalar length of the longest axis of the AABB. + /// </summary> + /// <returns>The scalar length of the longest axis of the AABB.</returns> public real_t GetLongestAxisSize() { real_t max_size = _size.x; @@ -157,6 +230,10 @@ namespace Godot return max_size; } + /// <summary> + /// Returns the normalized shortest axis of the AABB. + /// </summary> + /// <returns>A vector representing the normalized shortest axis of the AABB.</returns> public Vector3 GetShortestAxis() { var axis = new Vector3(1f, 0f, 0f); @@ -176,6 +253,10 @@ namespace Godot return axis; } + /// <summary> + /// Returns the <see cref="Vector3.Axis"/> index of the shortest axis of the AABB. + /// </summary> + /// <returns>A <see cref="Vector3.Axis"/> index for which axis is shortest.</returns> public Vector3.Axis GetShortestAxisIndex() { var axis = Vector3.Axis.X; @@ -195,6 +276,10 @@ namespace Godot return axis; } + /// <summary> + /// Returns the scalar length of the shortest axis of the AABB. + /// </summary> + /// <returns>The scalar length of the shortest axis of the AABB.</returns> public real_t GetShortestAxisSize() { real_t max_size = _size.x; @@ -208,6 +293,12 @@ namespace Godot return max_size; } + /// <summary> + /// Returns the support point in a given direction. + /// This is useful for collision detection algorithms. + /// </summary> + /// <param name="dir">The direction to find support for.</param> + /// <returns>A vector representing the support.</returns> public Vector3 GetSupport(Vector3 dir) { Vector3 half_extents = _size * 0.5f; @@ -219,6 +310,11 @@ namespace Godot dir.z > 0f ? -half_extents.z : half_extents.z); } + /// <summary> + /// Returns a copy of the AABB grown a given amount of units towards all the sides. + /// </summary> + /// <param name="by">The amount to grow by.</param> + /// <returns>The grown AABB.</returns> public AABB Grow(real_t by) { var res = this; @@ -233,16 +329,29 @@ namespace Godot return res; } + /// <summary> + /// Returns true if the AABB is flat or empty, or false otherwise. + /// </summary> + /// <returns>A bool for whether or not the AABB has area.</returns> public bool HasNoArea() { return _size.x <= 0f || _size.y <= 0f || _size.z <= 0f; } + /// <summary> + /// Returns true if the AABB has no surface (no size), or false otherwise. + /// </summary> + /// <returns>A bool for whether or not the AABB has area.</returns> public bool HasNoSurface() { return _size.x <= 0f && _size.y <= 0f && _size.z <= 0f; } + /// <summary> + /// Returns true if the AABB contains a point, or false otherwise. + /// </summary> + /// <param name="point">The point to check.</param> + /// <returns>A bool for whether or not the AABB contains `point`.</returns> public bool HasPoint(Vector3 point) { if (point.x < _position.x) @@ -261,6 +370,11 @@ namespace Godot return true; } + /// <summary> + /// Returns the intersection of this AABB and `b`. + /// </summary> + /// <param name="with">The other AABB.</param> + /// <returns>The clipped AABB.</returns> public AABB Intersection(AABB with) { Vector3 src_min = _position; @@ -297,24 +411,57 @@ namespace Godot return new AABB(min, max - min); } - public bool Intersects(AABB with) + /// <summary> + /// Returns true if the AABB overlaps with `b` + /// (i.e. they have at least one point in common). + /// + /// If `includeBorders` is true, they will also be considered overlapping + /// if their borders touch, even without intersection. + /// </summary> + /// <param name="with">The other AABB to check for intersections with.</param> + /// <param name="includeBorders">Whether or not to consider borders.</param> + /// <returns>A bool for whether or not they are intersecting.</returns> + public bool Intersects(AABB with, bool includeBorders = false) { - if (_position.x >= with._position.x + with._size.x) - return false; - if (_position.x + _size.x <= with._position.x) - return false; - if (_position.y >= with._position.y + with._size.y) - return false; - if (_position.y + _size.y <= with._position.y) - return false; - if (_position.z >= with._position.z + with._size.z) - return false; - if (_position.z + _size.z <= with._position.z) - return false; + if (includeBorders) + { + if (_position.x > with._position.x + with._size.x) + return false; + if (_position.x + _size.x < with._position.x) + return false; + if (_position.y > with._position.y + with._size.y) + return false; + if (_position.y + _size.y < with._position.y) + return false; + if (_position.z > with._position.z + with._size.z) + return false; + if (_position.z + _size.z < with._position.z) + return false; + } + else + { + if (_position.x >= with._position.x + with._size.x) + return false; + if (_position.x + _size.x <= with._position.x) + return false; + if (_position.y >= with._position.y + with._size.y) + return false; + if (_position.y + _size.y <= with._position.y) + return false; + if (_position.z >= with._position.z + with._size.z) + return false; + if (_position.z + _size.z <= with._position.z) + return false; + } return true; } + /// <summary> + /// Returns true if the AABB is on both sides of `plane`. + /// </summary> + /// <param name="plane">The plane to check for intersection.</param> + /// <returns>A bool for whether or not the AABB intersects the plane.</returns> public bool IntersectsPlane(Plane plane) { Vector3[] points = @@ -335,14 +482,24 @@ namespace Godot for (int i = 0; i < 8; i++) { if (plane.DistanceTo(points[i]) > 0) + { over = true; + } else + { under = true; + } } return under && over; } + /// <summary> + /// Returns true if the AABB intersects the line segment between `from` and `to`. + /// </summary> + /// <param name="from">The start of the line segment.</param> + /// <param name="to">The end of the line segment.</param> + /// <returns>A bool for whether or not the AABB intersects the line segment.</returns> public bool IntersectsSegment(Vector3 from, Vector3 to) { real_t min = 0f; @@ -359,7 +516,9 @@ namespace Godot if (segFrom < segTo) { if (segFrom > boxEnd || segTo < boxBegin) + { return false; + } real_t length = segTo - segFrom; cmin = segFrom < boxBegin ? (boxBegin - segFrom) / length : 0f; @@ -368,7 +527,9 @@ namespace Godot else { if (segTo > boxEnd || segFrom < boxBegin) + { return false; + } real_t length = segTo - segFrom; cmin = segFrom > boxEnd ? (boxEnd - segFrom) / length : 0f; @@ -381,14 +542,23 @@ namespace Godot } if (cmax < max) + { max = cmax; + } if (max < min) + { return false; + } } return true; } + /// <summary> + /// Returns a larger AABB that contains this AABB and `b`. + /// </summary> + /// <param name="with">The other AABB.</param> + /// <returns>The merged AABB.</returns> public AABB Merge(AABB with) { Vector3 beg1 = _position; @@ -411,22 +581,52 @@ namespace Godot return new AABB(min, max - min); } - // Constructors + /// <summary> + /// Constructs an AABB from a position and size. + /// </summary> + /// <param name="position">The position.</param> + /// <param name="size">The size, typically positive.</param> public AABB(Vector3 position, Vector3 size) { _position = position; _size = size; } + + /// <summary> + /// Constructs an AABB from a position, width, height, and depth. + /// </summary> + /// <param name="position">The position.</param> + /// <param name="width">The width, typically positive.</param> + /// <param name="height">The height, typically positive.</param> + /// <param name="depth">The depth, typically positive.</param> public AABB(Vector3 position, real_t width, real_t height, real_t depth) { _position = position; _size = new Vector3(width, height, depth); } + + /// <summary> + /// Constructs an AABB from x, y, z, and size. + /// </summary> + /// <param name="x">The position's X coordinate.</param> + /// <param name="y">The position's Y coordinate.</param> + /// <param name="z">The position's Z coordinate.</param> + /// <param name="size">The size, typically positive.</param> public AABB(real_t x, real_t y, real_t z, Vector3 size) { _position = new Vector3(x, y, z); _size = size; } + + /// <summary> + /// Constructs an AABB from x, y, z, width, height, and depth. + /// </summary> + /// <param name="x">The position's X coordinate.</param> + /// <param name="y">The position's Y coordinate.</param> + /// <param name="z">The position's Z coordinate.</param> + /// <param name="width">The width, typically positive.</param> + /// <param name="height">The height, typically positive.</param> + /// <param name="depth">The depth, typically positive.</param> public AABB(real_t x, real_t y, real_t z, real_t width, real_t height, real_t depth) { _position = new Vector3(x, y, z); @@ -458,6 +658,12 @@ namespace Godot return _position == other._position && _size == other._size; } + /// <summary> + /// Returns true if this AABB and `other` are approximately equal, by running + /// <see cref="Vector3.IsEqualApprox(Vector3)"/> on each component. + /// </summary> + /// <param name="other">The other AABB to compare.</param> + /// <returns>Whether or not the AABBs are approximately equal.</returns> public bool IsEqualApprox(AABB other) { return _position.IsEqualApprox(other._position) && _size.IsEqualApprox(other._size); diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs index baf470a0cc..16c666b8eb 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs @@ -8,6 +8,20 @@ using real_t = System.Single; namespace Godot { + /// <summary> + /// 3×3 matrix used for 3D rotation and scale. + /// Almost always used as an orthogonal basis for a Transform. + /// + /// Contains 3 vector fields X, Y and Z as its columns, which are typically + /// interpreted as the local basis vectors of a 3D transformation. For such use, + /// it is composed of a scaling and a rotation matrix, in that order (M = R.S). + /// + /// Can also be accessed as array of 3D vectors. These vectors are normally + /// orthogonal to each other, but are not necessarily normalized (due to scaling). + /// + /// For more information, read this documentation article: + /// https://docs.godotengine.org/en/latest/tutorials/math/matrices_and_transforms.html + /// </summary> [Serializable] [StructLayout(LayoutKind.Sequential)] public struct Basis : IEquatable<Basis> @@ -15,9 +29,9 @@ namespace Godot // NOTE: x, y and z are public-only. Use Column0, Column1 and Column2 internally. /// <summary> - /// Returns the basis matrix’s x vector. - /// This is equivalent to <see cref="Column0"/>. + /// The basis matrix's X vector (column 0). /// </summary> + /// <value>Equivalent to <see cref="Column0"/> and array index `[0]`.</value> public Vector3 x { get => Column0; @@ -25,9 +39,9 @@ namespace Godot } /// <summary> - /// Returns the basis matrix’s y vector. - /// This is equivalent to <see cref="Column1"/>. + /// The basis matrix's Y vector (column 1). /// </summary> + /// <value>Equivalent to <see cref="Column1"/> and array index `[1]`.</value> public Vector3 y { get => Column1; @@ -35,19 +49,40 @@ namespace Godot } /// <summary> - /// Returns the basis matrix’s z vector. - /// This is equivalent to <see cref="Column2"/>. + /// The basis matrix's Z vector (column 2). /// </summary> + /// <value>Equivalent to <see cref="Column2"/> and array index `[2]`.</value> public Vector3 z { get => Column2; set => Column2 = value; } + /// <summary> + /// Row 0 of the basis matrix. Shows which vectors contribute + /// to the X direction. Rows are not very useful for user code, + /// but are more efficient for some internal calculations. + /// </summary> public Vector3 Row0; + + /// <summary> + /// Row 1 of the basis matrix. Shows which vectors contribute + /// to the Y direction. Rows are not very useful for user code, + /// but are more efficient for some internal calculations. + /// </summary> public Vector3 Row1; + + /// <summary> + /// Row 2 of the basis matrix. Shows which vectors contribute + /// to the Z direction. Rows are not very useful for user code, + /// but are more efficient for some internal calculations. + /// </summary> public Vector3 Row2; + /// <summary> + /// Column 0 of the basis matrix (the X vector). + /// </summary> + /// <value>Equivalent to <see cref="x"/> and array index `[0]`.</value> public Vector3 Column0 { get => new Vector3(Row0.x, Row1.x, Row2.x); @@ -58,6 +93,11 @@ namespace Godot this.Row2.x = value.z; } } + + /// <summary> + /// Column 1 of the basis matrix (the Y vector). + /// </summary> + /// <value>Equivalent to <see cref="y"/> and array index `[1]`.</value> public Vector3 Column1 { get => new Vector3(Row0.y, Row1.y, Row2.y); @@ -68,6 +108,11 @@ namespace Godot this.Row2.y = value.z; } } + + /// <summary> + /// Column 2 of the basis matrix (the Z vector). + /// </summary> + /// <value>Equivalent to <see cref="z"/> and array index `[2]`.</value> public Vector3 Column2 { get => new Vector3(Row0.z, Row1.z, Row2.z); @@ -79,6 +124,10 @@ namespace Godot } } + /// <summary> + /// The scale of this basis. + /// </summary> + /// <value>Equivalent to the lengths of each column vector, but negative if the determinant is negative.</value> public Vector3 Scale { get @@ -86,11 +135,18 @@ namespace Godot real_t detSign = Mathf.Sign(Determinant()); return detSign * new Vector3 ( - new Vector3(this.Row0[0], this.Row1[0], this.Row2[0]).Length(), - new Vector3(this.Row0[1], this.Row1[1], this.Row2[1]).Length(), - new Vector3(this.Row0[2], this.Row1[2], this.Row2[2]).Length() + Column0.Length(), + Column1.Length(), + Column2.Length() ); } + set + { + value /= Scale; // Value becomes what's called "delta_scale" in core. + Column0 *= value.x; + Column1 *= value.y; + Column2 *= value.z; + } } /// <summary> @@ -157,8 +213,9 @@ namespace Godot real_t det = orthonormalizedBasis.Determinant(); if (det < 0) { - // Ensure that the determinant is 1, such that result is a proper rotation matrix which can be represented by Euler angles. - orthonormalizedBasis = orthonormalizedBasis.Scaled(Vector3.NegOne); + // Ensure that the determinant is 1, such that result is a proper + // rotation matrix which can be represented by Euler angles. + orthonormalizedBasis = orthonormalizedBasis.Scaled(-Vector3.One); } return orthonormalizedBasis.Quat(); @@ -182,6 +239,15 @@ namespace Godot Row2 = new Vector3(0, 0, diagonal.z); } + /// <summary> + /// Returns the determinant of the basis matrix. If the basis is + /// uniformly scaled, its determinant is the square of the scale. + /// + /// A negative determinant means the basis has a negative scale. + /// A zero determinant means the basis isn't invertible, + /// and is usually considered invalid. + /// </summary> + /// <returns>The determinant of the basis matrix.</returns> public real_t Determinant() { real_t cofac00 = Row1[1] * Row2[2] - Row1[2] * Row2[1]; @@ -191,6 +257,16 @@ namespace Godot return Row0[0] * cofac00 + Row0[1] * cofac10 + Row0[2] * cofac20; } + /// <summary> + /// Returns the basis's rotation in the form of Euler angles + /// (in the YXZ convention: when *decomposing*, first Z, then X, and Y last). + /// The returned vector contains the rotation angles in + /// the format (X angle, Y angle, Z angle). + /// + /// Consider using the <see cref="Basis.Quat()"/> method instead, which + /// returns a <see cref="Godot.Quat"/> quaternion instead of Euler angles. + /// </summary> + /// <returns>A Vector3 representing the basis rotation in Euler angles.</returns> public Vector3 GetEuler() { Basis m = Orthonormalized(); @@ -223,6 +299,12 @@ namespace Godot return euler; } + /// <summary> + /// Get rows by index. Rows are not very useful for user code, + /// but are more efficient for some internal calculations. + /// </summary> + /// <param name="index">Which row.</param> + /// <returns>One of `Row0`, `Row1`, or `Row2`.</returns> public Vector3 GetRow(int index) { switch (index) @@ -238,6 +320,12 @@ namespace Godot } } + /// <summary> + /// Sets rows by index. Rows are not very useful for user code, + /// but are more efficient for some internal calculations. + /// </summary> + /// <param name="index">Which row.</param> + /// <param name="value">The vector to set the row to.</param> public void SetRow(int index, Vector3 value) { switch (index) @@ -256,16 +344,16 @@ namespace Godot } } - public Vector3 GetColumn(int index) - { - return this[index]; - } - - public void SetColumn(int index, Vector3 value) - { - this[index] = value; - } - + /// <summary> + /// This function considers a discretization of rotations into + /// 24 points on unit sphere, lying along the vectors (x, y, z) with + /// each component being either -1, 0, or 1, and returns the index + /// of the point best representing the orientation of the object. + /// It is mainly used by the <see cref="GridMap"/> editor. + /// + /// For further details, refer to the Godot source code. + /// </summary> + /// <returns>The orthogonal index.</returns> public int GetOrthogonalIndex() { var orth = this; @@ -279,11 +367,17 @@ namespace Godot real_t v = row[j]; if (v > 0.5f) + { v = 1.0f; + } else if (v < -0.5f) + { v = -1.0f; + } else + { v = 0f; + } row[j] = v; @@ -294,12 +388,18 @@ namespace Godot for (int i = 0; i < 24; i++) { if (orth == _orthoBases[i]) + { return i; + } } return 0; } + /// <summary> + /// Returns the inverse of the matrix. + /// </summary> + /// <returns>The inverse matrix.</returns> public Basis Inverse() { real_t cofac00 = Row1[1] * Row2[2] - Row1[2] * Row2[1]; @@ -309,7 +409,9 @@ namespace Godot real_t det = Row0[0] * cofac00 + Row0[1] * cofac10 + Row0[2] * cofac20; if (det == 0) + { throw new InvalidOperationException("Matrix determinant is zero and cannot be inverted."); + } real_t detInv = 1.0f / det; @@ -328,11 +430,17 @@ namespace Godot ); } + /// <summary> + /// Returns the orthonormalized version of the basis matrix (useful to + /// call occasionally to avoid rounding errors for orthogonal matrices). + /// This performs a Gram-Schmidt orthonormalization on the basis of the matrix. + /// </summary> + /// <returns>An orthonormalized basis matrix.</returns> public Basis Orthonormalized() { - Vector3 column0 = GetColumn(0); - Vector3 column1 = GetColumn(1); - Vector3 column2 = GetColumn(2); + Vector3 column0 = this[0]; + Vector3 column1 = this[1]; + Vector3 column2 = this[2]; column0.Normalize(); column1 = column1 - column0 * column0.Dot(column1); @@ -343,48 +451,86 @@ namespace Godot return new Basis(column0, column1, column2); } + /// <summary> + /// Introduce an additional rotation around the given `axis` + /// by `phi` (in radians). The axis must be a normalized vector. + /// </summary> + /// <param name="axis">The axis to rotate around. Must be normalized.</param> + /// <param name="phi">The angle to rotate, in radians.</param> + /// <returns>The rotated basis matrix.</returns> public Basis Rotated(Vector3 axis, real_t phi) { return new Basis(axis, phi) * this; } + /// <summary> + /// Introduce an additional scaling specified by the given 3D scaling factor. + /// </summary> + /// <param name="scale">The scale to introduce.</param> + /// <returns>The scaled basis matrix.</returns> public Basis Scaled(Vector3 scale) { - var b = this; + Basis b = this; b.Row0 *= scale.x; b.Row1 *= scale.y; b.Row2 *= scale.z; return b; } - public Basis Slerp(Basis target, real_t t) + /// <summary> + /// Assuming that the matrix is a proper rotation matrix, slerp performs + /// a spherical-linear interpolation with another rotation matrix. + /// </summary> + /// <param name="target">The destination basis for interpolation.</param> + /// <param name="weight">A value on the range of 0.0 to 1.0, representing the amount of interpolation.</param> + /// <returns>The resulting basis matrix of the interpolation.</returns> + public Basis Slerp(Basis target, real_t weight) { - var from = new Quat(this); - var to = new Quat(target); + Quat from = new Quat(this); + Quat to = new Quat(target); - var b = new Basis(from.Slerp(to, t)); - b.Row0 *= Mathf.Lerp(Row0.Length(), target.Row0.Length(), t); - b.Row1 *= Mathf.Lerp(Row1.Length(), target.Row1.Length(), t); - b.Row2 *= Mathf.Lerp(Row2.Length(), target.Row2.Length(), t); + Basis b = new Basis(from.Slerp(to, weight)); + b.Row0 *= Mathf.Lerp(Row0.Length(), target.Row0.Length(), weight); + b.Row1 *= Mathf.Lerp(Row1.Length(), target.Row1.Length(), weight); + b.Row2 *= Mathf.Lerp(Row2.Length(), target.Row2.Length(), weight); return b; } + /// <summary> + /// Transposed dot product with the X axis of the matrix. + /// </summary> + /// <param name="with">A vector to calculate the dot product with.</param> + /// <returns>The resulting dot product.</returns> public real_t Tdotx(Vector3 with) { return this.Row0[0] * with[0] + this.Row1[0] * with[1] + this.Row2[0] * with[2]; } + /// <summary> + /// Transposed dot product with the Y axis of the matrix. + /// </summary> + /// <param name="with">A vector to calculate the dot product with.</param> + /// <returns>The resulting dot product.</returns> public real_t Tdoty(Vector3 with) { return this.Row0[1] * with[0] + this.Row1[1] * with[1] + this.Row2[1] * with[2]; } + /// <summary> + /// Transposed dot product with the Z axis of the matrix. + /// </summary> + /// <param name="with">A vector to calculate the dot product with.</param> + /// <returns>The resulting dot product.</returns> public real_t Tdotz(Vector3 with) { return this.Row0[2] * with[0] + this.Row1[2] * with[1] + this.Row2[2] * with[2]; } + /// <summary> + /// Returns the transposed version of the basis matrix. + /// </summary> + /// <returns>The transposed basis matrix.</returns> public Basis Transposed() { var tr = this; @@ -404,6 +550,11 @@ namespace Godot return tr; } + /// <summary> + /// Returns a vector transformed (multiplied) by the basis matrix. + /// </summary> + /// <param name="v">A vector to transform.</param> + /// <returns>The transfomed vector.</returns> public Vector3 Xform(Vector3 v) { return new Vector3 @@ -414,6 +565,14 @@ namespace Godot ); } + /// <summary> + /// Returns a vector transformed (multiplied) by the transposed basis matrix. + /// + /// Note: This results in a multiplication by the inverse of the + /// basis matrix only if it represents a rotation-reflection. + /// </summary> + /// <param name="v">A vector to inversely transform.</param> + /// <returns>The inversely transfomed vector.</returns> public Vector3 XformInv(Vector3 v) { return new Vector3 @@ -424,6 +583,12 @@ namespace Godot ); } + /// <summary> + /// Returns the basis's rotation in the form of a quaternion. + /// See <see cref="GetEuler()"/> if you need Euler angles, but keep in + /// mind that quaternions should generally be preferred to Euler angles. + /// </summary> + /// <returns>A <see cref="Godot.Quat"/> representing the basis's rotation.</returns> public Quat Quat() { real_t trace = Row0[0] + Row1[1] + Row2[2]; @@ -508,11 +673,33 @@ namespace Godot private static readonly Basis _flipY = new Basis(1, 0, 0, 0, -1, 0, 0, 0, 1); private static readonly Basis _flipZ = new Basis(1, 0, 0, 0, 1, 0, 0, 0, -1); + /// <summary> + /// The identity basis, with no rotation or scaling applied. + /// This is used as a replacement for `Basis()` in GDScript. + /// Do not use `new Basis()` with no arguments in C#, because it sets all values to zero. + /// </summary> + /// <value>Equivalent to `new Basis(Vector3.Right, Vector3.Up, Vector3.Back)`.</value> public static Basis Identity { get { return _identity; } } + /// <summary> + /// The basis that will flip something along the X axis when used in a transformation. + /// </summary> + /// <value>Equivalent to `new Basis(Vector3.Left, Vector3.Up, Vector3.Back)`.</value> public static Basis FlipX { get { return _flipX; } } + /// <summary> + /// The basis that will flip something along the Y axis when used in a transformation. + /// </summary> + /// <value>Equivalent to `new Basis(Vector3.Right, Vector3.Down, Vector3.Back)`.</value> public static Basis FlipY { get { return _flipY; } } + /// <summary> + /// The basis that will flip something along the Z axis when used in a transformation. + /// </summary> + /// <value>Equivalent to `new Basis(Vector3.Right, Vector3.Up, Vector3.Forward)`.</value> public static Basis FlipZ { get { return _flipZ; } } + /// <summary> + /// Constructs a pure rotation basis matrix from the given quaternion. + /// </summary> + /// <param name="quat">The quaternion to create the basis from.</param> public Basis(Quat quat) { real_t s = 2.0f / quat.LengthSquared; @@ -535,26 +722,41 @@ namespace Godot Row2 = new Vector3(xz - wy, yz + wx, 1.0f - (xx + yy)); } - public Basis(Vector3 euler) + /// <summary> + /// Constructs a pure rotation basis matrix from the given Euler angles + /// (in the YXZ convention: when *composing*, first Y, then X, and Z last), + /// given in the vector format as (X angle, Y angle, Z angle). + /// + /// Consider using the <see cref="Basis(Quat)"/> constructor instead, which + /// uses a <see cref="Godot.Quat"/> quaternion instead of Euler angles. + /// </summary> + /// <param name="eulerYXZ">The Euler angles to create the basis from.</param> + public Basis(Vector3 eulerYXZ) { real_t c; real_t s; - c = Mathf.Cos(euler.x); - s = Mathf.Sin(euler.x); + c = Mathf.Cos(eulerYXZ.x); + s = Mathf.Sin(eulerYXZ.x); var xmat = new Basis(1, 0, 0, 0, c, -s, 0, s, c); - c = Mathf.Cos(euler.y); - s = Mathf.Sin(euler.y); + c = Mathf.Cos(eulerYXZ.y); + s = Mathf.Sin(eulerYXZ.y); var ymat = new Basis(c, 0, s, 0, 1, 0, -s, 0, c); - c = Mathf.Cos(euler.z); - s = Mathf.Sin(euler.z); + c = Mathf.Cos(eulerYXZ.z); + s = Mathf.Sin(eulerYXZ.z); var zmat = new Basis(c, -s, 0, s, c, 0, 0, 0, 1); this = ymat * xmat * zmat; } + /// <summary> + /// Constructs a pure rotation basis matrix, rotated around the given `axis` + /// by `phi` (in radians). The axis must be a normalized vector. + /// </summary> + /// <param name="axis">The axis to rotate around. Must be normalized.</param> + /// <param name="phi">The angle to rotate, in radians.</param> public Basis(Vector3 axis, real_t phi) { Vector3 axisSq = new Vector3(axis.x * axis.x, axis.y * axis.y, axis.z * axis.z); @@ -582,6 +784,12 @@ namespace Godot Row2.y = xyzt + zyxs; } + /// <summary> + /// Constructs a basis matrix from 3 axis vectors (matrix columns). + /// </summary> + /// <param name="column0">The X vector, or Column0.</param> + /// <param name="column1">The Y vector, or Column1.</param> + /// <param name="column2">The Z vector, or Column2.</param> public Basis(Vector3 column0, Vector3 column1, Vector3 column2) { Row0 = new Vector3(column0.x, column1.x, column2.x); @@ -637,6 +845,12 @@ namespace Godot return Row0.Equals(other.Row0) && Row1.Equals(other.Row1) && Row2.Equals(other.Row2); } + /// <summary> + /// Returns true if this basis and `other` are approximately equal, by running + /// <see cref="Vector3.IsEqualApprox(Vector3)"/> on each component. + /// </summary> + /// <param name="other">The other basis to compare.</param> + /// <returns>Whether or not the matrices are approximately equal.</returns> public bool IsEqualApprox(Basis other) { return Row0.IsEqualApprox(other.Row0) && Row1.IsEqualApprox(other.Row1) && Row2.IsEqualApprox(other.Row2); diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Color.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Color.cs index 6030b72a44..3173a21986 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Color.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Color.cs @@ -3,15 +3,44 @@ using System.Runtime.InteropServices; namespace Godot { + /// <summary> + /// A color represented by red, green, blue, and alpha (RGBA) components. + /// The alpha component is often used for transparency. + /// Values are in floating-point and usually range from 0 to 1. + /// Some properties (such as CanvasItem.modulate) may accept values + /// greater than 1 (overbright or HDR colors). + /// + /// If you want to supply values in a range of 0 to 255, you should use + /// <see cref="Color8"/> and the `r8`/`g8`/`b8`/`a8` properties. + /// </summary> [Serializable] [StructLayout(LayoutKind.Sequential)] public struct Color : IEquatable<Color> { + /// <summary> + /// The color's red component, typically on the range of 0 to 1. + /// </summary> public float r; + + /// <summary> + /// The color's green component, typically on the range of 0 to 1. + /// </summary> public float g; + + /// <summary> + /// The color's blue component, typically on the range of 0 to 1. + /// </summary> public float b; + + /// <summary> + /// The color's alpha (transparency) component, typically on the range of 0 to 1. + /// </summary> public float a; + /// <summary> + /// Wrapper for <see cref="r"/> that uses the range 0 to 255 instead of 0 to 1. + /// </summary> + /// <value>Getting is equivalent to multiplying by 255 and rounding. Setting is equivalent to dividing by 255.</value> public int r8 { get @@ -24,6 +53,10 @@ namespace Godot } } + /// <summary> + /// Wrapper for <see cref="g"/> that uses the range 0 to 255 instead of 0 to 1. + /// </summary> + /// <value>Getting is equivalent to multiplying by 255 and rounding. Setting is equivalent to dividing by 255.</value> public int g8 { get @@ -36,6 +69,10 @@ namespace Godot } } + /// <summary> + /// Wrapper for <see cref="b"/> that uses the range 0 to 255 instead of 0 to 1. + /// </summary> + /// <value>Getting is equivalent to multiplying by 255 and rounding. Setting is equivalent to dividing by 255.</value> public int b8 { get @@ -48,6 +85,10 @@ namespace Godot } } + /// <summary> + /// Wrapper for <see cref="a"/> that uses the range 0 to 255 instead of 0 to 1. + /// </summary> + /// <value>Getting is equivalent to multiplying by 255 and rounding. Setting is equivalent to dividing by 255.</value> public int a8 { get @@ -60,6 +101,10 @@ namespace Godot } } + /// <summary> + /// The HSV hue of this color, on the range 0 to 1. + /// </summary> + /// <value>Getting is a long process, refer to the source code for details. Setting uses <see cref="FromHsv"/>.</value> public float h { get @@ -70,21 +115,31 @@ namespace Godot float delta = max - min; if (delta == 0) + { return 0; + } float h; if (r == max) + { h = (g - b) / delta; // Between yellow & magenta + } else if (g == max) + { h = 2 + (b - r) / delta; // Between cyan & yellow + } else + { h = 4 + (r - g) / delta; // Between magenta & cyan + } h /= 6.0f; if (h < 0) + { h += 1.0f; + } return h; } @@ -94,6 +149,10 @@ namespace Godot } } + /// <summary> + /// The HSV saturation of this color, on the range 0 to 1. + /// </summary> + /// <value>Getting is equivalent to the ratio between the min and max RGB value. Setting uses <see cref="FromHsv"/>.</value> public float s { get @@ -103,7 +162,7 @@ namespace Godot float delta = max - min; - return max != 0 ? delta / max : 0; + return max == 0 ? 0 : delta / max; } set { @@ -111,6 +170,10 @@ namespace Godot } } + /// <summary> + /// The HSV value (brightness) of this color, on the range 0 to 1. + /// </summary> + /// <value>Getting is equivalent to using `Max()` on the RGB components. Setting uses <see cref="FromHsv"/>.</value> public float v { get @@ -123,25 +186,10 @@ namespace Godot } } - public static Color ColorN(string name, float alpha = 1f) - { - name = name.Replace(" ", String.Empty); - name = name.Replace("-", String.Empty); - name = name.Replace("_", String.Empty); - name = name.Replace("'", String.Empty); - name = name.Replace(".", String.Empty); - name = name.ToLower(); - - if (!Colors.namedColors.ContainsKey(name)) - { - throw new ArgumentOutOfRangeException($"Invalid Color Name: {name}"); - } - - Color color = Colors.namedColors[name]; - color.a = alpha; - return color; - } - + /// <summary> + /// Access color components using their index. + /// </summary> + /// <value>`[0]` is equivalent to `.r`, `[1]` is equivalent to `.g`, `[2]` is equivalent to `.b`, `[3]` is equivalent to `.a`.</value> public float this[int index] { get @@ -182,73 +230,13 @@ namespace Godot } } - public void ToHsv(out float hue, out float saturation, out float value) - { - float max = (float)Mathf.Max(r, Mathf.Max(g, b)); - float min = (float)Mathf.Min(r, Mathf.Min(g, b)); - - float delta = max - min; - - if (delta == 0) - { - hue = 0; - } - else - { - if (r == max) - hue = (g - b) / delta; // Between yellow & magenta - else if (g == max) - hue = 2 + (b - r) / delta; // Between cyan & yellow - else - hue = 4 + (r - g) / delta; // Between magenta & cyan - - hue /= 6.0f; - - if (hue < 0) - hue += 1.0f; - } - - saturation = max == 0 ? 0 : 1f - 1f * min / max; - value = max; - } - - public static Color FromHsv(float hue, float saturation, float value, float alpha = 1.0f) - { - if (saturation == 0) - { - // acp_hromatic (grey) - return new Color(value, value, value, alpha); - } - - int i; - float f, p, q, t; - - hue *= 6.0f; - hue %= 6f; - i = (int)hue; - - f = hue - i; - p = value * (1 - saturation); - q = value * (1 - saturation * f); - t = value * (1 - saturation * (1 - f)); - - switch (i) - { - case 0: // Red is the dominant color - return new Color(value, t, p, alpha); - case 1: // Green is the dominant color - return new Color(q, value, p, alpha); - case 2: - return new Color(p, value, t, alpha); - case 3: // Blue is the dominant color - return new Color(p, q, value, alpha); - case 4: - return new Color(t, p, value, alpha); - default: // (5) Red is the dominant color - return new Color(value, p, q, alpha); - } - } - + /// <summary> + /// Returns a new color resulting from blending this color over another. + /// If the color is opaque, the result is also opaque. + /// The second color may have a range of alpha values. + /// </summary> + /// <param name="over">The color to blend over.</param> + /// <returns>This color blended over `over`.</returns> public Color Blend(Color over) { Color res; @@ -268,6 +256,10 @@ namespace Godot return res; } + /// <summary> + /// Returns the most contrasting color. + /// </summary> + /// <returns>The most contrasting color</returns> public Color Contrasted() { return new Color( @@ -278,6 +270,12 @@ namespace Godot ); } + /// <summary> + /// Returns a new color resulting from making this color darker + /// by the specified ratio (on the range of 0 to 1). + /// </summary> + /// <param name="amount">The ratio to darken by.</param> + /// <returns>The darkened color.</returns> public Color Darkened(float amount) { Color res = this; @@ -287,6 +285,10 @@ namespace Godot return res; } + /// <summary> + /// Returns the inverted color: `(1 - r, 1 - g, 1 - b, a)`. + /// </summary> + /// <returns>The inverted color.</returns> public Color Inverted() { return new Color( @@ -297,6 +299,12 @@ namespace Godot ); } + /// <summary> + /// Returns a new color resulting from making this color lighter + /// by the specified ratio (on the range of 0 to 1). + /// </summary> + /// <param name="amount">The ratio to lighten by.</param> + /// <returns>The darkened color.</returns> public Color Lightened(float amount) { Color res = this; @@ -306,6 +314,13 @@ namespace Godot return res; } + /// <summary> + /// Returns the result of the linear interpolation between + /// this color and `to` by amount `weight`. + /// </summary> + /// <param name="to">The destination color for interpolation.</param> + /// <param name="weight">A value on the range of 0.0 to 1.0, representing the amount of interpolation.</param> + /// <returns>The resulting color of the interpolation.</returns> public Color Lerp(Color to, float weight) { return new Color @@ -317,6 +332,13 @@ namespace Godot ); } + /// <summary> + /// Returns the result of the linear interpolation between + /// this color and `to` by color amount `weight`. + /// </summary> + /// <param name="to">The destination color for interpolation.</param> + /// <param name="weight">A color with components on the range of 0.0 to 1.0, representing the amount of interpolation.</param> + /// <returns>The resulting color of the interpolation.</returns> public Color Lerp(Color to, Color weight) { return new Color @@ -328,6 +350,12 @@ namespace Godot ); } + /// <summary> + /// Returns the color's 32-bit integer in ABGR format + /// (each byte represents a component of the ABGR profile). + /// ABGR is the reversed version of the default format. + /// </summary> + /// <returns>A uint representing this color in ABGR32 format.</returns> public uint ToAbgr32() { uint c = (byte)Math.Round(a * 255); @@ -341,6 +369,12 @@ namespace Godot return c; } + /// <summary> + /// Returns the color's 64-bit integer in ABGR format + /// (each word represents a component of the ABGR profile). + /// ABGR is the reversed version of the default format. + /// </summary> + /// <returns>A ulong representing this color in ABGR64 format.</returns> public ulong ToAbgr64() { ulong c = (ushort)Math.Round(a * 65535); @@ -354,6 +388,12 @@ namespace Godot return c; } + /// <summary> + /// Returns the color's 32-bit integer in ARGB format + /// (each byte represents a component of the ARGB profile). + /// ARGB is more compatible with DirectX, but not used much in Godot. + /// </summary> + /// <returns>A uint representing this color in ARGB32 format.</returns> public uint ToArgb32() { uint c = (byte)Math.Round(a * 255); @@ -367,6 +407,12 @@ namespace Godot return c; } + /// <summary> + /// Returns the color's 64-bit integer in ARGB format + /// (each word represents a component of the ARGB profile). + /// ARGB is more compatible with DirectX, but not used much in Godot. + /// </summary> + /// <returns>A ulong representing this color in ARGB64 format.</returns> public ulong ToArgb64() { ulong c = (ushort)Math.Round(a * 65535); @@ -380,6 +426,12 @@ namespace Godot return c; } + /// <summary> + /// Returns the color's 32-bit integer in RGBA format + /// (each byte represents a component of the RGBA profile). + /// RGBA is Godot's default and recommended format. + /// </summary> + /// <returns>A uint representing this color in RGBA32 format.</returns> public uint ToRgba32() { uint c = (byte)Math.Round(r * 255); @@ -393,6 +445,12 @@ namespace Godot return c; } + /// <summary> + /// Returns the color's 64-bit integer in RGBA format + /// (each word represents a component of the RGBA profile). + /// RGBA is Godot's default and recommended format. + /// </summary> + /// <returns>A ulong representing this color in RGBA64 format.</returns> public ulong ToRgba64() { ulong c = (ushort)Math.Round(r * 65535); @@ -406,6 +464,11 @@ namespace Godot return c; } + /// <summary> + /// Returns the color's HTML hexadecimal color string in RGBA format. + /// </summary> + /// <param name="includeAlpha">Whether or not to include alpha. If false, the color is RGB instead of RGBA.</param> + /// <returns>A string for the HTML hexadecimal representation of this color.</returns> public string ToHtml(bool includeAlpha = true) { var txt = string.Empty; @@ -415,12 +478,20 @@ namespace Godot txt += ToHex32(b); if (includeAlpha) - txt = ToHex32(a) + txt; + { + txt += ToHex32(a); + } return txt; } - // Constructors + /// <summary> + /// Constructs a color from RGB values on the range of 0 to 1. + /// </summary> + /// <param name="r">The color's red component, typically on the range of 0 to 1.</param> + /// <param name="g">The color's green component, typically on the range of 0 to 1.</param> + /// <param name="b">The color's blue component, typically on the range of 0 to 1.</param> + /// <param name="a">The color's alpha (transparency) value, typically on the range of 0 to 1. Default: 1.</param> public Color(float r, float g, float b, float a = 1.0f) { this.r = r; @@ -429,6 +500,11 @@ namespace Godot this.a = a; } + /// <summary> + /// Constructs a color from an existing color and an alpha value. + /// </summary> + /// <param name="c">The color to construct from. Only its RGB values are used.</param> + /// <param name="a">The color's alpha (transparency) value, typically on the range of 0 to 1. Default: 1.</param> public Color(Color c, float a = 1.0f) { r = c.r; @@ -437,6 +513,11 @@ namespace Godot this.a = a; } + /// <summary> + /// Constructs a color from a 32-bit integer + /// (each byte represents a component of the RGBA profile). + /// </summary> + /// <param name="rgba">The uint representing the color.</param> public Color(uint rgba) { a = (rgba & 0xFF) / 255.0f; @@ -448,6 +529,11 @@ namespace Godot r = (rgba & 0xFF) / 255.0f; } + /// <summary> + /// Constructs a color from a 64-bit integer + /// (each word represents a component of the RGBA profile). + /// </summary> + /// <param name="rgba">The ulong representing the color.</param> public Color(ulong rgba) { a = (rgba & 0xFFFF) / 65535.0f; @@ -459,6 +545,212 @@ namespace Godot r = (rgba & 0xFFFF) / 65535.0f; } + /// <summary> + /// Constructs a color from the HTML hexadecimal color string in RGBA format. + /// </summary> + /// <param name="rgba">A string for the HTML hexadecimal representation of this color.</param> + public Color(string rgba) + { + if (rgba.Length == 0) + { + r = 0f; + g = 0f; + b = 0f; + a = 1.0f; + return; + } + + if (rgba[0] == '#') + { + rgba = rgba.Substring(1); + } + + bool alpha; + + if (rgba.Length == 8) + { + alpha = true; + } + else if (rgba.Length == 6) + { + alpha = false; + } + else + { + throw new ArgumentOutOfRangeException("Invalid color code. Length is " + rgba.Length + " but a length of 6 or 8 is expected: " + rgba); + } + + if (alpha) + { + a = ParseCol8(rgba, 6) / 255f; + + if (a < 0) + { + throw new ArgumentOutOfRangeException("Invalid color code. Alpha part is not valid hexadecimal: " + rgba); + } + } + else + { + a = 1.0f; + } + + int from = alpha ? 2 : 0; + + r = ParseCol8(rgba, 0) / 255f; + + if (r < 0) + { + throw new ArgumentOutOfRangeException("Invalid color code. Red part is not valid hexadecimal: " + rgba); + } + + g = ParseCol8(rgba, 2) / 255f; + + if (g < 0) + { + throw new ArgumentOutOfRangeException("Invalid color code. Green part is not valid hexadecimal: " + rgba); + } + + b = ParseCol8(rgba, 4) / 255f; + + if (b < 0) + { + throw new ArgumentOutOfRangeException("Invalid color code. Blue part is not valid hexadecimal: " + rgba); + } + } + + /// <summary> + /// Returns a color constructed from integer red, green, blue, and alpha channels. + /// Each channel should have 8 bits of information ranging from 0 to 255. + /// </summary> + /// <param name="r8">The red component represented on the range of 0 to 255.</param> + /// <param name="g8">The green component represented on the range of 0 to 255.</param> + /// <param name="b8">The blue component represented on the range of 0 to 255.</param> + /// <param name="a8">The alpha (transparency) component represented on the range of 0 to 255.</param> + /// <returns>The constructed color.</returns> + public static Color Color8(byte r8, byte g8, byte b8, byte a8 = 255) + { + return new Color(r8 / 255f, g8 / 255f, b8 / 255f, a8 / 255f); + } + + /// <summary> + /// Returns a color according to the standardized name, with the + /// specified alpha value. Supported color names are the same as + /// the constants defined in <see cref="Colors"/>. + /// </summary> + /// <param name="name">The name of the color.</param> + /// <param name="alpha">The alpha (transparency) component represented on the range of 0 to 1. Default: 1.</param> + /// <returns>The constructed color.</returns> + public static Color ColorN(string name, float alpha = 1f) + { + name = name.Replace(" ", String.Empty); + name = name.Replace("-", String.Empty); + name = name.Replace("_", String.Empty); + name = name.Replace("'", String.Empty); + name = name.Replace(".", String.Empty); + name = name.ToLower(); + + if (!Colors.namedColors.ContainsKey(name)) + { + throw new ArgumentOutOfRangeException($"Invalid Color Name: {name}"); + } + + Color color = Colors.namedColors[name]; + color.a = alpha; + return color; + } + + /// <summary> + /// Constructs a color from an HSV profile, with values on the + /// range of 0 to 1. This is equivalent to using each of + /// the `h`/`s`/`v` properties, but much more efficient. + /// </summary> + /// <param name="hue">The HSV hue, typically on the range of 0 to 1.</param> + /// <param name="saturation">The HSV saturation, typically on the range of 0 to 1.</param> + /// <param name="value">The HSV value (brightness), typically on the range of 0 to 1.</param> + /// <param name="alpha">The alpha (transparency) value, typically on the range of 0 to 1.</param> + /// <returns>The constructed color.</returns> + public static Color FromHsv(float hue, float saturation, float value, float alpha = 1.0f) + { + if (saturation == 0) + { + // acp_hromatic (grey) + return new Color(value, value, value, alpha); + } + + int i; + float f, p, q, t; + + hue *= 6.0f; + hue %= 6f; + i = (int)hue; + + f = hue - i; + p = value * (1 - saturation); + q = value * (1 - saturation * f); + t = value * (1 - saturation * (1 - f)); + + switch (i) + { + case 0: // Red is the dominant color + return new Color(value, t, p, alpha); + case 1: // Green is the dominant color + return new Color(q, value, p, alpha); + case 2: + return new Color(p, value, t, alpha); + case 3: // Blue is the dominant color + return new Color(p, q, value, alpha); + case 4: + return new Color(t, p, value, alpha); + default: // (5) Red is the dominant color + return new Color(value, p, q, alpha); + } + } + + /// <summary> + /// Converts a color to HSV values. This is equivalent to using each of + /// the `h`/`s`/`v` properties, but much more efficient. + /// </summary> + /// <param name="hue">Output parameter for the HSV hue.</param> + /// <param name="saturation">Output parameter for the HSV saturation.</param> + /// <param name="value">Output parameter for the HSV value.</param> + public void ToHsv(out float hue, out float saturation, out float value) + { + float max = (float)Mathf.Max(r, Mathf.Max(g, b)); + float min = (float)Mathf.Min(r, Mathf.Min(g, b)); + + float delta = max - min; + + if (delta == 0) + { + hue = 0; + } + else + { + if (r == max) + { + hue = (g - b) / delta; // Between yellow & magenta + } + else if (g == max) + { + hue = 2 + (b - r) / delta; // Between cyan & yellow + } + else + { + hue = 4 + (r - g) / delta; // Between magenta & cyan + } + + hue /= 6.0f; + + if (hue < 0) + { + hue += 1.0f; + } + } + + saturation = max == 0 ? 0 : 1f - 1f * min / max; + value = max; + } + private static int ParseCol8(string str, int ofs) { int ig = 0; @@ -488,9 +780,13 @@ namespace Godot } if (i == 0) + { ig += v * 16; + } else + { ig += v; + } } return ig; @@ -508,9 +804,13 @@ namespace Godot int lv = v & 0xF; if (lv < 10) + { c = (char)('0' + lv); + } else + { c = (char)('a' + lv - 10); + } v >>= 4; ret = c + ret; @@ -522,10 +822,14 @@ namespace Godot internal static bool HtmlIsValid(string color) { if (color.Length == 0) + { return false; + } if (color[0] == '#') + { color = color.Substring(1, color.Length - 1); + } bool alpha; @@ -544,83 +848,27 @@ namespace Godot if (alpha) { if (ParseCol8(color, 0) < 0) + { return false; + } } int from = alpha ? 2 : 0; if (ParseCol8(color, from + 0) < 0) - return false; - if (ParseCol8(color, from + 2) < 0) - return false; - if (ParseCol8(color, from + 4) < 0) - return false; - - return true; - } - - public static Color Color8(byte r8, byte g8, byte b8, byte a8 = 255) - { - return new Color(r8 / 255f, g8 / 255f, b8 / 255f, a8 / 255f); - } - - public Color(string rgba) - { - if (rgba.Length == 0) { - r = 0f; - g = 0f; - b = 0f; - a = 1.0f; - return; - } - - if (rgba[0] == '#') - rgba = rgba.Substring(1); - - bool alpha; - - if (rgba.Length == 8) - { - alpha = true; - } - else if (rgba.Length == 6) - { - alpha = false; - } - else - { - throw new ArgumentOutOfRangeException("Invalid color code. Length is " + rgba.Length + " but a length of 6 or 8 is expected: " + rgba); + return false; } - - if (alpha) + if (ParseCol8(color, from + 2) < 0) { - a = ParseCol8(rgba, 0) / 255f; - - if (a < 0) - throw new ArgumentOutOfRangeException("Invalid color code. Alpha part is not valid hexadecimal: " + rgba); + return false; } - else + if (ParseCol8(color, from + 4) < 0) { - a = 1.0f; + return false; } - int from = alpha ? 2 : 0; - - r = ParseCol8(rgba, from + 0) / 255f; - - if (r < 0) - throw new ArgumentOutOfRangeException("Invalid color code. Red part is not valid hexadecimal: " + rgba); - - g = ParseCol8(rgba, from + 2) / 255f; - - if (g < 0) - throw new ArgumentOutOfRangeException("Invalid color code. Green part is not valid hexadecimal: " + rgba); - - b = ParseCol8(rgba, from + 4) / 255f; - - if (b < 0) - throw new ArgumentOutOfRangeException("Invalid color code. Blue part is not valid hexadecimal: " + rgba); + return true; } public static Color operator +(Color left, Color right) @@ -708,13 +956,13 @@ namespace Godot if (Mathf.IsEqualApprox(left.g, right.g)) { if (Mathf.IsEqualApprox(left.b, right.b)) + { return left.a < right.a; + } return left.b < right.b; } - return left.g < right.g; } - return left.r < right.r; } @@ -725,13 +973,13 @@ namespace Godot if (Mathf.IsEqualApprox(left.g, right.g)) { if (Mathf.IsEqualApprox(left.b, right.b)) + { return left.a > right.a; + } return left.b > right.b; } - return left.g > right.g; } - return left.r > right.r; } @@ -750,6 +998,12 @@ namespace Godot return r == other.r && g == other.g && b == other.b && a == other.a; } + /// <summary> + /// Returns true if this color and `other` are approximately equal, by running + /// <see cref="Godot.Mathf.IsEqualApprox(float, float)"/> on each component. + /// </summary> + /// <param name="other">The other color to compare.</param> + /// <returns>Whether or not the colors are approximately equal.</returns> public bool IsEqualApprox(Color other) { return Mathf.IsEqualApprox(r, other.r) && Mathf.IsEqualApprox(g, other.g) && Mathf.IsEqualApprox(b, other.b) && Mathf.IsEqualApprox(a, other.a); diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Colors.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Colors.cs index f41f5e9fc8..d05a0414aa 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Colors.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Colors.cs @@ -3,6 +3,10 @@ using System.Collections.Generic; namespace Godot { + /// <summary> + /// This class contains color constants created from standardized color names. + /// The standardized color set is based on the X11 and .NET color names. + /// </summary> public static class Colors { // Color names and values are derived from core/color_names.inc diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Mathf.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Mathf.cs index 4f7aa99df8..6eecc262d6 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Mathf.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Mathf.cs @@ -11,79 +11,185 @@ namespace Godot { // Define constants with Decimal precision and cast down to double or float. + /// <summary> + /// The circle constant, the circumference of the unit circle in radians. + /// </summary> public const real_t Tau = (real_t) 6.2831853071795864769252867666M; // 6.2831855f and 6.28318530717959 + + /// <summary> + /// Constant that represents how many times the diameter of a circle + /// fits around its perimeter. This is equivalent to `Mathf.Tau / 2`. + /// </summary> public const real_t Pi = (real_t) 3.1415926535897932384626433833M; // 3.1415927f and 3.14159265358979 + + /// <summary> + /// Positive infinity. For negative infinity, use `-Mathf.Inf`. + /// </summary> public const real_t Inf = real_t.PositiveInfinity; + + /// <summary> + /// "Not a Number", an invalid value. `NaN` has special properties, including + /// that it is not equal to itself. It is output by some invalid operations, + /// such as dividing zero by zero. + /// </summary> public const real_t NaN = real_t.NaN; private const real_t Deg2RadConst = (real_t) 0.0174532925199432957692369077M; // 0.0174532924f and 0.0174532925199433 private const real_t Rad2DegConst = (real_t) 57.295779513082320876798154814M; // 57.29578f and 57.2957795130823 + /// <summary> + /// Returns the absolute value of `s` (i.e. positive value). + /// </summary> + /// <param name="s">The input number.</param> + /// <returns>The absolute value of `s`.</returns> public static int Abs(int s) { return Math.Abs(s); } + /// <summary> + /// Returns the absolute value of `s` (i.e. positive value). + /// </summary> + /// <param name="s">The input number.</param> + /// <returns>The absolute value of `s`.</returns> public static real_t Abs(real_t s) { return Math.Abs(s); } + /// <summary> + /// Returns the arc cosine of `s` in radians. Use to get the angle of cosine s. + /// </summary> + /// <param name="s">The input cosine value. Must be on the range of -1.0 to 1.0.</param> + /// <returns>An angle that would result in the given cosine value. On the range `0` to `Tau/2`.</returns> public static real_t Acos(real_t s) { return (real_t)Math.Acos(s); } + /// <summary> + /// Returns the arc sine of `s` in radians. Use to get the angle of sine s. + /// </summary> + /// <param name="s">The input sine value. Must be on the range of -1.0 to 1.0.</param> + /// <returns>An angle that would result in the given sine value. On the range `-Tau/4` to `Tau/4`.</returns> public static real_t Asin(real_t s) { return (real_t)Math.Asin(s); } + /// <summary> + /// Returns the arc tangent of `s` in radians. Use to get the angle of tangent s. + /// + /// The method cannot know in which quadrant the angle should fall. + /// See <see cref="Atan2(real_t, real_t)"/> if you have both `y` and `x`. + /// </summary> + /// <param name="s">The input tangent value.</param> + /// <returns>An angle that would result in the given tangent value. On the range `-Tau/4` to `Tau/4`.</returns> public static real_t Atan(real_t s) { return (real_t)Math.Atan(s); } + /// <summary> + /// Returns the arc tangent of `y` and `x` in radians. Use to get the angle + /// of the tangent of `y/x`. To compute the value, the method takes into + /// account the sign of both arguments in order to determine the quadrant. + /// + /// Important note: The Y coordinate comes first, by convention. + /// </summary> + /// <param name="y">The Y coordinate of the point to find the angle to.</param> + /// <param name="x">The X coordinate of the point to find the angle to.</param> + /// <returns>An angle that would result in the given tangent value. On the range `-Tau/2` to `Tau/2`.</returns> public static real_t Atan2(real_t y, real_t x) { return (real_t)Math.Atan2(y, x); } + /// <summary> + /// Converts a 2D point expressed in the cartesian coordinate + /// system (X and Y axis) to the polar coordinate system + /// (a distance from the origin and an angle). + /// </summary> + /// <param name="x">The input X coordinate.</param> + /// <param name="y">The input Y coordinate.</param> + /// <returns>A <see cref="Vector2"/> with X representing the distance and Y representing the angle.</returns> public static Vector2 Cartesian2Polar(real_t x, real_t y) { return new Vector2(Sqrt(x * x + y * y), Atan2(y, x)); } + /// <summary> + /// Rounds `s` upward (towards positive infinity). + /// </summary> + /// <param name="s">The number to ceil.</param> + /// <returns>The smallest whole number that is not less than `s`.</returns> public static real_t Ceil(real_t s) { return (real_t)Math.Ceiling(s); } + /// <summary> + /// Clamps a `value` so that it is not less than `min` and not more than `max`. + /// </summary> + /// <param name="value">The value to clamp.</param> + /// <param name="min">The minimum allowed value.</param> + /// <param name="max">The maximum allowed value.</param> + /// <returns>The clamped value.</returns> public static int Clamp(int value, int min, int max) { return value < min ? min : value > max ? max : value; } + /// <summary> + /// Clamps a `value` so that it is not less than `min` and not more than `max`. + /// </summary> + /// <param name="value">The value to clamp.</param> + /// <param name="min">The minimum allowed value.</param> + /// <param name="max">The maximum allowed value.</param> + /// <returns>The clamped value.</returns> public static real_t Clamp(real_t value, real_t min, real_t max) { return value < min ? min : value > max ? max : value; } + /// <summary> + /// Returns the cosine of angle `s` in radians. + /// </summary> + /// <param name="s">The angle in radians.</param> + /// <returns>The cosine of that angle.</returns> public static real_t Cos(real_t s) { return (real_t)Math.Cos(s); } + /// <summary> + /// Returns the hyperbolic cosine of angle `s` in radians. + /// </summary> + /// <param name="s">The angle in radians.</param> + /// <returns>The hyperbolic cosine of that angle.</returns> public static real_t Cosh(real_t s) { return (real_t)Math.Cosh(s); } + /// <summary> + /// Converts an angle expressed in degrees to radians. + /// </summary> + /// <param name="deg">An angle expressed in degrees.</param> + /// <returns>The same angle expressed in radians.</returns> public static real_t Deg2Rad(real_t deg) { return deg * Deg2RadConst; } + /// <summary> + /// Easing function, based on exponent. The curve values are: + /// `0` is constant, `1` is linear, `0` to `1` is ease-in, `1` or more is ease-out. + /// Negative values are in-out/out-in. + /// </summary> + /// <param name="s">The value to ease.</param> + /// <param name="curve">`0` is constant, `1` is linear, `0` to `1` is ease-in, `1` or more is ease-out.</param> + /// <returns>The eased value.</returns> public static real_t Ease(real_t s, real_t curve) { if (s < 0f) @@ -118,21 +224,47 @@ namespace Godot return 0f; } + /// <summary> + /// The natural exponential function. It raises the mathematical + /// constant `e` to the power of `s` and returns it. + /// </summary> + /// <param name="s">The exponent to raise `e` to.</param> + /// <returns>`e` raised to the power of `s`.</returns> public static real_t Exp(real_t s) { return (real_t)Math.Exp(s); } + /// <summary> + /// Rounds `s` downward (towards negative infinity). + /// </summary> + /// <param name="s">The number to floor.</param> + /// <returns>The largest whole number that is not more than `s`.</returns> public static real_t Floor(real_t s) { return (real_t)Math.Floor(s); } + /// <summary> + /// Returns a normalized value considering the given range. + /// This is the opposite of <see cref="Lerp(real_t, real_t, real_t)"/>. + /// </summary> + /// <param name="from">The interpolated value.</param> + /// <param name="to">The destination value for interpolation.</param> + /// <param name="weight">A value on the range of 0.0 to 1.0, representing the amount of interpolation.</param> + /// <returns>The resulting value of the inverse interpolation.</returns> public static real_t InverseLerp(real_t from, real_t to, real_t weight) { return (weight - from) / (to - from); } + /// <summary> + /// Returns true if `a` and `b` are approximately equal to each other. + /// The comparison is done using a tolerance calculation with <see cref="Epsilon"/>. + /// </summary> + /// <param name="a">One of the values.</param> + /// <param name="b">The other value.</param> + /// <returns>A bool for whether or not the two values are approximately equal.</returns> public static bool IsEqualApprox(real_t a, real_t b) { // Check for exact equality first, required to handle "infinity" values. @@ -149,26 +281,62 @@ namespace Godot return Abs(a - b) < tolerance; } + /// <summary> + /// Returns whether `s` is an infinity value (either positive infinity or negative infinity). + /// </summary> + /// <param name="s">The value to check.</param> + /// <returns>A bool for whether or not the value is an infinity value.</returns> public static bool IsInf(real_t s) { return real_t.IsInfinity(s); } + /// <summary> + /// Returns whether `s` is a `NaN` ("Not a Number" or invalid) value. + /// </summary> + /// <param name="s">The value to check.</param> + /// <returns>A bool for whether or not the value is a `NaN` value.</returns> public static bool IsNaN(real_t s) { return real_t.IsNaN(s); } + /// <summary> + /// Returns true if `s` is approximately zero. + /// The comparison is done using a tolerance calculation with <see cref="Epsilon"/>. + /// + /// This method is faster than using <see cref="IsEqualApprox(real_t, real_t)"/> with one value as zero. + /// </summary> + /// <param name="s">The value to check.</param> + /// <returns>A bool for whether or not the value is nearly zero.</returns> public static bool IsZeroApprox(real_t s) { return Abs(s) < Epsilon; } + /// <summary> + /// Linearly interpolates between two values by a normalized value. + /// This is the opposite <see cref="InverseLerp(real_t, real_t, real_t)"/>. + /// </summary> + /// <param name="from">The start value for interpolation.</param> + /// <param name="to">The destination value for interpolation.</param> + /// <param name="weight">A value on the range of 0.0 to 1.0, representing the amount of interpolation.</param> + /// <returns>The resulting value of the interpolation.</returns> public static real_t Lerp(real_t from, real_t to, real_t weight) { return from + (to - from) * weight; } + /// <summary> + /// Linearly interpolates between two angles (in radians) by a normalized value. + /// + /// Similar to <see cref="Lerp(real_t, real_t, real_t)"/>, + /// but interpolates correctly when the angles wrap around <see cref="Tau"/>. + /// </summary> + /// <param name="from">The start angle for interpolation.</param> + /// <param name="to">The destination angle for interpolation.</param> + /// <param name="weight">A value on the range of 0.0 to 1.0, representing the amount of interpolation.</param> + /// <returns>The resulting angle of the interpolation.</returns> public static real_t LerpAngle(real_t from, real_t to, real_t weight) { real_t difference = (to - from) % Mathf.Tau; @@ -176,36 +344,81 @@ namespace Godot return from + distance * weight; } + /// <summary> + /// Natural logarithm. The amount of time needed to reach a certain level of continuous growth. + /// + /// Note: This is not the same as the "log" function on most calculators, which uses a base 10 logarithm. + /// </summary> + /// <param name="s">The input value.</param> + /// <returns>The natural log of `s`.</returns> public static real_t Log(real_t s) { return (real_t)Math.Log(s); } + /// <summary> + /// Returns the maximum of two values. + /// </summary> + /// <param name="a">One of the values.</param> + /// <param name="b">The other value.</param> + /// <returns>Whichever of the two values is higher.</returns> public static int Max(int a, int b) { return a > b ? a : b; } + /// <summary> + /// Returns the maximum of two values. + /// </summary> + /// <param name="a">One of the values.</param> + /// <param name="b">The other value.</param> + /// <returns>Whichever of the two values is higher.</returns> public static real_t Max(real_t a, real_t b) { return a > b ? a : b; } + /// <summary> + /// Returns the minimum of two values. + /// </summary> + /// <param name="a">One of the values.</param> + /// <param name="b">The other value.</param> + /// <returns>Whichever of the two values is lower.</returns> public static int Min(int a, int b) { return a < b ? a : b; } + /// <summary> + /// Returns the minimum of two values. + /// </summary> + /// <param name="a">One of the values.</param> + /// <param name="b">The other value.</param> + /// <returns>Whichever of the two values is lower.</returns> public static real_t Min(real_t a, real_t b) { return a < b ? a : b; } + /// <summary> + /// Moves `from` toward `to` by the `delta` value. + /// + /// Use a negative delta value to move away. + /// </summary> + /// <param name="from">The start value.</param> + /// <param name="to">The value to move towards.</param> + /// <param name="delta">The amount to move by.</param> + /// <returns>The value after moving.</returns> public static real_t MoveToward(real_t from, real_t to, real_t delta) { return Abs(to - from) <= delta ? to : from + Sign(to - from) * delta; } + /// <summary> + /// Returns the nearest larger power of 2 for the integer `value`. + /// </summary> + /// <param name="value">The input value.</param> + /// <returns>The nearest larger power of 2.</returns> public static int NearestPo2(int value) { value--; @@ -218,14 +431,25 @@ namespace Godot return value; } + /// <summary> + /// Converts a 2D point expressed in the polar coordinate + /// system (a distance from the origin `r` and an angle `th`) + /// to the cartesian coordinate system (X and Y axis). + /// </summary> + /// <param name="r">The distance from the origin.</param> + /// <param name="th">The angle of the point.</param> + /// <returns>A <see cref="Vector2"/> representing the cartesian coordinate.</returns> public static Vector2 Polar2Cartesian(real_t r, real_t th) { return new Vector2(r * Cos(th), r * Sin(th)); } /// <summary> - /// Performs a canonical Modulus operation, where the output is on the range [0, b). + /// Performs a canonical Modulus operation, where the output is on the range `[0, b)`. /// </summary> + /// <param name="a">The dividend, the primary input.</param> + /// <param name="b">The divisor. The output is on the range `[0, b)`.</param> + /// <returns>The resulting output.</returns> public static int PosMod(int a, int b) { int c = a % b; @@ -237,8 +461,11 @@ namespace Godot } /// <summary> - /// Performs a canonical Modulus operation, where the output is on the range [0, b). + /// Performs a canonical Modulus operation, where the output is on the range `[0, b)`. /// </summary> + /// <param name="a">The dividend, the primary input.</param> + /// <param name="b">The divisor. The output is on the range `[0, b)`.</param> + /// <returns>The resulting output.</returns> public static real_t PosMod(real_t a, real_t b) { real_t c = a % b; @@ -249,43 +476,89 @@ namespace Godot return c; } + /// <summary> + /// Returns the result of `x` raised to the power of `y`. + /// </summary> + /// <param name="x">The base.</param> + /// <param name="y">The exponent.</param> + /// <returns>`x` raised to the power of `y`.</returns> public static real_t Pow(real_t x, real_t y) { return (real_t)Math.Pow(x, y); } + /// <summary> + /// Converts an angle expressed in radians to degrees. + /// </summary> + /// <param name="rad">An angle expressed in radians.</param> + /// <returns>The same angle expressed in degrees.</returns> public static real_t Rad2Deg(real_t rad) { return rad * Rad2DegConst; } + /// <summary> + /// Rounds `s` to the nearest whole number, + /// with halfway cases rounded towards the nearest multiple of two. + /// </summary> + /// <param name="s">The number to round.</param> + /// <returns>The rounded number.</returns> public static real_t Round(real_t s) { return (real_t)Math.Round(s); } + /// <summary> + /// Returns the sign of `s`: `-1` or `1`. Returns `0` if `s` is `0`. + /// </summary> + /// <param name="s">The input number.</param> + /// <returns>One of three possible values: `1`, `-1`, or `0`.</returns> public static int Sign(int s) { if (s == 0) return 0; return s < 0 ? -1 : 1; } + /// <summary> + /// Returns the sign of `s`: `-1` or `1`. Returns `0` if `s` is `0`. + /// </summary> + /// <param name="s">The input number.</param> + /// <returns>One of three possible values: `1`, `-1`, or `0`.</returns> public static int Sign(real_t s) { if (s == 0) return 0; return s < 0 ? -1 : 1; } + /// <summary> + /// Returns the sine of angle `s` in radians. + /// </summary> + /// <param name="s">The angle in radians.</param> + /// <returns>The sine of that angle.</returns> public static real_t Sin(real_t s) { return (real_t)Math.Sin(s); } + /// <summary> + /// Returns the hyperbolic sine of angle `s` in radians. + /// </summary> + /// <param name="s">The angle in radians.</param> + /// <returns>The hyperbolic sine of that angle.</returns> public static real_t Sinh(real_t s) { return (real_t)Math.Sinh(s); } + /// <summary> + /// Returns a number smoothly interpolated between `from` and `to`, + /// based on the `weight`. Similar to <see cref="Lerp(real_t, real_t, real_t)"/>, + /// but interpolates faster at the beginning and slower at the end. + /// </summary> + /// <param name="from">The start value for interpolation.</param> + /// <param name="to">The destination value for interpolation.</param> + /// <param name="weight">A value representing the amount of interpolation.</param> + /// <returns>The resulting value of the interpolation.</returns> public static real_t SmoothStep(real_t from, real_t to, real_t weight) { if (IsEqualApprox(from, to)) @@ -296,11 +569,25 @@ namespace Godot return x * x * (3 - 2 * x); } + /// <summary> + /// Returns the square root of `s`, where `s` is a non-negative number. + /// + /// If you need negative inputs, use `System.Numerics.Complex`. + /// </summary> + /// <param name="s">The input number. Must not be negative.</param> + /// <returns>The square root of `s`.</returns> public static real_t Sqrt(real_t s) { return (real_t)Math.Sqrt(s); } + /// <summary> + /// Returns the position of the first non-zero digit, after the + /// decimal point. Note that the maximum return value is 10, + /// which is a design decision in the implementation. + /// </summary> + /// <param name="step">The input value.</param> + /// <returns>The position of the first non-zero digit.</returns> public static int StepDecimals(real_t step) { double[] sd = new double[] { @@ -326,32 +613,68 @@ namespace Godot return 0; } + /// <summary> + /// Snaps float value `s` to a given `step`. + /// This can also be used to round a floating point + /// number to an arbitrary number of decimals. + /// </summary> + /// <param name="s">The value to stepify.</param> + /// <param name="step">The step size to snap to.</param> + /// <returns></returns> public static real_t Stepify(real_t s, real_t step) { if (step != 0f) { - s = Floor(s / step + 0.5f) * step; + return Floor(s / step + 0.5f) * step; } return s; } + /// <summary> + /// Returns the tangent of angle `s` in radians. + /// </summary> + /// <param name="s">The angle in radians.</param> + /// <returns>The tangent of that angle.</returns> public static real_t Tan(real_t s) { return (real_t)Math.Tan(s); } + /// <summary> + /// Returns the hyperbolic tangent of angle `s` in radians. + /// </summary> + /// <param name="s">The angle in radians.</param> + /// <returns>The hyperbolic tangent of that angle.</returns> public static real_t Tanh(real_t s) { return (real_t)Math.Tanh(s); } + /// <summary> + /// Wraps `value` between `min` and `max`. Usable for creating loop-alike + /// behavior or infinite surfaces. If `min` is `0`, this is equivalent + /// to <see cref="PosMod(int, int)"/>, so prefer using that instead. + /// </summary> + /// <param name="value">The value to wrap.</param> + /// <param name="min">The minimum allowed value and lower bound of the range.</param> + /// <param name="max">The maximum allowed value and upper bound of the range.</param> + /// <returns>The wrapped value.</returns> public static int Wrap(int value, int min, int max) { int range = max - min; return range == 0 ? min : min + ((value - min) % range + range) % range; } + /// <summary> + /// Wraps `value` between `min` and `max`. Usable for creating loop-alike + /// behavior or infinite surfaces. If `min` is `0`, this is equivalent + /// to <see cref="PosMod(real_t, real_t)"/>, so prefer using that instead. + /// </summary> + /// <param name="value">The value to wrap.</param> + /// <param name="min">The minimum allowed value and lower bound of the range.</param> + /// <param name="max">The maximum allowed value and upper bound of the range.</param> + /// <returns>The wrapped value.</returns> public static real_t Wrap(real_t value, real_t min, real_t max) { real_t range = max - min; diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/MathfEx.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/MathfEx.cs index 1b7fd4906f..c2f4701b5f 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/MathfEx.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/MathfEx.cs @@ -12,40 +12,89 @@ namespace Godot { // Define constants with Decimal precision and cast down to double or float. + /// <summary> + /// The natural number `e`. + /// </summary> public const real_t E = (real_t) 2.7182818284590452353602874714M; // 2.7182817f and 2.718281828459045 + + /// <summary> + /// The square root of 2. + /// </summary> public const real_t Sqrt2 = (real_t) 1.4142135623730950488016887242M; // 1.4142136f and 1.414213562373095 + /// <summary> + /// A very small number used for float comparison with error tolerance. + /// 1e-06 with single-precision floats, but 1e-14 if `REAL_T_IS_DOUBLE`. + /// </summary> #if REAL_T_IS_DOUBLE public const real_t Epsilon = 1e-14; // Epsilon size should depend on the precision used. #else public const real_t Epsilon = 1e-06f; #endif + /// <summary> + /// Returns the amount of digits after the decimal place. + /// </summary> + /// <param name="s">The input value.</param> + /// <returns>The amount of digits.</returns> public static int DecimalCount(real_t s) { return DecimalCount((decimal)s); } + /// <summary> + /// Returns the amount of digits after the decimal place. + /// </summary> + /// <param name="s">The input <see cref="System.Decimal"/> value.</param> + /// <returns>The amount of digits.</returns> public static int DecimalCount(decimal s) { return BitConverter.GetBytes(decimal.GetBits(s)[3])[2]; } + /// <summary> + /// Rounds `s` upward (towards positive infinity). + /// + /// This is the same as <see cref="Ceil(real_t)"/>, but returns an `int`. + /// </summary> + /// <param name="s">The number to ceil.</param> + /// <returns>The smallest whole number that is not less than `s`.</returns> public static int CeilToInt(real_t s) { return (int)Math.Ceiling(s); } + /// <summary> + /// Rounds `s` downward (towards negative infinity). + /// + /// This is the same as <see cref="Floor(real_t)"/>, but returns an `int`. + /// </summary> + /// <param name="s">The number to floor.</param> + /// <returns>The largest whole number that is not more than `s`.</returns> public static int FloorToInt(real_t s) { return (int)Math.Floor(s); } + /// <summary> + /// + /// </summary> + /// <param name="s"></param> + /// <returns></returns> public static int RoundToInt(real_t s) { return (int)Math.Round(s); } + /// <summary> + /// Returns true if `a` and `b` are approximately equal to each other. + /// The comparison is done using the provided tolerance value. + /// If you want the tolerance to be calculated for you, use <see cref="IsEqualApprox(real_t, real_t)"/>. + /// </summary> + /// <param name="a">One of the values.</param> + /// <param name="b">The other value.</param> + /// <param name="tolerance">The pre-calculated tolerance value.</param> + /// <returns>A bool for whether or not the two values are equal.</returns> public static bool IsEqualApprox(real_t a, real_t b, real_t tolerance) { // Check for exact equality first, required to handle "infinity" values. diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Plane.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Plane.cs index 885845e3a4..af0d474bae 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Plane.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Plane.cs @@ -8,18 +8,32 @@ using real_t = System.Single; namespace Godot { + /// <summary> + /// Plane represents a normalized plane equation. + /// "Over" or "Above" the plane is considered the side of + /// the plane towards where the normal is pointing. + /// </summary> [Serializable] [StructLayout(LayoutKind.Sequential)] public struct Plane : IEquatable<Plane> { private Vector3 _normal; + /// <summary> + /// The normal of the plane (in the plane equation: a, b, and c). + /// The normal vector must be normalized. + /// </summary> + /// <value>Equivalent to `x`, `y`, and `z`.</value> public Vector3 Normal { get { return _normal; } set { _normal = value; } } + /// <summary> + /// The X component of the plane's normal vector. + /// </summary> + /// <value>Equivalent to <see cref="Normal"/>'s X value.</value> public real_t x { get @@ -32,6 +46,10 @@ namespace Godot } } + /// <summary> + /// The Y component of the plane's normal vector. + /// </summary> + /// <value>Equivalent to <see cref="Normal"/>'s Y value.</value> public real_t y { get @@ -44,6 +62,10 @@ namespace Godot } } + /// <summary> + /// The Z component of the plane's normal vector. + /// </summary> + /// <value>Equivalent to <see cref="Normal"/>'s Z value.</value> public real_t z { get @@ -56,38 +78,68 @@ namespace Godot } } + /// <summary> + /// The distance from the origin to the plane (in the direction of + /// <see cref="Normal"/>). This value is typically non-negative. + /// </summary> + /// <value>The plane's distance from the origin.</value> public real_t D { get; set; } + /// <summary> + /// The center of the plane, the point where the normal line intersects the plane. + /// </summary> + /// <value>Equivalent to <see cref="Normal"/> multiplied by `D`.</value> public Vector3 Center { get { return _normal * D; } + set + { + _normal = value.Normalized(); + D = value.Length(); + } } + /// <summary> + /// Returns the shortest distance from this plane to the position `point`. + /// </summary> + /// <param name="point">The position to use for the calcualtion.</param> + /// <returns>The shortest distance.</returns> public real_t DistanceTo(Vector3 point) { return _normal.Dot(point) - D; } - public Vector3 GetAnyPoint() - { - return _normal * D; - } - + /// <summary> + /// Returns true if point is inside the plane. + /// Comparison uses a custom minimum epsilon threshold. + /// </summary> + /// <param name="point">The point to check.</param> + /// <param name="epsilon">The tolerance threshold.</param> + /// <returns>A bool for whether or not the plane has the point.</returns> public bool HasPoint(Vector3 point, real_t epsilon = Mathf.Epsilon) { real_t dist = _normal.Dot(point) - D; return Mathf.Abs(dist) <= epsilon; } + /// <summary> + /// Returns the intersection point of the three planes: `b`, `c`, + /// and this plane. If no intersection is found, `null` is returned. + /// </summary> + /// <param name="b">One of the three planes to use in the calculation.</param> + /// <param name="c">One of the three planes to use in the calculation.</param> + /// <returns>The intersection, or `null` if none is found.</returns> public Vector3? Intersect3(Plane b, Plane c) { real_t denom = _normal.Cross(b._normal).Dot(c._normal); if (Mathf.IsZeroApprox(denom)) + { return null; + } Vector3 result = b._normal.Cross(c._normal) * D + c._normal.Cross(_normal) * b.D + @@ -96,54 +148,94 @@ namespace Godot return result / denom; } + /// <summary> + /// Returns the intersection point of a ray consisting of the + /// position `from` and the direction normal `dir` with this plane. + /// If no intersection is found, `null` is returned. + /// </summary> + /// <param name="from">The start of the ray.</param> + /// <param name="dir">The direction of the ray, normalized.</param> + /// <returns>The intersection, or `null` if none is found.</returns> public Vector3? IntersectRay(Vector3 from, Vector3 dir) { real_t den = _normal.Dot(dir); if (Mathf.IsZeroApprox(den)) + { return null; + } real_t dist = (_normal.Dot(from) - D) / den; // This is a ray, before the emitting pos (from) does not exist if (dist > Mathf.Epsilon) + { return null; + } return from + dir * -dist; } + /// <summary> + /// Returns the intersection point of a line segment from + /// position `begin` to position `end` with this plane. + /// If no intersection is found, `null` is returned. + /// </summary> + /// <param name="begin">The start of the line segment.</param> + /// <param name="end">The end of the line segment.</param> + /// <returns>The intersection, or `null` if none is found.</returns> public Vector3? IntersectSegment(Vector3 begin, Vector3 end) { Vector3 segment = begin - end; real_t den = _normal.Dot(segment); if (Mathf.IsZeroApprox(den)) + { return null; + } real_t dist = (_normal.Dot(begin) - D) / den; // Only allow dist to be in the range of 0 to 1, with tolerance. if (dist < -Mathf.Epsilon || dist > 1.0f + Mathf.Epsilon) + { return null; + } return begin + segment * -dist; } + /// <summary> + /// Returns true if `point` is located above the plane. + /// </summary> + /// <param name="point">The point to check.</param> + /// <returns>A bool for whether or not the point is above the plane.</returns> public bool IsPointOver(Vector3 point) { return _normal.Dot(point) > D; } + /// <summary> + /// Returns the plane scaled to unit length. + /// </summary> + /// <returns>A normalized version of the plane.</returns> public Plane Normalized() { real_t len = _normal.Length(); if (len == 0) + { return new Plane(0, 0, 0, 0); + } return new Plane(_normal / len, D / len); } + /// <summary> + /// Returns the orthogonal projection of `point` into the plane. + /// </summary> + /// <param name="point">The point to project.</param> + /// <returns>The projected point.</returns> public Vector3 Project(Vector3 point) { return point - _normal * DistanceTo(point); @@ -154,22 +246,56 @@ namespace Godot private static readonly Plane _planeXZ = new Plane(0, 1, 0, 0); private static readonly Plane _planeXY = new Plane(0, 0, 1, 0); + /// <summary> + /// A plane that extends in the Y and Z axes (normal vector points +X). + /// </summary> + /// <value>Equivalent to `new Plane(1, 0, 0, 0)`.</value> public static Plane PlaneYZ { get { return _planeYZ; } } + + /// <summary> + /// A plane that extends in the X and Z axes (normal vector points +Y). + /// </summary> + /// <value>Equivalent to `new Plane(0, 1, 0, 0)`.</value> public static Plane PlaneXZ { get { return _planeXZ; } } + + /// <summary> + /// A plane that extends in the X and Y axes (normal vector points +Z). + /// </summary> + /// <value>Equivalent to `new Plane(0, 0, 1, 0)`.</value> public static Plane PlaneXY { get { return _planeXY; } } - // Constructors + /// <summary> + /// Constructs a plane from four values. `a`, `b` and `c` become the + /// components of the resulting plane's <see cref="Normal"/> vector. + /// `d` becomes the plane's distance from the origin. + /// </summary> + /// <param name="a">The X component of the plane's normal vector.</param> + /// <param name="b">The Y component of the plane's normal vector.</param> + /// <param name="c">The Z component of the plane's normal vector.</param> + /// <param name="d">The plane's distance from the origin. This value is typically non-negative.</param> public Plane(real_t a, real_t b, real_t c, real_t d) { _normal = new Vector3(a, b, c); this.D = d; } + + /// <summary> + /// Constructs a plane from a normal vector and the plane's distance to the origin. + /// </summary> + /// <param name="normal">The normal of the plane, must be normalized.</param> + /// <param name="d">The plane's distance from the origin. This value is typically non-negative.</param> public Plane(Vector3 normal, real_t d) { this._normal = normal; this.D = d; } + /// <summary> + /// Constructs a plane from the three points, given in clockwise order. + /// </summary> + /// <param name="v1">The first point.</param> + /// <param name="v2">The second point.</param> + /// <param name="v3">The third point.</param> public Plane(Vector3 v1, Vector3 v2, Vector3 v3) { _normal = (v1 - v3).Cross(v1 - v2); @@ -207,6 +333,12 @@ namespace Godot return _normal == other._normal && D == other.D; } + /// <summary> + /// Returns true if this plane and `other` are approximately equal, by running + /// <see cref="Mathf.IsEqualApprox(real_t, real_t)"/> on each component. + /// </summary> + /// <param name="other">The other plane to compare.</param> + /// <returns>Whether or not the planes are approximately equal.</returns> public bool IsEqualApprox(Plane other) { return _normal.IsEqualApprox(other._normal) && Mathf.IsEqualApprox(D, other.D); diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Quat.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Quat.cs index bbc617ea6e..7c978801bd 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Quat.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Quat.cs @@ -8,15 +8,51 @@ using real_t = System.Single; namespace Godot { + /// <summary> + /// A unit quaternion used for representing 3D rotations. + /// Quaternions need to be normalized to be used for rotation. + /// + /// It is similar to Basis, which implements matrix representation of + /// rotations, and can be parametrized using both an axis-angle pair + /// or Euler angles. Basis stores rotation, scale, and shearing, + /// while Quat only stores rotation. + /// + /// Due to its compactness and the way it is stored in memory, certain + /// operations (obtaining axis-angle and performing SLERP, in particular) + /// are more efficient and robust against floating-point errors. + /// </summary> [Serializable] [StructLayout(LayoutKind.Sequential)] public struct Quat : IEquatable<Quat> { + /// <summary> + /// X component of the quaternion (imaginary `i` axis part). + /// Quaternion components should usually not be manipulated directly. + /// </summary> public real_t x; + + /// <summary> + /// Y component of the quaternion (imaginary `j` axis part). + /// Quaternion components should usually not be manipulated directly. + /// </summary> public real_t y; + + /// <summary> + /// Z component of the quaternion (imaginary `k` axis part). + /// Quaternion components should usually not be manipulated directly. + /// </summary> public real_t z; + + /// <summary> + /// W component of the quaternion (real part). + /// Quaternion components should usually not be manipulated directly. + /// </summary> public real_t w; + /// <summary> + /// Access quaternion components using their index. + /// </summary> + /// <value>`[0]` is equivalent to `.x`, `[1]` is equivalent to `.y`, `[2]` is equivalent to `.z`, `[3]` is equivalent to `.w`.</value> public real_t this[int index] { get @@ -57,16 +93,35 @@ namespace Godot } } + /// <summary> + /// Returns the length (magnitude) of the quaternion. + /// </summary> + /// <value>Equivalent to `Mathf.Sqrt(LengthSquared)`.</value> public real_t Length { get { return Mathf.Sqrt(LengthSquared); } } + /// <summary> + /// Returns the squared length (squared magnitude) of the quaternion. + /// This method runs faster than <see cref="Length"/>, so prefer it if + /// you need to compare quaternions or need the squared length for some formula. + /// </summary> + /// <value>Equivalent to `Dot(this)`.</value> public real_t LengthSquared { get { return Dot(this); } } + /// <summary> + /// Performs a cubic spherical interpolation between quaternions `preA`, + /// this vector, `b`, and `postB`, by the given amount `t`. + /// </summary> + /// <param name="b">The destination quaternion.</param> + /// <param name="preA">A quaternion before this quaternion.</param> + /// <param name="postB">A quaternion after `b`.</param> + /// <param name="t">A value on the range of 0.0 to 1.0, representing the amount of interpolation.</param> + /// <returns>The interpolated quaternion.</returns> public Quat CubicSlerp(Quat b, Quat preA, Quat postB, real_t t) { real_t t2 = (1.0f - t) * t * 2f; @@ -75,85 +130,131 @@ namespace Godot return sp.Slerpni(sq, t2); } + /// <summary> + /// Returns the dot product of two quaternions. + /// </summary> + /// <param name="b">The other quaternion.</param> + /// <returns>The dot product.</returns> public real_t Dot(Quat b) { return x * b.x + y * b.y + z * b.z + w * b.w; } + /// <summary> + /// Returns Euler angles (in the YXZ convention: when decomposing, + /// first Z, then X, and Y last) corresponding to the rotation + /// represented by the unit quaternion. Returned vector contains + /// the rotation angles in the format (X angle, Y angle, Z angle). + /// </summary> + /// <returns>The Euler angle representation of this quaternion.</returns> public Vector3 GetEuler() { #if DEBUG if (!IsNormalized()) + { throw new InvalidOperationException("Quat is not normalized"); + } #endif var basis = new Basis(this); return basis.GetEuler(); } + /// <summary> + /// Returns the inverse of the quaternion. + /// </summary> + /// <returns>The inverse quaternion.</returns> public Quat Inverse() { #if DEBUG if (!IsNormalized()) + { throw new InvalidOperationException("Quat is not normalized"); + } #endif return new Quat(-x, -y, -z, w); } + /// <summary> + /// Returns whether the quaternion is normalized or not. + /// </summary> + /// <returns>A bool for whether the quaternion is normalized or not.</returns> + public bool IsNormalized() + { + return Mathf.Abs(LengthSquared - 1) <= Mathf.Epsilon; + } + + /// <summary> + /// Returns a copy of the quaternion, normalized to unit length. + /// </summary> + /// <returns>The normalized quaternion.</returns> public Quat Normalized() { return this / Length; } - public Quat Slerp(Quat b, real_t t) + /// <summary> + /// Returns the result of the spherical linear interpolation between + /// this quaternion and `to` by amount `weight`. + /// + /// Note: Both quaternions must be normalized. + /// </summary> + /// <param name="to">The destination quaternion for interpolation. Must be normalized.</param> + /// <param name="weight">A value on the range of 0.0 to 1.0, representing the amount of interpolation.</param> + /// <returns>The resulting quaternion of the interpolation.</returns> + public Quat Slerp(Quat to, real_t weight) { #if DEBUG if (!IsNormalized()) + { throw new InvalidOperationException("Quat is not normalized"); - if (!b.IsNormalized()) - throw new ArgumentException("Argument is not normalized", nameof(b)); + } + if (!to.IsNormalized()) + { + throw new ArgumentException("Argument is not normalized", nameof(to)); + } #endif - // Calculate cosine - real_t cosom = x * b.x + y * b.y + z * b.z + w * b.w; + // Calculate cosine. + real_t cosom = x * to.x + y * to.y + z * to.z + w * to.w; var to1 = new Quat(); - // Adjust signs if necessary + // Adjust signs if necessary. if (cosom < 0.0) { cosom = -cosom; - to1.x = -b.x; - to1.y = -b.y; - to1.z = -b.z; - to1.w = -b.w; + to1.x = -to.x; + to1.y = -to.y; + to1.z = -to.z; + to1.w = -to.w; } else { - to1.x = b.x; - to1.y = b.y; - to1.z = b.z; - to1.w = b.w; + to1.x = to.x; + to1.y = to.y; + to1.z = to.z; + to1.w = to.w; } real_t sinom, scale0, scale1; - // Calculate coefficients + // Calculate coefficients. if (1.0 - cosom > Mathf.Epsilon) { - // Standard case (Slerp) + // Standard case (Slerp). real_t omega = Mathf.Acos(cosom); sinom = Mathf.Sin(omega); - scale0 = Mathf.Sin((1.0f - t) * omega) / sinom; - scale1 = Mathf.Sin(t * omega) / sinom; + scale0 = Mathf.Sin((1.0f - weight) * omega) / sinom; + scale1 = Mathf.Sin(weight * omega) / sinom; } else { - // Quaternions are very close so we can do a linear interpolation - scale0 = 1.0f - t; - scale1 = t; + // Quaternions are very close so we can do a linear interpolation. + scale0 = 1.0f - weight; + scale1 = weight; } - // Calculate final values + // Calculate final values. return new Quat ( scale0 * x + scale1 * to1.x, @@ -163,9 +264,17 @@ namespace Godot ); } - public Quat Slerpni(Quat b, real_t t) + /// <summary> + /// Returns the result of the spherical linear interpolation between + /// this quaternion and `to` by amount `weight`, but without + /// checking if the rotation path is not bigger than 90 degrees. + /// </summary> + /// <param name="to">The destination quaternion for interpolation. Must be normalized.</param> + /// <param name="weight">A value on the range of 0.0 to 1.0, representing the amount of interpolation.</param> + /// <returns>The resulting quaternion of the interpolation.</returns> + public Quat Slerpni(Quat to, real_t weight) { - real_t dot = Dot(b); + real_t dot = Dot(to); if (Mathf.Abs(dot) > 0.9999f) { @@ -174,33 +283,54 @@ namespace Godot real_t theta = Mathf.Acos(dot); real_t sinT = 1.0f / Mathf.Sin(theta); - real_t newFactor = Mathf.Sin(t * theta) * sinT; - real_t invFactor = Mathf.Sin((1.0f - t) * theta) * sinT; + real_t newFactor = Mathf.Sin(weight * theta) * sinT; + real_t invFactor = Mathf.Sin((1.0f - weight) * theta) * sinT; return new Quat ( - invFactor * x + newFactor * b.x, - invFactor * y + newFactor * b.y, - invFactor * z + newFactor * b.z, - invFactor * w + newFactor * b.w + invFactor * x + newFactor * to.x, + invFactor * y + newFactor * to.y, + invFactor * z + newFactor * to.z, + invFactor * w + newFactor * to.w ); } + /// <summary> + /// Returns a vector transformed (multiplied) by this quaternion. + /// </summary> + /// <param name="v">A vector to transform.</param> + /// <returns>The transfomed vector.</returns> public Vector3 Xform(Vector3 v) { #if DEBUG if (!IsNormalized()) + { throw new InvalidOperationException("Quat is not normalized"); + } #endif var u = new Vector3(x, y, z); Vector3 uv = u.Cross(v); return v + ((uv * w) + u.Cross(uv)) * 2; } - // Static Readonly Properties - public static Quat Identity { get; } = new Quat(0f, 0f, 0f, 1f); - - // Constructors + // Constants + private static readonly Quat _identity = new Quat(0, 0, 0, 1); + + /// <summary> + /// The identity quaternion, representing no rotation. + /// Equivalent to an identity <see cref="Basis"/> matrix. If a vector is transformed by + /// an identity quaternion, it will not change. + /// </summary> + /// <value>Equivalent to `new Quat(0, 0, 0, 1)`.</value> + public static Quat Identity { get { return _identity; } } + + /// <summary> + /// Constructs a quaternion defined by the given values. + /// </summary> + /// <param name="x">X component of the quaternion (imaginary `i` axis part).</param> + /// <param name="y">Y component of the quaternion (imaginary `j` axis part).</param> + /// <param name="z">Z component of the quaternion (imaginary `k` axis part).</param> + /// <param name="w">W component of the quaternion (real part).</param> public Quat(real_t x, real_t y, real_t z, real_t w) { this.x = x; @@ -209,21 +339,31 @@ namespace Godot this.w = w; } - public bool IsNormalized() - { - return Mathf.Abs(LengthSquared - 1) <= Mathf.Epsilon; - } - + /// <summary> + /// Constructs a quaternion from the given quaternion. + /// </summary> + /// <param name="q">The existing quaternion.</param> public Quat(Quat q) { this = q; } + /// <summary> + /// Constructs a quaternion from the given <see cref="Basis"/>. + /// </summary> + /// <param name="basis">The basis to construct from.</param> public Quat(Basis basis) { this = basis.Quat(); } + /// <summary> + /// Constructs a quaternion that will perform a rotation specified by + /// Euler angles (in the YXZ convention: when decomposing, + /// first Z, then X, and Y last), + /// given in the vector format as (X angle, Y angle, Z angle). + /// </summary> + /// <param name="eulerYXZ"></param> public Quat(Vector3 eulerYXZ) { real_t half_a1 = eulerYXZ.y * 0.5f; @@ -247,11 +387,19 @@ namespace Godot w = sin_a1 * sin_a2 * sin_a3 + cos_a1 * cos_a2 * cos_a3; } + /// <summary> + /// Constructs a quaternion that will rotate around the given axis + /// by the specified angle. The axis must be a normalized vector. + /// </summary> + /// <param name="axis">The axis to rotate around. Must be normalized.</param> + /// <param name="angle">The angle to rotate, in radians.</param> public Quat(Vector3 axis, real_t angle) { #if DEBUG if (!axis.IsNormalized()) + { throw new ArgumentException("Argument is not normalized", nameof(axis)); + } #endif real_t d = axis.Length(); @@ -364,6 +512,12 @@ namespace Godot return x == other.x && y == other.y && z == other.z && w == other.w; } + /// <summary> + /// Returns true if this quaternion and `other` are approximately equal, by running + /// <see cref="Mathf.IsEqualApprox(real_t, real_t)"/> on each component. + /// </summary> + /// <param name="other">The other quaternion to compare.</param> + /// <returns>Whether or not the quaternions are approximately equal.</returns> public bool IsEqualApprox(Quat other) { return Mathf.IsEqualApprox(x, other.x) && Mathf.IsEqualApprox(y, other.y) && Mathf.IsEqualApprox(z, other.z) && Mathf.IsEqualApprox(w, other.w); diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs index 1098ffe4e5..f7703c77cc 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs @@ -8,6 +8,10 @@ using real_t = System.Single; namespace Godot { + /// <summary> + /// 2D axis-aligned bounding box. Rect2 consists of a position, a size, and + /// several utility functions. It is typically used for fast overlap tests. + /// </summary> [Serializable] [StructLayout(LayoutKind.Sequential)] public struct Rect2 : IEquatable<Rect2> @@ -15,29 +19,52 @@ namespace Godot private Vector2 _position; private Vector2 _size; + /// <summary> + /// Beginning corner. Typically has values lower than End. + /// </summary> + /// <value>Directly uses a private field.</value> public Vector2 Position { get { return _position; } set { _position = value; } } + /// <summary> + /// Size from Position to End. Typically all components are positive. + /// If the size is negative, you can use <see cref="Abs"/> to fix it. + /// </summary> + /// <value>Directly uses a private field.</value> public Vector2 Size { get { return _size; } set { _size = value; } } + /// <summary> + /// Ending corner. This is calculated as <see cref="Position"/> plus + /// <see cref="Size"/>. Setting this value will change the size. + /// </summary> + /// <value>Getting is equivalent to `value = Position + Size`, setting is equivalent to `Size = value - Position`.</value> public Vector2 End { get { return _position + _size; } set { _size = value - _position; } } + /// <summary> + /// The area of this rect. + /// </summary> + /// <value>Equivalent to <see cref="GetArea()"/>.</value> public real_t Area { get { return GetArea(); } } + /// <summary> + /// Returns a Rect2 with equivalent position and size, modified so that + /// the top-left corner is the origin and width and height are positive. + /// </summary> + /// <returns>The modified rect.</returns> public Rect2 Abs() { Vector2 end = End; @@ -45,12 +72,19 @@ namespace Godot return new Rect2(topLeft, _size.Abs()); } + /// <summary> + /// Returns the intersection of this Rect2 and `b`. + /// </summary> + /// <param name="b">The other rect.</param> + /// <returns>The clipped rect.</returns> public Rect2 Clip(Rect2 b) { var newRect = b; if (!Intersects(newRect)) + { return new Rect2(); + } newRect._position.x = Mathf.Max(b._position.x, _position.x); newRect._position.y = Mathf.Max(b._position.y, _position.y); @@ -64,6 +98,11 @@ namespace Godot return newRect; } + /// <summary> + /// Returns true if this Rect2 completely encloses another one. + /// </summary> + /// <param name="b">The other rect that may be enclosed.</param> + /// <returns>A bool for whether or not this rect encloses `b`.</returns> public bool Encloses(Rect2 b) { return b._position.x >= _position.x && b._position.y >= _position.y && @@ -71,6 +110,11 @@ namespace Godot b._position.y + b._size.y < _position.y + _size.y; } + /// <summary> + /// Returns this Rect2 expanded to include a given point. + /// </summary> + /// <param name="to">The point to include.</param> + /// <returns>The expanded rect.</returns> public Rect2 Expand(Vector2 to) { var expanded = this; @@ -79,14 +123,22 @@ namespace Godot Vector2 end = expanded._position + expanded._size; if (to.x < begin.x) + { begin.x = to.x; + } if (to.y < begin.y) + { begin.y = to.y; + } if (to.x > end.x) + { end.x = to.x; + } if (to.y > end.y) + { end.y = to.y; + } expanded._position = begin; expanded._size = end - begin; @@ -94,11 +146,20 @@ namespace Godot return expanded; } + /// <summary> + /// Returns the area of the Rect2. + /// </summary> + /// <returns>The area.</returns> public real_t GetArea() { return _size.x * _size.y; } + /// <summary> + /// Returns a copy of the Rect2 grown a given amount of units towards all the sides. + /// </summary> + /// <param name="by">The amount to grow by.</param> + /// <returns>The grown rect.</returns> public Rect2 Grow(real_t by) { var g = this; @@ -111,6 +172,14 @@ namespace Godot return g; } + /// <summary> + /// Returns a copy of the Rect2 grown a given amount of units towards each direction individually. + /// </summary> + /// <param name="left">The amount to grow by on the left.</param> + /// <param name="top">The amount to grow by on the top.</param> + /// <param name="right">The amount to grow by on the right.</param> + /// <param name="bottom">The amount to grow by on the bottom.</param> + /// <returns>The grown rect.</returns> public Rect2 GrowIndividual(real_t left, real_t top, real_t right, real_t bottom) { var g = this; @@ -123,6 +192,12 @@ namespace Godot return g; } + /// <summary> + /// Returns a copy of the Rect2 grown a given amount of units towards the <see cref="Margin"/> direction. + /// </summary> + /// <param name="margin">The direction to grow in.</param> + /// <param name="by">The amount to grow by.</param> + /// <returns>The grown rect.</returns> public Rect2 GrowMargin(Margin margin, real_t by) { var g = this; @@ -135,11 +210,20 @@ namespace Godot return g; } + /// <summary> + /// Returns true if the Rect2 is flat or empty, or false otherwise. + /// </summary> + /// <returns>A bool for whether or not the rect has area.</returns> public bool HasNoArea() { return _size.x <= 0 || _size.y <= 0; } + /// <summary> + /// Returns true if the Rect2 contains a point, or false otherwise. + /// </summary> + /// <param name="point">The point to check.</param> + /// <returns>A bool for whether or not the rect contains `point`.</returns> public bool HasPoint(Vector2 point) { if (point.x < _position.x) @@ -155,20 +239,65 @@ namespace Godot return true; } - public bool Intersects(Rect2 b) + /// <summary> + /// Returns true if the Rect2 overlaps with `b` + /// (i.e. they have at least one point in common). + /// + /// If `includeBorders` is true, they will also be considered overlapping + /// if their borders touch, even without intersection. + /// </summary> + /// <param name="b">The other rect to check for intersections with.</param> + /// <param name="includeBorders">Whether or not to consider borders.</param> + /// <returns>A bool for whether or not they are intersecting.</returns> + public bool Intersects(Rect2 b, bool includeBorders = false) { - if (_position.x >= b._position.x + b._size.x) - return false; - if (_position.x + _size.x <= b._position.x) - return false; - if (_position.y >= b._position.y + b._size.y) - return false; - if (_position.y + _size.y <= b._position.y) - return false; + if (includeBorders) + { + if (_position.x > b._position.x + b._size.x) + { + return false; + } + if (_position.x + _size.x < b._position.x) + { + return false; + } + if (_position.y > b._position.y + b._size.y) + { + return false; + } + if (_position.y + _size.y < b._position.y) + { + return false; + } + } + else + { + if (_position.x >= b._position.x + b._size.x) + { + return false; + } + if (_position.x + _size.x <= b._position.x) + { + return false; + } + if (_position.y >= b._position.y + b._size.y) + { + return false; + } + if (_position.y + _size.y <= b._position.y) + { + return false; + } + } return true; } + /// <summary> + /// Returns a larger Rect2 that contains this Rect2 and `b`. + /// </summary> + /// <param name="b">The other rect.</param> + /// <returns>The merged rect.</returns> public Rect2 Merge(Rect2 b) { Rect2 newRect; @@ -179,27 +308,53 @@ namespace Godot newRect._size.x = Mathf.Max(b._position.x + b._size.x, _position.x + _size.x); newRect._size.y = Mathf.Max(b._position.y + b._size.y, _position.y + _size.y); - newRect._size = newRect._size - newRect._position; // Make relative again + newRect._size -= newRect._position; // Make relative again return newRect; } - // Constructors + /// <summary> + /// Constructs a Rect2 from a position and size. + /// </summary> + /// <param name="position">The position.</param> + /// <param name="size">The size.</param> public Rect2(Vector2 position, Vector2 size) { _position = position; _size = size; } + + /// <summary> + /// Constructs a Rect2 from a position, width, and height. + /// </summary> + /// <param name="position">The position.</param> + /// <param name="width">The width.</param> + /// <param name="height">The height.</param> public Rect2(Vector2 position, real_t width, real_t height) { _position = position; _size = new Vector2(width, height); } + + /// <summary> + /// Constructs a Rect2 from x, y, and size. + /// </summary> + /// <param name="x">The position's X coordinate.</param> + /// <param name="y">The position's Y coordinate.</param> + /// <param name="size">The size.</param> public Rect2(real_t x, real_t y, Vector2 size) { _position = new Vector2(x, y); _size = size; } + + /// <summary> + /// Constructs a Rect2 from x, y, width, and height. + /// </summary> + /// <param name="x">The position's X coordinate.</param> + /// <param name="y">The position's Y coordinate.</param> + /// <param name="width">The width.</param> + /// <param name="height">The height.</param> public Rect2(real_t x, real_t y, real_t width, real_t height) { _position = new Vector2(x, y); @@ -231,6 +386,12 @@ namespace Godot return _position.Equals(other._position) && _size.Equals(other._size); } + /// <summary> + /// Returns true if this rect and `other` are approximately equal, by running + /// <see cref="Vector2.IsEqualApprox(Vector2)"/> on each component. + /// </summary> + /// <param name="other">The other rect to compare.</param> + /// <returns>Whether or not the rects are approximately equal.</returns> public bool IsEqualApprox(Rect2 other) { return _position.IsEqualApprox(other._position) && _size.IsEqualApprox(other.Size); diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2i.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2i.cs index c0b236c524..8f71c00d76 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2i.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2i.cs @@ -3,6 +3,10 @@ using System.Runtime.InteropServices; namespace Godot { + /// <summary> + /// 2D axis-aligned bounding box using integers. Rect2i consists of a position, a size, and + /// several utility functions. It is typically used for fast overlap tests. + /// </summary> [Serializable] [StructLayout(LayoutKind.Sequential)] public struct Rect2i : IEquatable<Rect2i> @@ -10,29 +14,52 @@ namespace Godot private Vector2i _position; private Vector2i _size; + /// <summary> + /// Beginning corner. Typically has values lower than End. + /// </summary> + /// <value>Directly uses a private field.</value> public Vector2i Position { get { return _position; } set { _position = value; } } + /// <summary> + /// Size from Position to End. Typically all components are positive. + /// If the size is negative, you can use <see cref="Abs"/> to fix it. + /// </summary> + /// <value>Directly uses a private field.</value> public Vector2i Size { get { return _size; } set { _size = value; } } + /// <summary> + /// Ending corner. This is calculated as <see cref="Position"/> plus + /// <see cref="Size"/>. Setting this value will change the size. + /// </summary> + /// <value>Getting is equivalent to `value = Position + Size`, setting is equivalent to `Size = value - Position`.</value> public Vector2i End { get { return _position + _size; } set { _size = value - _position; } } + /// <summary> + /// The area of this rect. + /// </summary> + /// <value>Equivalent to <see cref="GetArea()"/>.</value> public int Area { get { return GetArea(); } } + /// <summary> + /// Returns a Rect2i with equivalent position and size, modified so that + /// the top-left corner is the origin and width and height are positive. + /// </summary> + /// <returns>The modified rect.</returns> public Rect2i Abs() { Vector2i end = End; @@ -40,12 +67,19 @@ namespace Godot return new Rect2i(topLeft, _size.Abs()); } + /// <summary> + /// Returns the intersection of this Rect2i and `b`. + /// </summary> + /// <param name="b">The other rect.</param> + /// <returns>The clipped rect.</returns> public Rect2i Clip(Rect2i b) { var newRect = b; if (!Intersects(newRect)) + { return new Rect2i(); + } newRect._position.x = Mathf.Max(b._position.x, _position.x); newRect._position.y = Mathf.Max(b._position.y, _position.y); @@ -59,6 +93,11 @@ namespace Godot return newRect; } + /// <summary> + /// Returns true if this Rect2i completely encloses another one. + /// </summary> + /// <param name="b">The other rect that may be enclosed.</param> + /// <returns>A bool for whether or not this rect encloses `b`.</returns> public bool Encloses(Rect2i b) { return b._position.x >= _position.x && b._position.y >= _position.y && @@ -66,6 +105,11 @@ namespace Godot b._position.y + b._size.y < _position.y + _size.y; } + /// <summary> + /// Returns this Rect2i expanded to include a given point. + /// </summary> + /// <param name="to">The point to include.</param> + /// <returns>The expanded rect.</returns> public Rect2i Expand(Vector2i to) { var expanded = this; @@ -74,14 +118,22 @@ namespace Godot Vector2i end = expanded._position + expanded._size; if (to.x < begin.x) + { begin.x = to.x; + } if (to.y < begin.y) + { begin.y = to.y; + } if (to.x > end.x) + { end.x = to.x; + } if (to.y > end.y) + { end.y = to.y; + } expanded._position = begin; expanded._size = end - begin; @@ -89,11 +141,20 @@ namespace Godot return expanded; } + /// <summary> + /// Returns the area of the Rect2. + /// </summary> + /// <returns>The area.</returns> public int GetArea() { return _size.x * _size.y; } + /// <summary> + /// Returns a copy of the Rect2i grown a given amount of units towards all the sides. + /// </summary> + /// <param name="by">The amount to grow by.</param> + /// <returns>The grown rect.</returns> public Rect2i Grow(int by) { var g = this; @@ -106,6 +167,14 @@ namespace Godot return g; } + /// <summary> + /// Returns a copy of the Rect2i grown a given amount of units towards each direction individually. + /// </summary> + /// <param name="left">The amount to grow by on the left.</param> + /// <param name="top">The amount to grow by on the top.</param> + /// <param name="right">The amount to grow by on the right.</param> + /// <param name="bottom">The amount to grow by on the bottom.</param> + /// <returns>The grown rect.</returns> public Rect2i GrowIndividual(int left, int top, int right, int bottom) { var g = this; @@ -118,6 +187,12 @@ namespace Godot return g; } + /// <summary> + /// Returns a copy of the Rect2i grown a given amount of units towards the <see cref="Margin"/> direction. + /// </summary> + /// <param name="margin">The direction to grow in.</param> + /// <param name="by">The amount to grow by.</param> + /// <returns>The grown rect.</returns> public Rect2i GrowMargin(Margin margin, int by) { var g = this; @@ -130,11 +205,20 @@ namespace Godot return g; } + /// <summary> + /// Returns true if the Rect2 is flat or empty, or false otherwise. + /// </summary> + /// <returns>A bool for whether or not the rect has area.</returns> public bool HasNoArea() { return _size.x <= 0 || _size.y <= 0; } + /// <summary> + /// Returns true if the Rect2 contains a point, or false otherwise. + /// </summary> + /// <param name="point">The point to check.</param> + /// <returns>A bool for whether or not the rect contains `point`.</returns> public bool HasPoint(Vector2i point) { if (point.x < _position.x) @@ -150,20 +234,49 @@ namespace Godot return true; } - public bool Intersects(Rect2i b) + /// <summary> + /// Returns true if the Rect2i overlaps with `b` + /// (i.e. they have at least one point in common). + /// + /// If `includeBorders` is true, they will also be considered overlapping + /// if their borders touch, even without intersection. + /// </summary> + /// <param name="b">The other rect to check for intersections with.</param> + /// <param name="includeBorders">Whether or not to consider borders.</param> + /// <returns>A bool for whether or not they are intersecting.</returns> + public bool Intersects(Rect2i b, bool includeBorders = false) { - if (_position.x >= b._position.x + b._size.x) - return false; - if (_position.x + _size.x <= b._position.x) - return false; - if (_position.y >= b._position.y + b._size.y) - return false; - if (_position.y + _size.y <= b._position.y) - return false; + if (includeBorders) + { + if (_position.x > b._position.x + b._size.x) + return false; + if (_position.x + _size.x < b._position.x) + return false; + if (_position.y > b._position.y + b._size.y) + return false; + if (_position.y + _size.y < b._position.y) + return false; + } + else + { + if (_position.x >= b._position.x + b._size.x) + return false; + if (_position.x + _size.x <= b._position.x) + return false; + if (_position.y >= b._position.y + b._size.y) + return false; + if (_position.y + _size.y <= b._position.y) + return false; + } return true; } + /// <summary> + /// Returns a larger Rect2i that contains this Rect2 and `b`. + /// </summary> + /// <param name="b">The other rect.</param> + /// <returns>The merged rect.</returns> public Rect2i Merge(Rect2i b) { Rect2i newRect; @@ -174,27 +287,53 @@ namespace Godot newRect._size.x = Mathf.Max(b._position.x + b._size.x, _position.x + _size.x); newRect._size.y = Mathf.Max(b._position.y + b._size.y, _position.y + _size.y); - newRect._size = newRect._size - newRect._position; // Make relative again + newRect._size -= newRect._position; // Make relative again return newRect; } - // Constructors + /// <summary> + /// Constructs a Rect2i from a position and size. + /// </summary> + /// <param name="position">The position.</param> + /// <param name="size">The size.</param> public Rect2i(Vector2i position, Vector2i size) { _position = position; _size = size; } + + /// <summary> + /// Constructs a Rect2i from a position, width, and height. + /// </summary> + /// <param name="position">The position.</param> + /// <param name="width">The width.</param> + /// <param name="height">The height.</param> public Rect2i(Vector2i position, int width, int height) { _position = position; _size = new Vector2i(width, height); } + + /// <summary> + /// Constructs a Rect2i from x, y, and size. + /// </summary> + /// <param name="x">The position's X coordinate.</param> + /// <param name="y">The position's Y coordinate.</param> + /// <param name="size">The size.</param> public Rect2i(int x, int y, Vector2i size) { _position = new Vector2i(x, y); _size = size; } + + /// <summary> + /// Constructs a Rect2i from x, y, width, and height. + /// </summary> + /// <param name="x">The position's X coordinate.</param> + /// <param name="y">The position's Y coordinate.</param> + /// <param name="width">The width.</param> + /// <param name="height">The height.</param> public Rect2i(int x, int y, int width, int height) { _position = new Vector2i(x, y); diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform.cs index 6a58b90561..98d4b92bd1 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform.cs @@ -8,11 +8,28 @@ using real_t = System.Single; namespace Godot { + /// <summary> + /// 3×4 matrix (3 rows, 4 columns) used for 3D linear transformations. + /// It can represent transformations such as translation, rotation, or scaling. + /// It consists of a <see cref="Basis"/> (first 3 columns) and a + /// <see cref="Vector3"/> for the origin (last column). + /// + /// For more information, read this documentation article: + /// https://docs.godotengine.org/en/latest/tutorials/math/matrices_and_transforms.html + /// </summary> [Serializable] [StructLayout(LayoutKind.Sequential)] public struct Transform : IEquatable<Transform> { + /// <summary> + /// The <see cref="Basis"/> of this transform. Contains the X, Y, and Z basis + /// vectors (columns 0 to 2) and is responsible for rotation and scale. + /// </summary> public Basis basis; + + /// <summary> + /// The origin vector (column 3, the fourth column). Equivalent to array index `[3]`. + /// </summary> public Vector3 origin; /// <summary> @@ -85,13 +102,24 @@ namespace Godot } } + /// <summary> + /// Returns the inverse of the transform, under the assumption that + /// the transformation is composed of rotation, scaling, and translation. + /// </summary> + /// <returns>The inverse transformation matrix.</returns> public Transform AffineInverse() { Basis basisInv = basis.Inverse(); return new Transform(basisInv, basisInv.Xform(-origin)); } - public Transform InterpolateWith(Transform transform, real_t c) + /// <summary> + /// Interpolates this transform to the other `transform` by `weight`. + /// </summary> + /// <param name="transform">The other transform.</param> + /// <param name="weight">A value on the range of 0.0 to 1.0, representing the amount of interpolation.</param> + /// <returns>The interpolated transform.</returns> + public Transform InterpolateWith(Transform transform, real_t weight) { /* not sure if very "efficient" but good enough? */ @@ -104,18 +132,37 @@ namespace Godot Vector3 destinationLocation = transform.origin; var interpolated = new Transform(); - interpolated.basis.SetQuatScale(sourceRotation.Slerp(destinationRotation, c).Normalized(), sourceScale.Lerp(destinationScale, c)); - interpolated.origin = sourceLocation.Lerp(destinationLocation, c); + interpolated.basis.SetQuatScale(sourceRotation.Slerp(destinationRotation, weight).Normalized(), sourceScale.Lerp(destinationScale, weight)); + interpolated.origin = sourceLocation.Lerp(destinationLocation, weight); return interpolated; } + /// <summary> + /// Returns the inverse of the transform, under the assumption that + /// the transformation is composed of rotation and translation + /// (no scaling, use <see cref="AffineInverse"/> for transforms with scaling). + /// </summary> + /// <returns>The inverse matrix.</returns> public Transform Inverse() { Basis basisTr = basis.Transposed(); return new Transform(basisTr, basisTr.Xform(-origin)); } + /// <summary> + /// Returns a copy of the transform rotated such that its + /// -Z axis (forward) points towards the target position. + /// + /// The transform will first be rotated around the given up vector, + /// and then fully aligned to the target by a further rotation around + /// an axis perpendicular to both the target and up vectors. + /// + /// Operations take place in global space. + /// </summary> + /// <param name="target">The object to look at.</param> + /// <param name="up">The relative up direction</param> + /// <returns>The resulting transform.</returns> public Transform LookingAt(Vector3 target, Vector3 up) { var t = this; @@ -123,22 +170,39 @@ namespace Godot return t; } + /// <summary> + /// Returns the transform with the basis orthogonal (90 degrees), + /// and normalized axis vectors (scale of 1 or -1). + /// </summary> + /// <returns>The orthonormalized transform.</returns> public Transform Orthonormalized() { return new Transform(basis.Orthonormalized(), origin); } + /// <summary> + /// Rotates the transform around the given `axis` by `phi` (in radians), + /// using matrix multiplication. The axis must be a normalized vector. + /// </summary> + /// <param name="axis">The axis to rotate around. Must be normalized.</param> + /// <param name="phi">The angle to rotate, in radians.</param> + /// <returns>The rotated transformation matrix.</returns> public Transform Rotated(Vector3 axis, real_t phi) { return new Transform(new Basis(axis, phi), new Vector3()) * this; } + /// <summary> + /// Scales the transform by the given 3D scaling factor, using matrix multiplication. + /// </summary> + /// <param name="scale">The scale to introduce.</param> + /// <returns>The scaled transformation matrix.</returns> public Transform Scaled(Vector3 scale) { return new Transform(basis.Scaled(scale), origin * scale); } - public void SetLookAt(Vector3 eye, Vector3 target, Vector3 up) + private void SetLookAt(Vector3 eye, Vector3 target, Vector3 up) { // Make rotation matrix // Z vector @@ -161,16 +225,30 @@ namespace Godot origin = eye; } - public Transform Translated(Vector3 ofs) + /// <summary> + /// Translates the transform by the given `offset`, + /// relative to the transform's basis vectors. + /// + /// Unlike <see cref="Rotated"/> and <see cref="Scaled"/>, + /// this does not use matrix multiplication. + /// </summary> + /// <param name="offset">The offset to translate by.</param> + /// <returns>The translated matrix.</returns> + public Transform Translated(Vector3 offset) { return new Transform(basis, new Vector3 ( - origin[0] += basis.Row0.Dot(ofs), - origin[1] += basis.Row1.Dot(ofs), - origin[2] += basis.Row2.Dot(ofs) + origin[0] += basis.Row0.Dot(offset), + origin[1] += basis.Row1.Dot(offset), + origin[2] += basis.Row2.Dot(offset) )); } + /// <summary> + /// Returns a vector transformed (multiplied) by this transformation matrix. + /// </summary> + /// <param name="v">A vector to transform.</param> + /// <returns>The transfomed vector.</returns> public Vector3 Xform(Vector3 v) { return new Vector3 @@ -181,6 +259,14 @@ namespace Godot ); } + /// <summary> + /// Returns a vector transformed (multiplied) by the transposed transformation matrix. + /// + /// Note: This results in a multiplication by the inverse of the + /// transformation matrix only if it represents a rotation-reflection. + /// </summary> + /// <param name="v">A vector to inversely transform.</param> + /// <returns>The inversely transfomed vector.</returns> public Vector3 XformInv(Vector3 v) { Vector3 vInv = v - origin; @@ -199,24 +285,58 @@ namespace Godot private static readonly Transform _flipY = new Transform(new Basis(1, 0, 0, 0, -1, 0, 0, 0, 1), Vector3.Zero); private static readonly Transform _flipZ = new Transform(new Basis(1, 0, 0, 0, 1, 0, 0, 0, -1), Vector3.Zero); + /// <summary> + /// The identity transform, with no translation, rotation, or scaling applied. + /// This is used as a replacement for `Transform()` in GDScript. + /// Do not use `new Transform()` with no arguments in C#, because it sets all values to zero. + /// </summary> + /// <value>Equivalent to `new Transform(Vector3.Right, Vector3.Up, Vector3.Back, Vector3.Zero)`.</value> public static Transform Identity { get { return _identity; } } + /// <summary> + /// The transform that will flip something along the X axis. + /// </summary> + /// <value>Equivalent to `new Transform(Vector3.Left, Vector3.Up, Vector3.Back, Vector3.Zero)`.</value> public static Transform FlipX { get { return _flipX; } } + /// <summary> + /// The transform that will flip something along the Y axis. + /// </summary> + /// <value>Equivalent to `new Transform(Vector3.Right, Vector3.Down, Vector3.Back, Vector3.Zero)`.</value> public static Transform FlipY { get { return _flipY; } } + /// <summary> + /// The transform that will flip something along the Z axis. + /// </summary> + /// <value>Equivalent to `new Transform(Vector3.Right, Vector3.Up, Vector3.Forward, Vector3.Zero)`.</value> public static Transform FlipZ { get { return _flipZ; } } - // Constructors + /// <summary> + /// Constructs a transformation matrix from 4 vectors (matrix columns). + /// </summary> + /// <param name="column0">The X vector, or column index 0.</param> + /// <param name="column1">The Y vector, or column index 1.</param> + /// <param name="column2">The Z vector, or column index 2.</param> + /// <param name="origin">The origin vector, or column index 3.</param> public Transform(Vector3 column0, Vector3 column1, Vector3 column2, Vector3 origin) { basis = new Basis(column0, column1, column2); this.origin = origin; } + /// <summary> + /// Constructs a transformation matrix from the given quaternion and origin vector. + /// </summary> + /// <param name="quat">The <see cref="Godot.Quat"/> to create the basis from.</param> + /// <param name="origin">The origin vector, or column index 3.</param> public Transform(Quat quat, Vector3 origin) { basis = new Basis(quat); this.origin = origin; } + /// <summary> + /// Constructs a transformation matrix from the given basis and origin vector. + /// </summary> + /// <param name="basis">The <see cref="Godot.Basis"/> to create the basis from.</param> + /// <param name="origin">The origin vector, or column index 3.</param> public Transform(Basis basis, Vector3 origin) { this.basis = basis; @@ -255,6 +375,12 @@ namespace Godot return basis.Equals(other.basis) && origin.Equals(other.origin); } + /// <summary> + /// Returns true if this transform and `other` are approximately equal, by running + /// <see cref="Vector3.IsEqualApprox(Vector3)"/> on each component. + /// </summary> + /// <param name="other">The other transform to compare.</param> + /// <returns>Whether or not the matrices are approximately equal.</returns> public bool IsEqualApprox(Transform other) { return basis.IsEqualApprox(other.basis) && origin.IsEqualApprox(other.origin); diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs index 3ae96d4922..9f9ae50c59 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs @@ -8,25 +8,44 @@ using real_t = System.Single; namespace Godot { + /// <summary> + /// 2×3 matrix (2 rows, 3 columns) used for 2D linear transformations. + /// It can represent transformations such as translation, rotation, or scaling. + /// It consists of a three <see cref="Vector2"/> values: x, y, and the origin. + /// + /// For more information, read this documentation article: + /// https://docs.godotengine.org/en/latest/tutorials/math/matrices_and_transforms.html + /// </summary> [Serializable] [StructLayout(LayoutKind.Sequential)] public struct Transform2D : IEquatable<Transform2D> { + /// <summary> + /// The basis matrix's X vector (column 0). Equivalent to array index `[0]`. + /// </summary> + /// <value></value> public Vector2 x; + + /// <summary> + /// The basis matrix's Y vector (column 1). Equivalent to array index `[1]`. + /// </summary> public Vector2 y; + + /// <summary> + /// The origin vector (column 2, the third column). Equivalent to array index `[2]`. + /// The origin vector represents translation. + /// </summary> public Vector2 origin; + /// <summary> + /// The rotation of this transformation matrix. + /// </summary> + /// <value>Getting is equivalent to calling <see cref="Mathf.Atan2(real_t, real_t)"/> with the values of <see cref="x"/>.</value> public real_t Rotation { get { - real_t det = BasisDeterminant(); - Transform2D t = Orthonormalized(); - if (det < 0) - { - t.ScaleBasis(new Vector2(1, -1)); - } - return Mathf.Atan2(t.x.y, t.x.x); + return Mathf.Atan2(x.y, x.x); } set { @@ -38,6 +57,10 @@ namespace Godot } } + /// <summary> + /// The scale of this transformation matrix. + /// </summary> + /// <value>Equivalent to the lengths of each column vector, but Y is negative if the determinant is negative.</value> public Vector2 Scale { get @@ -47,8 +70,7 @@ namespace Godot } set { - x = x.Normalized(); - y = y.Normalized(); + value /= Scale; // Value becomes what's called "delta_scale" in core. x *= value.x; y *= value.y; } @@ -112,6 +134,11 @@ namespace Godot } } + /// <summary> + /// Returns the inverse of the transform, under the assumption that + /// the transformation is composed of rotation, scaling, and translation. + /// </summary> + /// <returns>The inverse transformation matrix.</returns> public Transform2D AffineInverse() { real_t det = BasisDeterminant(); @@ -135,28 +162,58 @@ namespace Godot return inv; } + /// <summary> + /// Returns the determinant of the basis matrix. If the basis is + /// uniformly scaled, its determinant is the square of the scale. + /// + /// A negative determinant means the Y scale is negative. + /// A zero determinant means the basis isn't invertible, + /// and is usually considered invalid. + /// </summary> + /// <returns>The determinant of the basis matrix.</returns> private real_t BasisDeterminant() { return x.x * y.y - x.y * y.x; } + /// <summary> + /// Returns a vector transformed (multiplied) by the basis matrix. + /// This method does not account for translation (the origin vector). + /// </summary> + /// <param name="v">A vector to transform.</param> + /// <returns>The transfomed vector.</returns> public Vector2 BasisXform(Vector2 v) { return new Vector2(Tdotx(v), Tdoty(v)); } + /// <summary> + /// Returns a vector transformed (multiplied) by the inverse basis matrix. + /// This method does not account for translation (the origin vector). + /// + /// Note: This results in a multiplication by the inverse of the + /// basis matrix only if it represents a rotation-reflection. + /// </summary> + /// <param name="v">A vector to inversely transform.</param> + /// <returns>The inversely transfomed vector.</returns> public Vector2 BasisXformInv(Vector2 v) { return new Vector2(x.Dot(v), y.Dot(v)); } - public Transform2D InterpolateWith(Transform2D m, real_t c) + /// <summary> + /// Interpolates this transform to the other `transform` by `weight`. + /// </summary> + /// <param name="transform">The other transform.</param> + /// <param name="weight">A value on the range of 0.0 to 1.0, representing the amount of interpolation.</param> + /// <returns>The interpolated transform.</returns> + public Transform2D InterpolateWith(Transform2D transform, real_t weight) { real_t r1 = Rotation; - real_t r2 = m.Rotation; + real_t r2 = transform.Rotation; Vector2 s1 = Scale; - Vector2 s2 = m.Scale; + Vector2 s2 = transform.Scale; // Slerp rotation var v1 = new Vector2(Mathf.Cos(r1), Mathf.Sin(r1)); @@ -172,28 +229,34 @@ namespace Godot if (dot > 0.9995f) { // Linearly interpolate to avoid numerical precision issues - v = v1.Lerp(v2, c).Normalized(); + v = v1.Lerp(v2, weight).Normalized(); } else { - real_t angle = c * Mathf.Acos(dot); + real_t angle = weight * Mathf.Acos(dot); Vector2 v3 = (v2 - v1 * dot).Normalized(); v = v1 * Mathf.Cos(angle) + v3 * Mathf.Sin(angle); } // Extract parameters Vector2 p1 = origin; - Vector2 p2 = m.origin; + Vector2 p2 = transform.origin; // Construct matrix - var res = new Transform2D(Mathf.Atan2(v.y, v.x), p1.Lerp(p2, c)); - Vector2 scale = s1.Lerp(s2, c); + var res = new Transform2D(Mathf.Atan2(v.y, v.x), p1.Lerp(p2, weight)); + Vector2 scale = s1.Lerp(s2, weight); res.x *= scale; res.y *= scale; return res; } + /// <summary> + /// Returns the inverse of the transform, under the assumption that + /// the transformation is composed of rotation and translation + /// (no scaling, use <see cref="AffineInverse"/> for transforms with scaling). + /// </summary> + /// <returns>The inverse matrix.</returns> public Transform2D Inverse() { var inv = this; @@ -208,6 +271,11 @@ namespace Godot return inv; } + /// <summary> + /// Returns the transform with the basis orthogonal (90 degrees), + /// and normalized axis vectors (scale of 1 or -1). + /// </summary> + /// <returns>The orthonormalized transform.</returns> public Transform2D Orthonormalized() { var on = this; @@ -225,11 +293,21 @@ namespace Godot return on; } + /// <summary> + /// Rotates the transform by `phi` (in radians), using matrix multiplication. + /// </summary> + /// <param name="phi">The angle to rotate, in radians.</param> + /// <returns>The rotated transformation matrix.</returns> public Transform2D Rotated(real_t phi) { return this * new Transform2D(phi, new Vector2()); } + /// <summary> + /// Scales the transform by the given scaling factor, using matrix multiplication. + /// </summary> + /// <param name="scale">The scale to introduce.</param> + /// <returns>The scaled transformation matrix.</returns> public Transform2D Scaled(Vector2 scale) { var copy = this; @@ -257,6 +335,15 @@ namespace Godot return this[0, 1] * with[0] + this[1, 1] * with[1]; } + /// <summary> + /// Translates the transform by the given `offset`, + /// relative to the transform's basis vectors. + /// + /// Unlike <see cref="Rotated"/> and <see cref="Scaled"/>, + /// this does not use matrix multiplication. + /// </summary> + /// <param name="offset">The offset to translate by.</param> + /// <returns>The translated matrix.</returns> public Transform2D Translated(Vector2 offset) { var copy = this; @@ -264,11 +351,21 @@ namespace Godot return copy; } + /// <summary> + /// Returns a vector transformed (multiplied) by this transformation matrix. + /// </summary> + /// <param name="v">A vector to transform.</param> + /// <returns>The transfomed vector.</returns> public Vector2 Xform(Vector2 v) { return new Vector2(Tdotx(v), Tdoty(v)) + origin; } + /// <summary> + /// Returns a vector transformed (multiplied) by the inverse transformation matrix. + /// </summary> + /// <param name="v">A vector to inversely transform.</param> + /// <returns>The inversely transfomed vector.</returns> public Vector2 XformInv(Vector2 v) { Vector2 vInv = v - origin; @@ -280,11 +377,30 @@ namespace Godot private static readonly Transform2D _flipX = new Transform2D(-1, 0, 0, 1, 0, 0); private static readonly Transform2D _flipY = new Transform2D(1, 0, 0, -1, 0, 0); - public static Transform2D Identity => _identity; - public static Transform2D FlipX => _flipX; - public static Transform2D FlipY => _flipY; + /// <summary> + /// The identity transform, with no translation, rotation, or scaling applied. + /// This is used as a replacement for `Transform2D()` in GDScript. + /// Do not use `new Transform2D()` with no arguments in C#, because it sets all values to zero. + /// </summary> + /// <value>Equivalent to `new Transform2D(Vector2.Right, Vector2.Down, Vector2.Zero)`.</value> + public static Transform2D Identity { get { return _identity; } } + /// <summary> + /// The transform that will flip something along the X axis. + /// </summary> + /// <value>Equivalent to `new Transform2D(Vector2.Left, Vector2.Down, Vector2.Zero)`.</value> + public static Transform2D FlipX { get { return _flipX; } } + /// <summary> + /// The transform that will flip something along the Y axis. + /// </summary> + /// <value>Equivalent to `new Transform2D(Vector2.Right, Vector2.Up, Vector2.Zero)`.</value> + public static Transform2D FlipY { get { return _flipY; } } - // Constructors + /// <summary> + /// Constructs a transformation matrix from 3 vectors (matrix columns). + /// </summary> + /// <param name="xAxis">The X vector, or column index 0.</param> + /// <param name="yAxis">The Y vector, or column index 1.</param> + /// <param name="originPos">The origin vector, or column index 2.</param> public Transform2D(Vector2 xAxis, Vector2 yAxis, Vector2 originPos) { x = xAxis; @@ -292,7 +408,16 @@ namespace Godot origin = originPos; } - // Arguments are named such that xy is equal to calling x.y + /// <summary> + /// Constructs a transformation matrix from the given components. + /// Arguments are named such that xy is equal to calling x.y + /// </summary> + /// <param name="xx">The X component of the X column vector, accessed via `t.x.x` or `[0][0]`</param> + /// <param name="xy">The Y component of the X column vector, accessed via `t.x.y` or `[0][1]`</param> + /// <param name="yx">The X component of the Y column vector, accessed via `t.y.x` or `[1][0]`</param> + /// <param name="yy">The Y component of the Y column vector, accessed via `t.y.y` or `[1][1]`</param> + /// <param name="ox">The X component of the origin vector, accessed via `t.origin.x` or `[2][0]`</param> + /// <param name="oy">The Y component of the origin vector, accessed via `t.origin.y` or `[2][1]`</param> public Transform2D(real_t xx, real_t xy, real_t yx, real_t yy, real_t ox, real_t oy) { x = new Vector2(xx, xy); @@ -300,6 +425,11 @@ namespace Godot origin = new Vector2(ox, oy); } + /// <summary> + /// Constructs a transformation matrix from a rotation value and origin vector. + /// </summary> + /// <param name="rot">The rotation of the new transform, in radians.</param> + /// <param name="pos">The origin vector, or column index 2.</param> public Transform2D(real_t rot, Vector2 pos) { x.x = y.y = Mathf.Cos(rot); @@ -345,6 +475,12 @@ namespace Godot return x.Equals(other.x) && y.Equals(other.y) && origin.Equals(other.origin); } + /// <summary> + /// Returns true if this transform and `other` are approximately equal, by running + /// <see cref="Vector2.IsEqualApprox(Vector2)"/> on each component. + /// </summary> + /// <param name="other">The other transform to compare.</param> + /// <returns>Whether or not the matrices are approximately equal.</returns> public bool IsEqualApprox(Transform2D other) { return x.IsEqualApprox(other.x) && y.IsEqualApprox(other.y) && origin.IsEqualApprox(other.origin); diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs index 7e4804f9fd..26bd828a5b 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs @@ -21,15 +21,29 @@ namespace Godot [StructLayout(LayoutKind.Sequential)] public struct Vector2 : IEquatable<Vector2> { + /// <summary> + /// Enumerated index values for the axes. + /// Returned by <see cref="MaxAxis"/> and <see cref="MinAxis"/>. + /// </summary> public enum Axis { X = 0, Y } + /// <summary> + /// The vector's X component. Also accessible by using the index position `[0]`. + /// </summary> public real_t x; + /// <summary> + /// The vector's Y component. Also accessible by using the index position `[1]`. + /// </summary> public real_t y; + /// <summary> + /// Access vector components using their index. + /// </summary> + /// <value>`[0]` is equivalent to `.x`, `[1]` is equivalent to `.y`.</value> public real_t this[int index] { get @@ -76,41 +90,80 @@ namespace Godot } } + /// <summary> + /// Returns a new vector with all components in absolute values (i.e. positive). + /// </summary> + /// <returns>A vector with <see cref="Mathf.Abs(real_t)"/> called on each component.</returns> public Vector2 Abs() { return new Vector2(Mathf.Abs(x), Mathf.Abs(y)); } + /// <summary> + /// Returns this vector's angle with respect to the X axis, or (1, 0) vector, in radians. + /// + /// Equivalent to the result of <see cref="Mathf.Atan2(real_t, real_t)"/> when + /// called with the vector's `y` and `x` as parameters: `Mathf.Atan2(v.y, v.x)`. + /// </summary> + /// <returns>The angle of this vector, in radians.</returns> public real_t Angle() { return Mathf.Atan2(y, x); } + /// <summary> + /// Returns the angle to the given vector, in radians. + /// </summary> + /// <param name="to">The other vector to compare this vector to.</param> + /// <returns>The angle between the two vectors, in radians.</returns> public real_t AngleTo(Vector2 to) { return Mathf.Atan2(Cross(to), Dot(to)); } + /// <summary> + /// Returns the angle between the line connecting the two points and the X axis, in radians. + /// </summary> + /// <param name="to">The other vector to compare this vector to.</param> + /// <returns>The angle between the two vectors, in radians.</returns> public real_t AngleToPoint(Vector2 to) { return Mathf.Atan2(y - to.y, x - to.x); } + /// <summary> + /// Returns the aspect ratio of this vector, the ratio of `x` to `y`. + /// </summary> + /// <returns>The `x` component divided by the `y` component.</returns> public real_t Aspect() { return x / y; } - public Vector2 Bounce(Vector2 n) + /// <summary> + /// Returns the vector "bounced off" from a plane defined by the given normal. + /// </summary> + /// <param name="normal">The normal vector defining the plane to bounce off. Must be normalized.</param> + /// <returns>The bounced vector.</returns> + public Vector2 Bounce(Vector2 normal) { - return -Reflect(n); + return -Reflect(normal); } + /// <summary> + /// Returns a new vector with all components rounded up (towards positive infinity). + /// </summary> + /// <returns>A vector with <see cref="Mathf.Ceil"/> called on each component.</returns> public Vector2 Ceil() { return new Vector2(Mathf.Ceil(x), Mathf.Ceil(y)); } + /// <summary> + /// Returns the vector with a maximum length by limiting its length to `length`. + /// </summary> + /// <param name="length">The length to limit to.</param> + /// <returns>The vector with its length limited.</returns> public Vector2 Clamped(real_t length) { var v = this; @@ -125,17 +178,30 @@ namespace Godot return v; } + /// <summary> + /// Returns the cross product of this vector and `b`. + /// </summary> + /// <param name="b">The other vector.</param> + /// <returns>The cross product value.</returns> public real_t Cross(Vector2 b) { return x * b.y - y * b.x; } + /// <summary> + /// Performs a cubic interpolation between vectors `preA`, this vector, `b`, and `postB`, by the given amount `t`. + /// </summary> + /// <param name="b">The destination vector.</param> + /// <param name="preA">A vector before this vector.</param> + /// <param name="postB">A vector after `b`.</param> + /// <param name="t">A value on the range of 0.0 to 1.0, representing the amount of interpolation.</param> + /// <returns>The interpolated vector.</returns> public Vector2 CubicInterpolate(Vector2 b, Vector2 preA, Vector2 postB, real_t t) { - var p0 = preA; - var p1 = this; - var p2 = b; - var p3 = postB; + Vector2 p0 = preA; + Vector2 p1 = this; + Vector2 p2 = b; + Vector2 p3 = postB; real_t t2 = t * t; real_t t3 = t2 * t; @@ -146,46 +212,102 @@ namespace Godot (-p0 + 3.0f * p1 - 3.0f * p2 + p3) * t3); } + /// <summary> + /// Returns the normalized vector pointing from this vector to `b`. + /// </summary> + /// <param name="b">The other vector to point towards.</param> + /// <returns>The direction from this vector to `b`.</returns> public Vector2 DirectionTo(Vector2 b) { return new Vector2(b.x - x, b.y - y).Normalized(); } + /// <summary> + /// Returns the squared distance between this vector and `to`. + /// This method runs faster than <see cref="DistanceTo"/>, so prefer it if + /// you need to compare vectors or need the squared distance for some formula. + /// </summary> + /// <param name="to">The other vector to use.</param> + /// <returns>The squared distance between the two vectors.</returns> public real_t DistanceSquaredTo(Vector2 to) { return (x - to.x) * (x - to.x) + (y - to.y) * (y - to.y); } + /// <summary> + /// Returns the distance between this vector and `to`. + /// </summary> + /// <param name="to">The other vector to use.</param> + /// <returns>The distance between the two vectors.</returns> public real_t DistanceTo(Vector2 to) { return Mathf.Sqrt((x - to.x) * (x - to.x) + (y - to.y) * (y - to.y)); } + /// <summary> + /// Returns the dot product of this vector and `with`. + /// </summary> + /// <param name="with">The other vector to use.</param> + /// <returns>The dot product of the two vectors.</returns> public real_t Dot(Vector2 with) { return x * with.x + y * with.y; } + /// <summary> + /// Returns a new vector with all components rounded down (towards negative infinity). + /// </summary> + /// <returns>A vector with <see cref="Mathf.Floor"/> called on each component.</returns> public Vector2 Floor() { return new Vector2(Mathf.Floor(x), Mathf.Floor(y)); } + /// <summary> + /// Returns the inverse of this vector. This is the same as `new Vector2(1 / v.x, 1 / v.y)`. + /// </summary> + /// <returns>The inverse of this vector.</returns> + public Vector2 Inverse() + { + return new Vector2(1 / x, 1 / y); + } + + /// <summary> + /// Returns true if the vector is normalized, and false otherwise. + /// </summary> + /// <returns>A bool indicating whether or not the vector is normalized.</returns> public bool IsNormalized() { return Mathf.Abs(LengthSquared() - 1.0f) < Mathf.Epsilon; } + /// <summary> + /// Returns the length (magnitude) of this vector. + /// </summary> + /// <returns>The length of this vector.</returns> public real_t Length() { return Mathf.Sqrt(x * x + y * y); } + /// <summary> + /// Returns the squared length (squared magnitude) of this vector. + /// This method runs faster than <see cref="Length"/>, so prefer it if + /// you need to compare vectors or need the squared length for some formula. + /// </summary> + /// <returns>The squared length of this vector.</returns> public real_t LengthSquared() { return x * x + y * y; } + /// <summary> + /// Returns the result of the linear interpolation between + /// this vector and `to` by amount `weight`. + /// </summary> + /// <param name="to">The destination vector for interpolation.</param> + /// <param name="weight">A value on the range of 0.0 to 1.0, representing the amount of interpolation.</param> + /// <returns>The resulting vector of the interpolation.</returns> public Vector2 Lerp(Vector2 to, real_t weight) { return new Vector2 @@ -195,6 +317,13 @@ namespace Godot ); } + /// <summary> + /// Returns the result of the linear interpolation between + /// this vector and `to` by the vector amount `weight`. + /// </summary> + /// <param name="to">The destination vector for interpolation.</param> + /// <param name="weight">A vector with components on the range of 0.0 to 1.0, representing the amount of interpolation.</param> + /// <returns>The resulting vector of the interpolation.</returns> public Vector2 Lerp(Vector2 to, Vector2 weight) { return new Vector2 @@ -204,6 +333,32 @@ namespace Godot ); } + /// <summary> + /// Returns the axis of the vector's largest value. See <see cref="Axis"/>. + /// If both components are equal, this method returns <see cref="Axis.X"/>. + /// </summary> + /// <returns>The index of the largest axis.</returns> + public Axis MaxAxis() + { + return x < y ? Axis.Y : Axis.X; + } + + /// <summary> + /// Returns the axis of the vector's smallest value. See <see cref="Axis"/>. + /// If both components are equal, this method returns <see cref="Axis.Y"/>. + /// </summary> + /// <returns>The index of the smallest axis.</returns> + public Axis MinAxis() + { + return x < y ? Axis.X : Axis.Y; + } + + /// <summary> + /// Moves this vector toward `to` by the fixed `delta` amount. + /// </summary> + /// <param name="to">The vector to move towards.</param> + /// <param name="delta">The amount to move towards by.</param> + /// <returns>The resulting vector.</returns> public Vector2 MoveToward(Vector2 to, real_t delta) { var v = this; @@ -212,6 +367,10 @@ namespace Godot return len <= delta || len < Mathf.Epsilon ? to : v + vd / len * delta; } + /// <summary> + /// Returns the vector scaled to unit length. Equivalent to `v / v.Length()`. + /// </summary> + /// <returns>A normalized version of the vector.</returns> public Vector2 Normalized() { var v = this; @@ -219,6 +378,11 @@ namespace Godot return v; } + /// <summary> + /// Returns a vector composed of the <see cref="Mathf.PosMod(real_t, real_t)"/> of this vector's components and `mod`. + /// </summary> + /// <param name="mod">A value representing the divisor of the operation.</param> + /// <returns>A vector with each component <see cref="Mathf.PosMod(real_t, real_t)"/> by `mod`.</returns> public Vector2 PosMod(real_t mod) { Vector2 v; @@ -227,6 +391,11 @@ namespace Godot return v; } + /// <summary> + /// Returns a vector composed of the <see cref="Mathf.PosMod(real_t, real_t)"/> of this vector's components and `modv`'s components. + /// </summary> + /// <param name="modv">A vector representing the divisors of the operation.</param> + /// <returns>A vector with each component <see cref="Mathf.PosMod(real_t, real_t)"/> by `modv`'s components.</returns> public Vector2 PosMod(Vector2 modv) { Vector2 v; @@ -235,27 +404,59 @@ namespace Godot return v; } + /// <summary> + /// Returns this vector projected onto another vector `b`. + /// </summary> + /// <param name="onNormal">The vector to project onto.</param> + /// <returns>The projected vector.</returns> public Vector2 Project(Vector2 onNormal) { return onNormal * (Dot(onNormal) / onNormal.LengthSquared()); } - public Vector2 Reflect(Vector2 n) + /// <summary> + /// Returns this vector reflected from a plane defined by the given `normal`. + /// </summary> + /// <param name="normal">The normal vector defining the plane to reflect from. Must be normalized.</param> + /// <returns>The reflected vector.</returns> + public Vector2 Reflect(Vector2 normal) { - return 2 * Dot(n) * n - this; +#if DEBUG + if (!normal.IsNormalized()) + { + throw new ArgumentException("Argument is not normalized", nameof(normal)); + } +#endif + return 2 * Dot(normal) * normal - this; } + /// <summary> + /// Rotates this vector by `phi` radians. + /// </summary> + /// <param name="phi">The angle to rotate by, in radians.</param> + /// <returns>The rotated vector.</returns> public Vector2 Rotated(real_t phi) { real_t rads = Angle() + phi; return new Vector2(Mathf.Cos(rads), Mathf.Sin(rads)) * Length(); } + /// <summary> + /// Returns this vector with all components rounded to the nearest integer, + /// with halfway cases rounded towards the nearest multiple of two. + /// </summary> + /// <returns>The rounded vector.</returns> public Vector2 Round() { return new Vector2(Mathf.Round(x), Mathf.Round(y)); } + /// <summary> + /// Returns a vector with each component set to one or negative one, depending + /// on the signs of this vector's components, or zero if the component is zero, + /// by calling <see cref="Mathf.Sign(real_t)"/> on each component. + /// </summary> + /// <returns>A vector with all components as either `1`, `-1`, or `0`.</returns> public Vector2 Sign() { Vector2 v; @@ -264,23 +465,57 @@ namespace Godot return v; } - public Vector2 Slerp(Vector2 b, real_t t) + /// <summary> + /// Returns the result of the spherical linear interpolation between + /// this vector and `to` by amount `weight`. + /// + /// Note: Both vectors must be normalized. + /// </summary> + /// <param name="to">The destination vector for interpolation. Must be normalized.</param> + /// <param name="weight">A value on the range of 0.0 to 1.0, representing the amount of interpolation.</param> + /// <returns>The resulting vector of the interpolation.</returns> + public Vector2 Slerp(Vector2 to, real_t weight) { - real_t theta = AngleTo(b); - return Rotated(theta * t); +#if DEBUG + if (!IsNormalized()) + { + throw new InvalidOperationException("Vector2.Slerp: From vector is not normalized."); + } + if (!to.IsNormalized()) + { + throw new InvalidOperationException("Vector2.Slerp: `to` is not normalized."); + } +#endif + return Rotated(AngleTo(to) * weight); } - public Vector2 Slide(Vector2 n) + /// <summary> + /// Returns this vector slid along a plane defined by the given normal. + /// </summary> + /// <param name="normal">The normal vector defining the plane to slide on.</param> + /// <returns>The slid vector.</returns> + public Vector2 Slide(Vector2 normal) { - return this - n * Dot(n); + return this - normal * Dot(normal); } - public Vector2 Snapped(Vector2 by) + /// <summary> + /// Returns this vector with each component snapped to the nearest multiple of `step`. + /// This can also be used to round to an arbitrary number of decimals. + /// </summary> + /// <param name="step">A vector value representing the step size to snap to.</param> + /// <returns>The snapped vector.</returns> + public Vector2 Snapped(Vector2 step) { - return new Vector2(Mathf.Stepify(x, by.x), Mathf.Stepify(y, by.y)); + return new Vector2(Mathf.Stepify(x, step.x), Mathf.Stepify(y, step.y)); } - public Vector2 Tangent() + /// <summary> + /// Returns a perpendicular vector rotated 90 degrees counter-clockwise + /// compared to the original, with the same length. + /// </summary> + /// <returns>The perpendicular vector.</returns> + public Vector2 Perpendicular() { return new Vector2(y, -x); } @@ -288,7 +523,6 @@ namespace Godot // Constants private static readonly Vector2 _zero = new Vector2(0, 0); private static readonly Vector2 _one = new Vector2(1, 1); - private static readonly Vector2 _negOne = new Vector2(-1, -1); private static readonly Vector2 _inf = new Vector2(Mathf.Inf, Mathf.Inf); private static readonly Vector2 _up = new Vector2(0, -1); @@ -296,22 +530,58 @@ namespace Godot private static readonly Vector2 _right = new Vector2(1, 0); private static readonly Vector2 _left = new Vector2(-1, 0); + /// <summary> + /// Zero vector, a vector with all components set to `0`. + /// </summary> + /// <value>Equivalent to `new Vector2(0, 0)`</value> public static Vector2 Zero { get { return _zero; } } - public static Vector2 NegOne { get { return _negOne; } } + /// <summary> + /// One vector, a vector with all components set to `1`. + /// </summary> + /// <value>Equivalent to `new Vector2(1, 1)`</value> public static Vector2 One { get { return _one; } } + /// <summary> + /// Infinity vector, a vector with all components set to `Mathf.Inf`. + /// </summary> + /// <value>Equivalent to `new Vector2(Mathf.Inf, Mathf.Inf)`</value> public static Vector2 Inf { get { return _inf; } } + /// <summary> + /// Up unit vector. Y is down in 2D, so this vector points -Y. + /// </summary> + /// <value>Equivalent to `new Vector2(0, -1)`</value> public static Vector2 Up { get { return _up; } } + /// <summary> + /// Down unit vector. Y is down in 2D, so this vector points +Y. + /// </summary> + /// <value>Equivalent to `new Vector2(0, 1)`</value> public static Vector2 Down { get { return _down; } } + /// <summary> + /// Right unit vector. Represents the direction of right. + /// </summary> + /// <value>Equivalent to `new Vector2(1, 0)`</value> public static Vector2 Right { get { return _right; } } + /// <summary> + /// Left unit vector. Represents the direction of left. + /// </summary> + /// <value>Equivalent to `new Vector2(-1, 0)`</value> public static Vector2 Left { get { return _left; } } - // Constructors + /// <summary> + /// Constructs a new <see cref="Vector2"/> with the given components. + /// </summary> + /// <param name="x">The vector's X component.</param> + /// <param name="y">The vector's Y component.</param> public Vector2(real_t x, real_t y) { this.x = x; this.y = y; } + + /// <summary> + /// Constructs a new <see cref="Vector2"/> from an existing <see cref="Vector2"/>. + /// </summary> + /// <param name="v">The existing <see cref="Vector2"/>.</param> public Vector2(Vector2 v) { x = v.x; @@ -453,6 +723,12 @@ namespace Godot return x == other.x && y == other.y; } + /// <summary> + /// Returns true if this vector and `other` are approximately equal, by running + /// <see cref="Mathf.IsEqualApprox(real_t, real_t)"/> on each component. + /// </summary> + /// <param name="other">The other vector to compare.</param> + /// <returns>Whether or not the vectors are approximately equal.</returns> public bool IsEqualApprox(Vector2 other) { return Mathf.IsEqualApprox(x, other.x) && Mathf.IsEqualApprox(y, other.y); diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2i.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2i.cs index 7dc22d7918..8dd9ab2f0d 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2i.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2i.cs @@ -16,15 +16,29 @@ namespace Godot [StructLayout(LayoutKind.Sequential)] public struct Vector2i : IEquatable<Vector2i> { + /// <summary> + /// Enumerated index values for the axes. + /// Returned by <see cref="MaxAxis"/> and <see cref="MinAxis"/>. + /// </summary> public enum Axis { X = 0, Y } + /// <summary> + /// The vector's X component. Also accessible by using the index position `[0]`. + /// </summary> public int x; + /// <summary> + /// The vector's Y component. Also accessible by using the index position `[1]`. + /// </summary> public int y; + /// <summary> + /// Access vector components using their index. + /// </summary> + /// <value>`[0]` is equivalent to `.x`, `[1]` is equivalent to `.y`.</value> public int this[int index] { get @@ -55,56 +69,102 @@ namespace Godot } } + /// <summary> + /// Returns a new vector with all components in absolute values (i.e. positive). + /// </summary> + /// <returns>A vector with <see cref="Mathf.Abs(int)"/> called on each component.</returns> public Vector2i Abs() { return new Vector2i(Mathf.Abs(x), Mathf.Abs(y)); } + /// <summary> + /// Returns this vector's angle with respect to the X axis, or (1, 0) vector, in radians. + /// + /// Equivalent to the result of <see cref="Mathf.Atan2(real_t, real_t)"/> when + /// called with the vector's `y` and `x` as parameters: `Mathf.Atan2(v.y, v.x)`. + /// </summary> + /// <returns>The angle of this vector, in radians.</returns> public real_t Angle() { return Mathf.Atan2(y, x); } + /// <summary> + /// Returns the angle to the given vector, in radians. + /// </summary> + /// <param name="to">The other vector to compare this vector to.</param> + /// <returns>The angle between the two vectors, in radians.</returns> public real_t AngleTo(Vector2i to) { return Mathf.Atan2(Cross(to), Dot(to)); } + /// <summary> + /// Returns the angle between the line connecting the two points and the X axis, in radians. + /// </summary> + /// <param name="to">The other vector to compare this vector to.</param> + /// <returns>The angle between the two vectors, in radians.</returns> public real_t AngleToPoint(Vector2i to) { return Mathf.Atan2(y - to.y, x - to.x); } + /// <summary> + /// Returns the aspect ratio of this vector, the ratio of `x` to `y`. + /// </summary> + /// <returns>The `x` component divided by the `y` component.</returns> public real_t Aspect() { return x / (real_t)y; } - public Vector2i Bounce(Vector2i n) - { - return -Reflect(n); - } - + /// <summary> + /// Returns the cross product of this vector and `b`. + /// </summary> + /// <param name="b">The other vector.</param> + /// <returns>The cross product vector.</returns> public int Cross(Vector2i b) { return x * b.y - y * b.x; } + /// <summary> + /// Returns the squared distance between this vector and `b`. + /// This method runs faster than <see cref="DistanceTo"/>, so prefer it if + /// you need to compare vectors or need the squared distance for some formula. + /// </summary> + /// <param name="b">The other vector to use.</param> + /// <returns>The squared distance between the two vectors.</returns> public int DistanceSquaredTo(Vector2i b) { return (b - this).LengthSquared(); } + /// <summary> + /// Returns the distance between this vector and `b`. + /// </summary> + /// <param name="b">The other vector to use.</param> + /// <returns>The distance between the two vectors.</returns> public real_t DistanceTo(Vector2i b) { return (b - this).Length(); } + /// <summary> + /// Returns the dot product of this vector and `b`. + /// </summary> + /// <param name="b">The other vector to use.</param> + /// <returns>The dot product of the two vectors.</returns> public int Dot(Vector2i b) { return x * b.x + y * b.y; } + /// <summary> + /// Returns the length (magnitude) of this vector. + /// </summary> + /// <returns>The length of this vector.</returns> public real_t Length() { int x2 = x * x; @@ -113,6 +173,12 @@ namespace Godot return Mathf.Sqrt(x2 + y2); } + /// <summary> + /// Returns the squared length (squared magnitude) of this vector. + /// This method runs faster than <see cref="Length"/>, so prefer it if + /// you need to compare vectors or need the squared length for some formula. + /// </summary> + /// <returns>The squared length of this vector.</returns> public int LengthSquared() { int x2 = x * x; @@ -121,16 +187,31 @@ namespace Godot return x2 + y2; } + /// <summary> + /// Returns the axis of the vector's largest value. See <see cref="Axis"/>. + /// If both components are equal, this method returns <see cref="Axis.X"/>. + /// </summary> + /// <returns>The index of the largest axis.</returns> public Axis MaxAxis() { return x < y ? Axis.Y : Axis.X; } + /// <summary> + /// Returns the axis of the vector's smallest value. See <see cref="Axis"/>. + /// If both components are equal, this method returns <see cref="Axis.Y"/>. + /// </summary> + /// <returns>The index of the smallest axis.</returns> public Axis MinAxis() { - return x > y ? Axis.Y : Axis.X; + return x < y ? Axis.X : Axis.Y; } + /// <summary> + /// Returns a vector composed of the <see cref="Mathf.PosMod(int, int)"/> of this vector's components and `mod`. + /// </summary> + /// <param name="mod">A value representing the divisor of the operation.</param> + /// <returns>A vector with each component <see cref="Mathf.PosMod(int, int)"/> by `mod`.</returns> public Vector2i PosMod(int mod) { Vector2i v = this; @@ -139,6 +220,11 @@ namespace Godot return v; } + /// <summary> + /// Returns a vector composed of the <see cref="Mathf.PosMod(int, int)"/> of this vector's components and `modv`'s components. + /// </summary> + /// <param name="modv">A vector representing the divisors of the operation.</param> + /// <returns>A vector with each component <see cref="Mathf.PosMod(int, int)"/> by `modv`'s components.</returns> public Vector2i PosMod(Vector2i modv) { Vector2i v = this; @@ -147,11 +233,12 @@ namespace Godot return v; } - public Vector2i Reflect(Vector2i n) - { - return 2 * Dot(n) * n - this; - } - + /// <summary> + /// Returns a vector with each component set to one or negative one, depending + /// on the signs of this vector's components, or zero if the component is zero, + /// by calling <see cref="Mathf.Sign(int)"/> on each component. + /// </summary> + /// <returns>A vector with all components as either `1`, `-1`, or `0`.</returns> public Vector2i Sign() { Vector2i v = this; @@ -160,9 +247,14 @@ namespace Godot return v; } - public Vector2i Tangent() + /// <summary> + /// Returns a vector rotated 90 degrees counter-clockwise + /// compared to the original, with the same length. + /// </summary> + /// <returns>The perpendicular vector.</returns> + public Vector2 Perpendicular() { - return new Vector2i(y, -x); + return new Vector2(y, -x); } // Constants @@ -174,25 +266,64 @@ namespace Godot private static readonly Vector2i _right = new Vector2i(1, 0); private static readonly Vector2i _left = new Vector2i(-1, 0); + /// <summary> + /// Zero vector, a vector with all components set to `0`. + /// </summary> + /// <value>Equivalent to `new Vector2i(0, 0)`</value> public static Vector2i Zero { get { return _zero; } } + /// <summary> + /// One vector, a vector with all components set to `1`. + /// </summary> + /// <value>Equivalent to `new Vector2i(1, 1)`</value> public static Vector2i One { get { return _one; } } + /// <summary> + /// Up unit vector. Y is down in 2D, so this vector points -Y. + /// </summary> + /// <value>Equivalent to `new Vector2i(0, -1)`</value> public static Vector2i Up { get { return _up; } } + /// <summary> + /// Down unit vector. Y is down in 2D, so this vector points +Y. + /// </summary> + /// <value>Equivalent to `new Vector2i(0, 1)`</value> public static Vector2i Down { get { return _down; } } + /// <summary> + /// Right unit vector. Represents the direction of right. + /// </summary> + /// <value>Equivalent to `new Vector2i(1, 0)`</value> public static Vector2i Right { get { return _right; } } + /// <summary> + /// Left unit vector. Represents the direction of left. + /// </summary> + /// <value>Equivalent to `new Vector2i(-1, 0)`</value> public static Vector2i Left { get { return _left; } } - // Constructors + /// <summary> + /// Constructs a new <see cref="Vector2i"/> with the given components. + /// </summary> + /// <param name="x">The vector's X component.</param> + /// <param name="y">The vector's Y component.</param> public Vector2i(int x, int y) { this.x = x; this.y = y; } + + /// <summary> + /// Constructs a new <see cref="Vector2i"/> from an existing <see cref="Vector2i"/>. + /// </summary> + /// <param name="vi">The existing <see cref="Vector2i"/>.</param> public Vector2i(Vector2i vi) { this.x = vi.x; this.y = vi.y; } + + /// <summary> + /// Constructs a new <see cref="Vector2i"/> from an existing <see cref="Vector2"/> + /// by rounding the components via <see cref="Mathf.RoundToInt(real_t)"/>. + /// </summary> + /// <param name="v">The <see cref="Vector2"/> to convert.</param> public Vector2i(Vector2 v) { this.x = Mathf.RoundToInt(v.x); diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs index b26e17ecba..d9b16a6afd 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs @@ -21,6 +21,10 @@ namespace Godot [StructLayout(LayoutKind.Sequential)] public struct Vector3 : IEquatable<Vector3> { + /// <summary> + /// Enumerated index values for the axes. + /// Returned by <see cref="MaxAxis"/> and <see cref="MinAxis"/>. + /// </summary> public enum Axis { X = 0, @@ -28,10 +32,23 @@ namespace Godot Z } + /// <summary> + /// The vector's X component. Also accessible by using the index position `[0]`. + /// </summary> public real_t x; + /// <summary> + /// The vector's Y component. Also accessible by using the index position `[1]`. + /// </summary> public real_t y; + /// <summary> + /// The vector's Z component. Also accessible by using the index position `[2]`. + /// </summary> public real_t z; + /// <summary> + /// Access vector components using their index. + /// </summary> + /// <value>`[0]` is equivalent to `.x`, `[1]` is equivalent to `.y`, `[2]` is equivalent to `.z`.</value> public real_t this[int index] { get @@ -84,26 +101,49 @@ namespace Godot } } + /// <summary> + /// Returns a new vector with all components in absolute values (i.e. positive). + /// </summary> + /// <returns>A vector with <see cref="Mathf.Abs(real_t)"/> called on each component.</returns> public Vector3 Abs() { return new Vector3(Mathf.Abs(x), Mathf.Abs(y), Mathf.Abs(z)); } + /// <summary> + /// Returns the minimum angle to the given vector, in radians. + /// </summary> + /// <param name="to">The other vector to compare this vector to.</param> + /// <returns>The angle between the two vectors, in radians.</returns> public real_t AngleTo(Vector3 to) { return Mathf.Atan2(Cross(to).Length(), Dot(to)); } - public Vector3 Bounce(Vector3 n) + /// <summary> + /// Returns this vector "bounced off" from a plane defined by the given normal. + /// </summary> + /// <param name="normal">The normal vector defining the plane to bounce off. Must be normalized.</param> + /// <returns>The bounced vector.</returns> + public Vector3 Bounce(Vector3 normal) { - return -Reflect(n); + return -Reflect(normal); } + /// <summary> + /// Returns a new vector with all components rounded up (towards positive infinity). + /// </summary> + /// <returns>A vector with <see cref="Mathf.Ceil"/> called on each component.</returns> public Vector3 Ceil() { return new Vector3(Mathf.Ceil(x), Mathf.Ceil(y), Mathf.Ceil(z)); } + /// <summary> + /// Returns the cross product of this vector and `b`. + /// </summary> + /// <param name="b">The other vector.</param> + /// <returns>The cross product vector.</returns> public Vector3 Cross(Vector3 b) { return new Vector3 @@ -114,12 +154,21 @@ namespace Godot ); } + /// <summary> + /// Performs a cubic interpolation between vectors `preA`, this vector, + /// `b`, and `postB`, by the given amount `t`. + /// </summary> + /// <param name="b">The destination vector.</param> + /// <param name="preA">A vector before this vector.</param> + /// <param name="postB">A vector after `b`.</param> + /// <param name="t">A value on the range of 0.0 to 1.0, representing the amount of interpolation.</param> + /// <returns>The interpolated vector.</returns> public Vector3 CubicInterpolate(Vector3 b, Vector3 preA, Vector3 postB, real_t t) { - var p0 = preA; - var p1 = this; - var p2 = b; - var p3 = postB; + Vector3 p0 = preA; + Vector3 p1 = this; + Vector3 p2 = b; + Vector3 p3 = postB; real_t t2 = t * t; real_t t3 = t2 * t; @@ -131,41 +180,79 @@ namespace Godot ); } + /// <summary> + /// Returns the normalized vector pointing from this vector to `b`. + /// </summary> + /// <param name="b">The other vector to point towards.</param> + /// <returns>The direction from this vector to `b`.</returns> public Vector3 DirectionTo(Vector3 b) { return new Vector3(b.x - x, b.y - y, b.z - z).Normalized(); } + /// <summary> + /// Returns the squared distance between this vector and `b`. + /// This method runs faster than <see cref="DistanceTo"/>, so prefer it if + /// you need to compare vectors or need the squared distance for some formula. + /// </summary> + /// <param name="b">The other vector to use.</param> + /// <returns>The squared distance between the two vectors.</returns> public real_t DistanceSquaredTo(Vector3 b) { return (b - this).LengthSquared(); } + /// <summary> + /// Returns the distance between this vector and `b`. + /// </summary> + /// <param name="b">The other vector to use.</param> + /// <returns>The distance between the two vectors.</returns> public real_t DistanceTo(Vector3 b) { return (b - this).Length(); } + /// <summary> + /// Returns the dot product of this vector and `b`. + /// </summary> + /// <param name="b">The other vector to use.</param> + /// <returns>The dot product of the two vectors.</returns> public real_t Dot(Vector3 b) { return x * b.x + y * b.y + z * b.z; } + /// <summary> + /// Returns a new vector with all components rounded down (towards negative infinity). + /// </summary> + /// <returns>A vector with <see cref="Mathf.Floor"/> called on each component.</returns> public Vector3 Floor() { return new Vector3(Mathf.Floor(x), Mathf.Floor(y), Mathf.Floor(z)); } + /// <summary> + /// Returns the inverse of this vector. This is the same as `new Vector3(1 / v.x, 1 / v.y, 1 / v.z)`. + /// </summary> + /// <returns>The inverse of this vector.</returns> public Vector3 Inverse() { - return new Vector3(1.0f / x, 1.0f / y, 1.0f / z); + return new Vector3(1 / x, 1 / y, 1 / z); } + /// <summary> + /// Returns true if the vector is normalized, and false otherwise. + /// </summary> + /// <returns>A bool indicating whether or not the vector is normalized.</returns> public bool IsNormalized() { return Mathf.Abs(LengthSquared() - 1.0f) < Mathf.Epsilon; } + /// <summary> + /// Returns the length (magnitude) of this vector. + /// </summary> + /// <returns>The length of this vector.</returns> public real_t Length() { real_t x2 = x * x; @@ -175,6 +262,12 @@ namespace Godot return Mathf.Sqrt(x2 + y2 + z2); } + /// <summary> + /// Returns the squared length (squared magnitude) of this vector. + /// This method runs faster than <see cref="Length"/>, so prefer it if + /// you need to compare vectors or need the squared length for some formula. + /// </summary> + /// <returns>The squared length of this vector.</returns> public real_t LengthSquared() { real_t x2 = x * x; @@ -184,6 +277,13 @@ namespace Godot return x2 + y2 + z2; } + /// <summary> + /// Returns the result of the linear interpolation between + /// this vector and `to` by amount `weight`. + /// </summary> + /// <param name="to">The destination vector for interpolation.</param> + /// <param name="weight">A value on the range of 0.0 to 1.0, representing the amount of interpolation.</param> + /// <returns>The resulting vector of the interpolation.</returns> public Vector3 Lerp(Vector3 to, real_t weight) { return new Vector3 @@ -194,6 +294,13 @@ namespace Godot ); } + /// <summary> + /// Returns the result of the linear interpolation between + /// this vector and `to` by the vector amount `weight`. + /// </summary> + /// <param name="to">The destination vector for interpolation.</param> + /// <param name="weight">A vector with components on the range of 0.0 to 1.0, representing the amount of interpolation.</param> + /// <returns>The resulting vector of the interpolation.</returns> public Vector3 Lerp(Vector3 to, Vector3 weight) { return new Vector3 @@ -204,24 +311,44 @@ namespace Godot ); } - public Vector3 MoveToward(Vector3 to, real_t delta) - { - var v = this; - var vd = to - v; - var len = vd.Length(); - return len <= delta || len < Mathf.Epsilon ? to : v + vd / len * delta; - } - + /// <summary> + /// Returns the axis of the vector's largest value. See <see cref="Axis"/>. + /// If all components are equal, this method returns <see cref="Axis.X"/>. + /// </summary> + /// <returns>The index of the largest axis.</returns> public Axis MaxAxis() { return x < y ? (y < z ? Axis.Z : Axis.Y) : (x < z ? Axis.Z : Axis.X); } + /// <summary> + /// Returns the axis of the vector's smallest value. See <see cref="Axis"/>. + /// If all components are equal, this method returns <see cref="Axis.Z"/>. + /// </summary> + /// <returns>The index of the smallest axis.</returns> public Axis MinAxis() { return x < y ? (x < z ? Axis.X : Axis.Z) : (y < z ? Axis.Y : Axis.Z); } + /// <summary> + /// Moves this vector toward `to` by the fixed `delta` amount. + /// </summary> + /// <param name="to">The vector to move towards.</param> + /// <param name="delta">The amount to move towards by.</param> + /// <returns>The resulting vector.</returns> + public Vector3 MoveToward(Vector3 to, real_t delta) + { + var v = this; + var vd = to - v; + var len = vd.Length(); + return len <= delta || len < Mathf.Epsilon ? to : v + vd / len * delta; + } + + /// <summary> + /// Returns the vector scaled to unit length. Equivalent to `v / v.Length()`. + /// </summary> + /// <returns>A normalized version of the vector.</returns> public Vector3 Normalized() { var v = this; @@ -229,6 +356,11 @@ namespace Godot return v; } + /// <summary> + /// Returns the outer product with `b`. + /// </summary> + /// <param name="b">The other vector.</param> + /// <returns>A <see cref="Basis"/> representing the outer product matrix.</returns> public Basis Outer(Vector3 b) { return new Basis( @@ -238,6 +370,11 @@ namespace Godot ); } + /// <summary> + /// Returns a vector composed of the <see cref="Mathf.PosMod(real_t, real_t)"/> of this vector's components and `mod`. + /// </summary> + /// <param name="mod">A value representing the divisor of the operation.</param> + /// <returns>A vector with each component <see cref="Mathf.PosMod(real_t, real_t)"/> by `mod`.</returns> public Vector3 PosMod(real_t mod) { Vector3 v; @@ -247,6 +384,11 @@ namespace Godot return v; } + /// <summary> + /// Returns a vector composed of the <see cref="Mathf.PosMod(real_t, real_t)"/> of this vector's components and `modv`'s components. + /// </summary> + /// <param name="modv">A vector representing the divisors of the operation.</param> + /// <returns>A vector with each component <see cref="Mathf.PosMod(real_t, real_t)"/> by `modv`'s components.</returns> public Vector3 PosMod(Vector3 modv) { Vector3 v; @@ -256,30 +398,66 @@ namespace Godot return v; } + /// <summary> + /// Returns this vector projected onto another vector `b`. + /// </summary> + /// <param name="onNormal">The vector to project onto.</param> + /// <returns>The projected vector.</returns> public Vector3 Project(Vector3 onNormal) { return onNormal * (Dot(onNormal) / onNormal.LengthSquared()); } - public Vector3 Reflect(Vector3 n) + /// <summary> + /// Returns this vector reflected from a plane defined by the given `normal`. + /// </summary> + /// <param name="normal">The normal vector defining the plane to reflect from. Must be normalized.</param> + /// <returns>The reflected vector.</returns> + public Vector3 Reflect(Vector3 normal) { #if DEBUG - if (!n.IsNormalized()) - throw new ArgumentException("Argument is not normalized", nameof(n)); + if (!normal.IsNormalized()) + { + throw new ArgumentException("Argument is not normalized", nameof(normal)); + } #endif - return 2.0f * n * Dot(n) - this; + return 2.0f * Dot(normal) * normal - this; } - public Vector3 Round() + /// <summary> + /// Rotates this vector around a given `axis` vector by `phi` radians. + /// The `axis` vector must be a normalized vector. + /// </summary> + /// <param name="axis">The vector to rotate around. Must be normalized.</param> + /// <param name="phi">The angle to rotate by, in radians.</param> + /// <returns>The rotated vector.</returns> + public Vector3 Rotated(Vector3 axis, real_t phi) { - return new Vector3(Mathf.Round(x), Mathf.Round(y), Mathf.Round(z)); +#if DEBUG + if (!axis.IsNormalized()) + { + throw new ArgumentException("Argument is not normalized", nameof(axis)); + } +#endif + return new Basis(axis, phi).Xform(this); } - public Vector3 Rotated(Vector3 axis, real_t phi) + /// <summary> + /// Returns this vector with all components rounded to the nearest integer, + /// with halfway cases rounded towards the nearest multiple of two. + /// </summary> + /// <returns>The rounded vector.</returns> + public Vector3 Round() { - return new Basis(axis, phi).Xform(this); + return new Vector3(Mathf.Round(x), Mathf.Round(y), Mathf.Round(z)); } + /// <summary> + /// Returns a vector with each component set to one or negative one, depending + /// on the signs of this vector's components, or zero if the component is zero, + /// by calling <see cref="Mathf.Sign(real_t)"/> on each component. + /// </summary> + /// <returns>A vector with all components as either `1`, `-1`, or `0`.</returns> public Vector3 Sign() { Vector3 v; @@ -289,44 +467,76 @@ namespace Godot return v; } - public Vector3 Slerp(Vector3 b, real_t t) + /// <summary> + /// Returns the result of the spherical linear interpolation between + /// this vector and `to` by amount `weight`. + /// + /// Note: Both vectors must be normalized. + /// </summary> + /// <param name="to">The destination vector for interpolation. Must be normalized.</param> + /// <param name="weight">A value on the range of 0.0 to 1.0, representing the amount of interpolation.</param> + /// <returns>The resulting vector of the interpolation.</returns> + public Vector3 Slerp(Vector3 to, real_t weight) { #if DEBUG if (!IsNormalized()) - throw new InvalidOperationException("Vector3 is not normalized"); + { + throw new InvalidOperationException("Vector3.Slerp: From vector is not normalized."); + } + if (!to.IsNormalized()) + { + throw new InvalidOperationException("Vector3.Slerp: `to` is not normalized."); + } #endif - real_t theta = AngleTo(b); - return Rotated(Cross(b), theta * t); + real_t theta = AngleTo(to); + return Rotated(Cross(to), theta * weight); } - public Vector3 Slide(Vector3 n) + /// <summary> + /// Returns this vector slid along a plane defined by the given normal. + /// </summary> + /// <param name="normal">The normal vector defining the plane to slide on.</param> + /// <returns>The slid vector.</returns> + public Vector3 Slide(Vector3 normal) { - return this - n * Dot(n); + return this - normal * Dot(normal); } - public Vector3 Snapped(Vector3 by) + /// <summary> + /// Returns this vector with each component snapped to the nearest multiple of `step`. + /// This can also be used to round to an arbitrary number of decimals. + /// </summary> + /// <param name="step">A vector value representing the step size to snap to.</param> + /// <returns>The snapped vector.</returns> + public Vector3 Snapped(Vector3 step) { return new Vector3 ( - Mathf.Stepify(x, by.x), - Mathf.Stepify(y, by.y), - Mathf.Stepify(z, by.z) + Mathf.Stepify(x, step.x), + Mathf.Stepify(y, step.y), + Mathf.Stepify(z, step.z) ); } + /// <summary> + /// Returns a diagonal matrix with the vector as main diagonal. + /// + /// This is equivalent to a Basis with no rotation or shearing and + /// this vector's components set as the scale. + /// </summary> + /// <returns>A Basis with the vector as its main diagonal.</returns> public Basis ToDiagonalMatrix() { return new Basis( - x, 0f, 0f, - 0f, y, 0f, - 0f, 0f, z + x, 0, 0, + 0, y, 0, + 0, 0, z ); } // Constants private static readonly Vector3 _zero = new Vector3(0, 0, 0); private static readonly Vector3 _one = new Vector3(1, 1, 1); - private static readonly Vector3 _negOne = new Vector3(-1, -1, -1); private static readonly Vector3 _inf = new Vector3(Mathf.Inf, Mathf.Inf, Mathf.Inf); private static readonly Vector3 _up = new Vector3(0, 1, 0); @@ -336,25 +546,74 @@ namespace Godot private static readonly Vector3 _forward = new Vector3(0, 0, -1); private static readonly Vector3 _back = new Vector3(0, 0, 1); + /// <summary> + /// Zero vector, a vector with all components set to `0`. + /// </summary> + /// <value>Equivalent to `new Vector3(0, 0, 0)`</value> public static Vector3 Zero { get { return _zero; } } + /// <summary> + /// One vector, a vector with all components set to `1`. + /// </summary> + /// <value>Equivalent to `new Vector3(1, 1, 1)`</value> public static Vector3 One { get { return _one; } } - public static Vector3 NegOne { get { return _negOne; } } + /// <summary> + /// Infinity vector, a vector with all components set to `Mathf.Inf`. + /// </summary> + /// <value>Equivalent to `new Vector3(Mathf.Inf, Mathf.Inf, Mathf.Inf)`</value> public static Vector3 Inf { get { return _inf; } } + /// <summary> + /// Up unit vector. + /// </summary> + /// <value>Equivalent to `new Vector3(0, 1, 0)`</value> public static Vector3 Up { get { return _up; } } + /// <summary> + /// Down unit vector. + /// </summary> + /// <value>Equivalent to `new Vector3(0, -1, 0)`</value> public static Vector3 Down { get { return _down; } } + /// <summary> + /// Right unit vector. Represents the local direction of right, + /// and the global direction of east. + /// </summary> + /// <value>Equivalent to `new Vector3(1, 0, 0)`</value> public static Vector3 Right { get { return _right; } } + /// <summary> + /// Left unit vector. Represents the local direction of left, + /// and the global direction of west. + /// </summary> + /// <value>Equivalent to `new Vector3(-1, 0, 0)`</value> public static Vector3 Left { get { return _left; } } + /// <summary> + /// Forward unit vector. Represents the local direction of forward, + /// and the global direction of north. + /// </summary> + /// <value>Equivalent to `new Vector3(0, 0, -1)`</value> public static Vector3 Forward { get { return _forward; } } + /// <summary> + /// Back unit vector. Represents the local direction of back, + /// and the global direction of south. + /// </summary> + /// <value>Equivalent to `new Vector3(0, 0, 1)`</value> public static Vector3 Back { get { return _back; } } - // Constructors + /// <summary> + /// Constructs a new <see cref="Vector3"/> with the given components. + /// </summary> + /// <param name="x">The vector's X component.</param> + /// <param name="y">The vector's Y component.</param> + /// <param name="z">The vector's Z component.</param> public Vector3(real_t x, real_t y, real_t z) { this.x = x; this.y = y; this.z = z; } + + /// <summary> + /// Constructs a new <see cref="Vector3"/> from an existing <see cref="Vector3"/>. + /// </summary> + /// <param name="v">The existing <see cref="Vector3"/>.</param> public Vector3(Vector3 v) { x = v.x; @@ -515,6 +774,12 @@ namespace Godot return x == other.x && y == other.y && z == other.z; } + /// <summary> + /// Returns true if this vector and `other` are approximately equal, by running + /// <see cref="Mathf.IsEqualApprox(real_t, real_t)"/> on each component. + /// </summary> + /// <param name="other">The other vector to compare.</param> + /// <returns>Whether or not the vectors are approximately equal.</returns> public bool IsEqualApprox(Vector3 other) { return Mathf.IsEqualApprox(x, other.x) && Mathf.IsEqualApprox(y, other.y) && Mathf.IsEqualApprox(z, other.z); diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3i.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3i.cs index c17f900131..bf25ba9cb3 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3i.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3i.cs @@ -16,6 +16,10 @@ namespace Godot [StructLayout(LayoutKind.Sequential)] public struct Vector3i : IEquatable<Vector3i> { + /// <summary> + /// Enumerated index values for the axes. + /// Returned by <see cref="MaxAxis"/> and <see cref="MinAxis"/>. + /// </summary> public enum Axis { X = 0, @@ -23,10 +27,23 @@ namespace Godot Z } + /// <summary> + /// The vector's X component. Also accessible by using the index position `[0]`. + /// </summary> public int x; + /// <summary> + /// The vector's Y component. Also accessible by using the index position `[1]`. + /// </summary> public int y; + /// <summary> + /// The vector's Z component. Also accessible by using the index position `[2]`. + /// </summary> public int z; + /// <summary> + /// Access vector components using their index. + /// </summary> + /// <value>`[0]` is equivalent to `.x`, `[1]` is equivalent to `.y`, `[2]` is equivalent to `.z`.</value> public int this[int index] { get @@ -62,39 +79,51 @@ namespace Godot } } + /// <summary> + /// Returns a new vector with all components in absolute values (i.e. positive). + /// </summary> + /// <returns>A vector with <see cref="Mathf.Abs(int)"/> called on each component.</returns> public Vector3i Abs() { - Vector3i v = this; - if (v.x < 0) - { - v.x = -v.x; - } - if (v.y < 0) - { - v.y = -v.y; - } - if (v.z < 0) - { - v.z = -v.z; - } - return v; + return new Vector3i(Mathf.Abs(x), Mathf.Abs(y), Mathf.Abs(z)); } + /// <summary> + /// Returns the squared distance between this vector and `b`. + /// This method runs faster than <see cref="DistanceTo"/>, so prefer it if + /// you need to compare vectors or need the squared distance for some formula. + /// </summary> + /// <param name="b">The other vector to use.</param> + /// <returns>The squared distance between the two vectors.</returns> public int DistanceSquaredTo(Vector3i b) { return (b - this).LengthSquared(); } + /// <summary> + /// Returns the distance between this vector and `b`. + /// </summary> + /// <param name="b">The other vector to use.</param> + /// <returns>The distance between the two vectors.</returns> public real_t DistanceTo(Vector3i b) { return (b - this).Length(); } + /// <summary> + /// Returns the dot product of this vector and `b`. + /// </summary> + /// <param name="b">The other vector to use.</param> + /// <returns>The dot product of the two vectors.</returns> public int Dot(Vector3i b) { return x * b.x + y * b.y + z * b.z; } + /// <summary> + /// Returns the length (magnitude) of this vector. + /// </summary> + /// <returns>The length of this vector.</returns> public real_t Length() { int x2 = x * x; @@ -104,6 +133,12 @@ namespace Godot return Mathf.Sqrt(x2 + y2 + z2); } + /// <summary> + /// Returns the squared length (squared magnitude) of this vector. + /// This method runs faster than <see cref="Length"/>, so prefer it if + /// you need to compare vectors or need the squared length for some formula. + /// </summary> + /// <returns>The squared length of this vector.</returns> public int LengthSquared() { int x2 = x * x; @@ -113,16 +148,31 @@ namespace Godot return x2 + y2 + z2; } + /// <summary> + /// Returns the axis of the vector's largest value. See <see cref="Axis"/>. + /// If all components are equal, this method returns <see cref="Axis.X"/>. + /// </summary> + /// <returns>The index of the largest axis.</returns> public Axis MaxAxis() { return x < y ? (y < z ? Axis.Z : Axis.Y) : (x < z ? Axis.Z : Axis.X); } + /// <summary> + /// Returns the axis of the vector's smallest value. See <see cref="Axis"/>. + /// If all components are equal, this method returns <see cref="Axis.Z"/>. + /// </summary> + /// <returns>The index of the smallest axis.</returns> public Axis MinAxis() { return x < y ? (x < z ? Axis.X : Axis.Z) : (y < z ? Axis.Y : Axis.Z); } + /// <summary> + /// Returns a vector composed of the <see cref="Mathf.PosMod(int, int)"/> of this vector's components and `mod`. + /// </summary> + /// <param name="mod">A value representing the divisor of the operation.</param> + /// <returns>A vector with each component <see cref="Mathf.PosMod(int, int)"/> by `mod`.</returns> public Vector3i PosMod(int mod) { Vector3i v = this; @@ -132,6 +182,11 @@ namespace Godot return v; } + /// <summary> + /// Returns a vector composed of the <see cref="Mathf.PosMod(int, int)"/> of this vector's components and `modv`'s components. + /// </summary> + /// <param name="modv">A vector representing the divisors of the operation.</param> + /// <returns>A vector with each component <see cref="Mathf.PosMod(int, int)"/> by `modv`'s components.</returns> public Vector3i PosMod(Vector3i modv) { Vector3i v = this; @@ -141,6 +196,12 @@ namespace Godot return v; } + /// <summary> + /// Returns a vector with each component set to one or negative one, depending + /// on the signs of this vector's components, or zero if the component is zero, + /// by calling <see cref="Mathf.Sign(int)"/> on each component. + /// </summary> + /// <returns>A vector with all components as either `1`, `-1`, or `0`.</returns> public Vector3i Sign() { Vector3i v = this; @@ -161,29 +222,81 @@ namespace Godot private static readonly Vector3i _forward = new Vector3i(0, 0, -1); private static readonly Vector3i _back = new Vector3i(0, 0, 1); + /// <summary> + /// Zero vector, a vector with all components set to `0`. + /// </summary> + /// <value>Equivalent to `new Vector3i(0, 0, 0)`</value> public static Vector3i Zero { get { return _zero; } } + /// <summary> + /// One vector, a vector with all components set to `1`. + /// </summary> + /// <value>Equivalent to `new Vector3i(1, 1, 1)`</value> public static Vector3i One { get { return _one; } } + /// <summary> + /// Up unit vector. + /// </summary> + /// <value>Equivalent to `new Vector3i(0, 1, 0)`</value> public static Vector3i Up { get { return _up; } } + /// <summary> + /// Down unit vector. + /// </summary> + /// <value>Equivalent to `new Vector3i(0, -1, 0)`</value> public static Vector3i Down { get { return _down; } } + /// <summary> + /// Right unit vector. Represents the local direction of right, + /// and the global direction of east. + /// </summary> + /// <value>Equivalent to `new Vector3i(1, 0, 0)`</value> public static Vector3i Right { get { return _right; } } + /// <summary> + /// Left unit vector. Represents the local direction of left, + /// and the global direction of west. + /// </summary> + /// <value>Equivalent to `new Vector3i(-1, 0, 0)`</value> public static Vector3i Left { get { return _left; } } + /// <summary> + /// Forward unit vector. Represents the local direction of forward, + /// and the global direction of north. + /// </summary> + /// <value>Equivalent to `new Vector3i(0, 0, -1)`</value> public static Vector3i Forward { get { return _forward; } } + /// <summary> + /// Back unit vector. Represents the local direction of back, + /// and the global direction of south. + /// </summary> + /// <value>Equivalent to `new Vector3i(0, 0, 1)`</value> public static Vector3i Back { get { return _back; } } - // Constructors + /// <summary> + /// Constructs a new <see cref="Vector3i"/> with the given components. + /// </summary> + /// <param name="x">The vector's X component.</param> + /// <param name="y">The vector's Y component.</param> + /// <param name="z">The vector's Z component.</param> public Vector3i(int x, int y, int z) { this.x = x; this.y = y; this.z = z; } + + /// <summary> + /// Constructs a new <see cref="Vector3i"/> from an existing <see cref="Vector3i"/>. + /// </summary> + /// <param name="vi">The existing <see cref="Vector3i"/>.</param> public Vector3i(Vector3i vi) { this.x = vi.x; this.y = vi.y; this.z = vi.z; } + + /// <summary> + /// Constructs a new <see cref="Vector3i"/> from an existing <see cref="Vector3"/> + /// by rounding the components via <see cref="Mathf.RoundToInt(real_t)"/>. + /// </summary> + /// <param name="v">The <see cref="Vector3"/> to convert.</param> public Vector3i(Vector3 v) { this.x = Mathf.RoundToInt(v.x); diff --git a/modules/mono/utils/osx_utils.cpp b/modules/mono/utils/osx_utils.cpp index 8e3e51e688..e68466b1cf 100644 --- a/modules/mono/utils/osx_utils.cpp +++ b/modules/mono/utils/osx_utils.cpp @@ -38,24 +38,21 @@ #include <CoreServices/CoreServices.h> bool osx_is_app_bundle_installed(const String &p_bundle_id) { - CFURLRef app_url = nullptr; CFStringRef bundle_id = CFStringCreateWithCString(nullptr, p_bundle_id.utf8(), kCFStringEncodingUTF8); - OSStatus result = LSFindApplicationForInfo(kLSUnknownCreator, bundle_id, nullptr, nullptr, &app_url); + CFArrayRef result = LSCopyApplicationURLsForBundleIdentifier(bundle_id, nullptr); CFRelease(bundle_id); - if (app_url) - CFRelease(app_url); - - switch (result) { - case noErr: + if (result) { + if (CFArrayGetCount(result) > 0) { + CFRelease(result); return true; - case kLSApplicationNotFoundErr: - break; - default: - break; + } else { + CFRelease(result); + return false; + } + } else { + return false; } - - return false; } #endif diff --git a/modules/visual_script/visual_script_editor.cpp b/modules/visual_script/visual_script_editor.cpp index b7ca3c882b..2fcd9332a1 100644 --- a/modules/visual_script/visual_script_editor.cpp +++ b/modules/visual_script/visual_script_editor.cpp @@ -4660,10 +4660,10 @@ void VisualScriptEditor::_member_option(int p_option) { } } -void VisualScriptEditor::add_syntax_highlighter(SyntaxHighlighter *p_highlighter) { +void VisualScriptEditor::add_syntax_highlighter(Ref<EditorSyntaxHighlighter> p_highlighter) { } -void VisualScriptEditor::set_syntax_highlighter(SyntaxHighlighter *p_highlighter) { +void VisualScriptEditor::set_syntax_highlighter(Ref<EditorSyntaxHighlighter> p_highlighter) { } void VisualScriptEditor::_bind_methods() { @@ -4686,6 +4686,8 @@ void VisualScriptEditor::_bind_methods() { ClassDB::bind_method("_update_members", &VisualScriptEditor::_update_members); ClassDB::bind_method("_generic_search", &VisualScriptEditor::_generic_search); + + ClassDB::bind_method(D_METHOD("add_syntax_highlighter", "highlighter"), &VisualScriptEditor::add_syntax_highlighter); } VisualScriptEditor::VisualScriptEditor() { diff --git a/modules/visual_script/visual_script_editor.h b/modules/visual_script/visual_script_editor.h index 28418965b8..e59618e120 100644 --- a/modules/visual_script/visual_script_editor.h +++ b/modules/visual_script/visual_script_editor.h @@ -288,8 +288,8 @@ protected: static void _bind_methods(); public: - virtual void add_syntax_highlighter(SyntaxHighlighter *p_highlighter) override; - virtual void set_syntax_highlighter(SyntaxHighlighter *p_highlighter) override; + virtual void add_syntax_highlighter(Ref<EditorSyntaxHighlighter> p_highlighter) override; + virtual void set_syntax_highlighter(Ref<EditorSyntaxHighlighter> p_highlighter) override; virtual void apply_code() override; virtual RES get_edited_resource() const override; |