diff options
459 files changed, 14913 insertions, 12036 deletions
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 09ff2454ee..ebbbe345fd 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -73,7 +73,6 @@ doc_classes/* @godotengine/documentation /modules/minimp3/ @godotengine/audio /modules/ogg/ @godotengine/audio /modules/opus/ @godotengine/audio -/modules/stb_vorbis/ @godotengine/audio /modules/theora/ @godotengine/audio /modules/vorbis/ @godotengine/audio /modules/webm/ @godotengine/audio diff --git a/COPYRIGHT.txt b/COPYRIGHT.txt index 4a38d0457c..e46cc6f3f4 100644 --- a/COPYRIGHT.txt +++ b/COPYRIGHT.txt @@ -367,7 +367,6 @@ Copyright: 2016-2020, Aras Pranckevicius License: public-domain or Unlicense or Expat Files: ./thirdparty/misc/stb_rect_pack.h - ./thirdparty/misc/stb_vorbis.c Comment: stb libraries Copyright: Sean Barrett License: public-domain or Unlicense or Expat diff --git a/core/SCsub b/core/SCsub index d9167b8f83..14dfa3487f 100644 --- a/core/SCsub +++ b/core/SCsub @@ -183,6 +183,7 @@ SConscript("os/SCsub") SConscript("math/SCsub") SConscript("crypto/SCsub") SConscript("io/SCsub") +SConscript("multiplayer/SCsub") SConscript("debugger/SCsub") SConscript("input/SCsub") SConscript("variant/SCsub") diff --git a/core/core_constants.cpp b/core/core_constants.cpp index 4f3f1fd16e..721e5ae622 100644 --- a/core/core_constants.cpp +++ b/core/core_constants.cpp @@ -31,6 +31,7 @@ #include "core_constants.h" #include "core/input/input_event.h" +#include "core/multiplayer/multiplayer.h" #include "core/object/class_db.h" #include "core/os/keyboard.h" #include "core/variant/variant.h" @@ -593,6 +594,7 @@ void register_global_constants() { BIND_CORE_ENUM_CONSTANT(PROPERTY_USAGE_DEFERRED_SET_RESOURCE); BIND_CORE_ENUM_CONSTANT(PROPERTY_USAGE_EDITOR_INSTANTIATE_OBJECT); BIND_CORE_ENUM_CONSTANT(PROPERTY_USAGE_EDITOR_BASIC_SETTING); + BIND_CORE_ENUM_CONSTANT(PROPERTY_USAGE_ARRAY); BIND_CORE_ENUM_CONSTANT(PROPERTY_USAGE_DEFAULT); BIND_CORE_ENUM_CONSTANT(PROPERTY_USAGE_DEFAULT_INTL); @@ -609,6 +611,15 @@ void register_global_constants() { BIND_CORE_ENUM_CONSTANT(METHOD_FLAG_OBJECT_CORE); BIND_CORE_ENUM_CONSTANT(METHOD_FLAGS_DEFAULT); + // rpc + BIND_CORE_ENUM_CONSTANT_CUSTOM("RPC_MODE_DISABLED", Multiplayer::RPC_MODE_DISABLED); + BIND_CORE_ENUM_CONSTANT_CUSTOM("RPC_MODE_ANY", Multiplayer::RPC_MODE_ANY); + BIND_CORE_ENUM_CONSTANT_CUSTOM("RPC_MODE_AUTH", Multiplayer::RPC_MODE_AUTHORITY); + + BIND_CORE_ENUM_CONSTANT_CUSTOM("TRANSFER_MODE_UNRELIABLE", Multiplayer::TRANSFER_MODE_UNRELIABLE); + BIND_CORE_ENUM_CONSTANT_CUSTOM("TRANSFER_MODE_ORDERED", Multiplayer::TRANSFER_MODE_ORDERED); + BIND_CORE_ENUM_CONSTANT_CUSTOM("TRANSFER_MODE_RELIABLE", Multiplayer::TRANSFER_MODE_RELIABLE); + BIND_CORE_ENUM_CONSTANT_CUSTOM("TYPE_NIL", Variant::NIL); BIND_CORE_ENUM_CONSTANT_CUSTOM("TYPE_BOOL", Variant::BOOL); BIND_CORE_ENUM_CONSTANT_CUSTOM("TYPE_INT", Variant::INT); diff --git a/core/extension/gdnative_interface.cpp b/core/extension/gdnative_interface.cpp index de107b4156..a65bdd16dc 100644 --- a/core/extension/gdnative_interface.cpp +++ b/core/extension/gdnative_interface.cpp @@ -661,6 +661,116 @@ static const char32_t *gdnative_string_operator_index_const(const GDNativeString return &self->ptr()[p_index]; } +/* Packed array functions */ + +static uint8_t *gdnative_packed_byte_array_operator_index(GDNativeTypePtr p_self, GDNativeInt p_index) { + PackedByteArray *self = (PackedByteArray *)p_self; + ERR_FAIL_INDEX_V(p_index, self->size(), nullptr); + return &self->ptrw()[p_index]; +} + +static const uint8_t *gdnative_packed_byte_array_operator_index_const(const GDNativeTypePtr p_self, GDNativeInt p_index) { + const PackedByteArray *self = (const PackedByteArray *)p_self; + ERR_FAIL_INDEX_V(p_index, self->size(), nullptr); + return &self->ptr()[p_index]; +} + +static GDNativeTypePtr gdnative_packed_color_array_operator_index(GDNativeTypePtr p_self, GDNativeInt p_index) { + PackedColorArray *self = (PackedColorArray *)p_self; + ERR_FAIL_INDEX_V(p_index, self->size(), nullptr); + return (GDNativeTypePtr)&self->ptrw()[p_index]; +} + +static GDNativeTypePtr gdnative_packed_color_array_operator_index_const(const GDNativeTypePtr p_self, GDNativeInt p_index) { + const PackedColorArray *self = (const PackedColorArray *)p_self; + ERR_FAIL_INDEX_V(p_index, self->size(), nullptr); + return (GDNativeTypePtr)&self->ptr()[p_index]; +} + +static float *gdnative_packed_float32_array_operator_index(GDNativeTypePtr p_self, GDNativeInt p_index) { + PackedFloat32Array *self = (PackedFloat32Array *)p_self; + ERR_FAIL_INDEX_V(p_index, self->size(), nullptr); + return &self->ptrw()[p_index]; +} + +static const float *gdnative_packed_float32_array_operator_index_const(const GDNativeTypePtr p_self, GDNativeInt p_index) { + const PackedFloat32Array *self = (const PackedFloat32Array *)p_self; + ERR_FAIL_INDEX_V(p_index, self->size(), nullptr); + return &self->ptr()[p_index]; +} + +static double *gdnative_packed_float64_array_operator_index(GDNativeTypePtr p_self, GDNativeInt p_index) { + PackedFloat64Array *self = (PackedFloat64Array *)p_self; + ERR_FAIL_INDEX_V(p_index, self->size(), nullptr); + return &self->ptrw()[p_index]; +} + +static const double *gdnative_packed_float64_array_operator_index_const(const GDNativeTypePtr p_self, GDNativeInt p_index) { + const PackedFloat64Array *self = (const PackedFloat64Array *)p_self; + ERR_FAIL_INDEX_V(p_index, self->size(), nullptr); + return &self->ptr()[p_index]; +} + +static int32_t *gdnative_packed_int32_array_operator_index(GDNativeTypePtr p_self, GDNativeInt p_index) { + PackedInt32Array *self = (PackedInt32Array *)p_self; + ERR_FAIL_INDEX_V(p_index, self->size(), nullptr); + return &self->ptrw()[p_index]; +} + +static const int32_t *gdnative_packed_int32_array_operator_index_const(const GDNativeTypePtr p_self, GDNativeInt p_index) { + const PackedInt32Array *self = (const PackedInt32Array *)p_self; + ERR_FAIL_INDEX_V(p_index, self->size(), nullptr); + return &self->ptr()[p_index]; +} + +static int64_t *gdnative_packed_int64_array_operator_index(GDNativeTypePtr p_self, GDNativeInt p_index) { + PackedInt64Array *self = (PackedInt64Array *)p_self; + ERR_FAIL_INDEX_V(p_index, self->size(), nullptr); + return &self->ptrw()[p_index]; +} + +static const int64_t *gdnative_packed_int64_array_operator_index_const(const GDNativeTypePtr p_self, GDNativeInt p_index) { + const PackedInt64Array *self = (const PackedInt64Array *)p_self; + ERR_FAIL_INDEX_V(p_index, self->size(), nullptr); + return &self->ptr()[p_index]; +} + +static GDNativeStringPtr gdnative_packed_string_array_operator_index(GDNativeTypePtr p_self, GDNativeInt p_index) { + PackedStringArray *self = (PackedStringArray *)p_self; + ERR_FAIL_INDEX_V(p_index, self->size(), nullptr); + return (GDNativeStringPtr)&self->ptrw()[p_index]; +} + +static GDNativeStringPtr gdnative_packed_string_array_operator_index_const(const GDNativeTypePtr p_self, GDNativeInt p_index) { + const PackedStringArray *self = (const PackedStringArray *)p_self; + ERR_FAIL_INDEX_V(p_index, self->size(), nullptr); + return (GDNativeStringPtr)&self->ptr()[p_index]; +} + +static GDNativeTypePtr gdnative_packed_vector2_array_operator_index(GDNativeTypePtr p_self, GDNativeInt p_index) { + PackedVector2Array *self = (PackedVector2Array *)p_self; + ERR_FAIL_INDEX_V(p_index, self->size(), nullptr); + return (GDNativeTypePtr)&self->ptrw()[p_index]; +} + +static GDNativeTypePtr gdnative_packed_vector2_array_operator_index_const(const GDNativeTypePtr p_self, GDNativeInt p_index) { + const PackedVector2Array *self = (const PackedVector2Array *)p_self; + ERR_FAIL_INDEX_V(p_index, self->size(), nullptr); + return (GDNativeTypePtr)&self->ptr()[p_index]; +} + +static GDNativeTypePtr gdnative_packed_vector3_array_operator_index(GDNativeTypePtr p_self, GDNativeInt p_index) { + PackedVector3Array *self = (PackedVector3Array *)p_self; + ERR_FAIL_INDEX_V(p_index, self->size(), nullptr); + return (GDNativeTypePtr)&self->ptrw()[p_index]; +} + +static GDNativeTypePtr gdnative_packed_vector3_array_operator_index_const(const GDNativeTypePtr p_self, GDNativeInt p_index) { + const PackedVector3Array *self = (const PackedVector3Array *)p_self; + ERR_FAIL_INDEX_V(p_index, self->size(), nullptr); + return (GDNativeTypePtr)&self->ptr()[p_index]; +} + /* OBJECT API */ static void gdnative_object_method_bind_call(const GDNativeMethodBindPtr p_method_bind, GDNativeObjectPtr p_instance, const GDNativeVariantPtr *p_args, GDNativeInt p_arg_count, GDNativeVariantPtr r_return, GDNativeCallError *r_error) { @@ -843,6 +953,32 @@ void gdnative_setup_interface(GDNativeInterface *p_interface) { gdni.string_operator_index = gdnative_string_operator_index; gdni.string_operator_index_const = gdnative_string_operator_index_const; + /* Packed array functions */ + + gdni.packed_byte_array_operator_index = gdnative_packed_byte_array_operator_index; + gdni.packed_byte_array_operator_index_const = gdnative_packed_byte_array_operator_index_const; + + gdni.packed_color_array_operator_index = gdnative_packed_color_array_operator_index; + gdni.packed_color_array_operator_index_const = gdnative_packed_color_array_operator_index_const; + + gdni.packed_float32_array_operator_index = gdnative_packed_float32_array_operator_index; + gdni.packed_float32_array_operator_index_const = gdnative_packed_float32_array_operator_index_const; + gdni.packed_float64_array_operator_index = gdnative_packed_float64_array_operator_index; + gdni.packed_float64_array_operator_index_const = gdnative_packed_float64_array_operator_index_const; + + gdni.packed_int32_array_operator_index = gdnative_packed_int32_array_operator_index; + gdni.packed_int32_array_operator_index_const = gdnative_packed_int32_array_operator_index_const; + gdni.packed_int64_array_operator_index = gdnative_packed_int64_array_operator_index; + gdni.packed_int64_array_operator_index_const = gdnative_packed_int64_array_operator_index_const; + + gdni.packed_string_array_operator_index = gdnative_packed_string_array_operator_index; + gdni.packed_string_array_operator_index_const = gdnative_packed_string_array_operator_index_const; + + gdni.packed_vector2_array_operator_index = gdnative_packed_vector2_array_operator_index; + gdni.packed_vector2_array_operator_index_const = gdnative_packed_vector2_array_operator_index_const; + gdni.packed_vector3_array_operator_index = gdnative_packed_vector3_array_operator_index; + gdni.packed_vector3_array_operator_index_const = gdnative_packed_vector3_array_operator_index_const; + /* OBJECT */ gdni.object_method_bind_call = gdnative_object_method_bind_call; diff --git a/core/extension/gdnative_interface.h b/core/extension/gdnative_interface.h index 3a5b04429c..63f4b0917c 100644 --- a/core/extension/gdnative_interface.h +++ b/core/extension/gdnative_interface.h @@ -387,6 +387,32 @@ typedef struct { char32_t *(*string_operator_index)(GDNativeStringPtr p_self, GDNativeInt p_index); const char32_t *(*string_operator_index_const)(const GDNativeStringPtr p_self, GDNativeInt p_index); + /* Packed array functions */ + + uint8_t *(*packed_byte_array_operator_index)(GDNativeTypePtr p_self, GDNativeInt p_index); // p_self should be a PackedByteArray + const uint8_t *(*packed_byte_array_operator_index_const)(const GDNativeTypePtr p_self, GDNativeInt p_index); // p_self should be a PackedByteArray + + GDNativeTypePtr (*packed_color_array_operator_index)(GDNativeTypePtr p_self, GDNativeInt p_index); // p_self should be a PackedColorArray, returns Color ptr + GDNativeTypePtr (*packed_color_array_operator_index_const)(const GDNativeTypePtr p_self, GDNativeInt p_index); // p_self should be a PackedColorArray, returns Color ptr + + float *(*packed_float32_array_operator_index)(GDNativeTypePtr p_self, GDNativeInt p_index); // p_self should be a PackedFloat32Array + const float *(*packed_float32_array_operator_index_const)(const GDNativeTypePtr p_self, GDNativeInt p_index); // p_self should be a PackedFloat32Array + double *(*packed_float64_array_operator_index)(GDNativeTypePtr p_self, GDNativeInt p_index); // p_self should be a PackedFloat64Array + const double *(*packed_float64_array_operator_index_const)(const GDNativeTypePtr p_self, GDNativeInt p_index); // p_self should be a PackedFloat64Array + + int32_t *(*packed_int32_array_operator_index)(GDNativeTypePtr p_self, GDNativeInt p_index); // p_self should be a PackedInt32Array + const int32_t *(*packed_int32_array_operator_index_const)(const GDNativeTypePtr p_self, GDNativeInt p_index); // p_self should be a PackedInt32Array + int64_t *(*packed_int64_array_operator_index)(GDNativeTypePtr p_self, GDNativeInt p_index); // p_self should be a PackedInt32Array + const int64_t *(*packed_int64_array_operator_index_const)(const GDNativeTypePtr p_self, GDNativeInt p_index); // p_self should be a PackedInt32Array + + GDNativeStringPtr (*packed_string_array_operator_index)(GDNativeTypePtr p_self, GDNativeInt p_index); // p_self should be a PackedStringArray + GDNativeStringPtr (*packed_string_array_operator_index_const)(const GDNativeTypePtr p_self, GDNativeInt p_index); // p_self should be a PackedStringArray + + GDNativeTypePtr (*packed_vector2_array_operator_index)(GDNativeTypePtr p_self, GDNativeInt p_index); // p_self should be a PackedVector2Array, returns Vector2 ptr + GDNativeTypePtr (*packed_vector2_array_operator_index_const)(const GDNativeTypePtr p_self, GDNativeInt p_index); // p_self should be a PackedVector2Array, returns Vector2 ptr + GDNativeTypePtr (*packed_vector3_array_operator_index)(GDNativeTypePtr p_self, GDNativeInt p_index); // p_self should be a PackedVector3Array, returns Vector3 ptr + GDNativeTypePtr (*packed_vector3_array_operator_index_const)(const GDNativeTypePtr p_self, GDNativeInt p_index); // p_self should be a PackedVector3Array, returns Vector3 ptr + /* OBJECT */ void (*object_method_bind_call)(const GDNativeMethodBindPtr p_method_bind, GDNativeObjectPtr p_instance, const GDNativeVariantPtr *p_args, GDNativeInt p_arg_count, GDNativeVariantPtr r_ret, GDNativeCallError *r_error); diff --git a/core/input/input_map.cpp b/core/input/input_map.cpp index c7ca65f61a..816d9d1082 100644 --- a/core/input/input_map.cpp +++ b/core/input/input_map.cpp @@ -317,36 +317,36 @@ static const _BuiltinActionDisplayName _builtin_action_display_names[] = { { "ui_text_dedent", TTRC("Dedent") }, { "ui_text_backspace", TTRC("Backspace") }, { "ui_text_backspace_word", TTRC("Backspace Word") }, - { "ui_text_backspace_word.OSX", TTRC("Backspace Word") }, + { "ui_text_backspace_word.osx", TTRC("Backspace Word") }, { "ui_text_backspace_all_to_left", TTRC("Backspace all to Left") }, - { "ui_text_backspace_all_to_left.OSX", TTRC("Backspace all to Left") }, + { "ui_text_backspace_all_to_left.osx", TTRC("Backspace all to Left") }, { "ui_text_delete", TTRC("Delete") }, { "ui_text_delete_word", TTRC("Delete Word") }, - { "ui_text_delete_word.OSX", TTRC("Delete Word") }, + { "ui_text_delete_word.osx", TTRC("Delete Word") }, { "ui_text_delete_all_to_right", TTRC("Delete all to Right") }, - { "ui_text_delete_all_to_right.OSX", TTRC("Delete all to Right") }, + { "ui_text_delete_all_to_right.osx", TTRC("Delete all to Right") }, { "ui_text_caret_left", TTRC("Caret Left") }, { "ui_text_caret_word_left", TTRC("Caret Word Left") }, - { "ui_text_caret_word_left.OSX", TTRC("Caret Word Left") }, + { "ui_text_caret_word_left.osx", TTRC("Caret Word Left") }, { "ui_text_caret_right", TTRC("Caret Right") }, { "ui_text_caret_word_right", TTRC("Caret Word Right") }, - { "ui_text_caret_word_right.OSX", TTRC("Caret Word Right") }, + { "ui_text_caret_word_right.osx", TTRC("Caret Word Right") }, { "ui_text_caret_up", TTRC("Caret Up") }, { "ui_text_caret_down", TTRC("Caret Down") }, { "ui_text_caret_line_start", TTRC("Caret Line Start") }, - { "ui_text_caret_line_start.OSX", TTRC("Caret Line Start") }, + { "ui_text_caret_line_start.osx", TTRC("Caret Line Start") }, { "ui_text_caret_line_end", TTRC("Caret Line End") }, - { "ui_text_caret_line_end.OSX", TTRC("Caret Line End") }, + { "ui_text_caret_line_end.osx", TTRC("Caret Line End") }, { "ui_text_caret_page_up", TTRC("Caret Page Up") }, { "ui_text_caret_page_down", TTRC("Caret Page Down") }, { "ui_text_caret_document_start", TTRC("Caret Document Start") }, - { "ui_text_caret_document_start.OSX", TTRC("Caret Document Start") }, + { "ui_text_caret_document_start.osx", TTRC("Caret Document Start") }, { "ui_text_caret_document_end", TTRC("Caret Document End") }, - { "ui_text_caret_document_end.OSX", TTRC("Caret Document End") }, + { "ui_text_caret_document_end.osx", TTRC("Caret Document End") }, { "ui_text_scroll_up", TTRC("Scroll Up") }, - { "ui_text_scroll_up.OSX", TTRC("Scroll Up") }, + { "ui_text_scroll_up.osx", TTRC("Scroll Up") }, { "ui_text_scroll_down", TTRC("Scroll Down") }, - { "ui_text_scroll_down.OSX", TTRC("Scroll Down") }, + { "ui_text_scroll_down.osx", TTRC("Scroll Down") }, { "ui_text_select_all", TTRC("Select All") }, { "ui_text_select_word_under_caret", TTRC("Select Word Under Caret") }, { "ui_text_toggle_insert_mode", TTRC("Toggle Insert Mode") }, @@ -516,14 +516,14 @@ const OrderedHashMap<String, List<Ref<InputEvent>>> &InputMap::get_builtins() { inputs = List<Ref<InputEvent>>(); inputs.push_back(InputEventKey::create_reference(KEY_BACKSPACE | KEY_MASK_ALT)); - default_builtin_cache.insert("ui_text_backspace_word.OSX", inputs); + default_builtin_cache.insert("ui_text_backspace_word.osx", inputs); inputs = List<Ref<InputEvent>>(); default_builtin_cache.insert("ui_text_backspace_all_to_left", inputs); inputs = List<Ref<InputEvent>>(); inputs.push_back(InputEventKey::create_reference(KEY_BACKSPACE | KEY_MASK_CMD)); - default_builtin_cache.insert("ui_text_backspace_all_to_left.OSX", inputs); + default_builtin_cache.insert("ui_text_backspace_all_to_left.osx", inputs); inputs = List<Ref<InputEvent>>(); inputs.push_back(InputEventKey::create_reference(KEY_DELETE)); @@ -535,14 +535,14 @@ const OrderedHashMap<String, List<Ref<InputEvent>>> &InputMap::get_builtins() { inputs = List<Ref<InputEvent>>(); inputs.push_back(InputEventKey::create_reference(KEY_DELETE | KEY_MASK_ALT)); - default_builtin_cache.insert("ui_text_delete_word.OSX", inputs); + default_builtin_cache.insert("ui_text_delete_word.osx", inputs); inputs = List<Ref<InputEvent>>(); default_builtin_cache.insert("ui_text_delete_all_to_right", inputs); inputs = List<Ref<InputEvent>>(); inputs.push_back(InputEventKey::create_reference(KEY_DELETE | KEY_MASK_CMD)); - default_builtin_cache.insert("ui_text_delete_all_to_right.OSX", inputs); + default_builtin_cache.insert("ui_text_delete_all_to_right.osx", inputs); // Text Caret Movement Left/Right @@ -556,7 +556,7 @@ const OrderedHashMap<String, List<Ref<InputEvent>>> &InputMap::get_builtins() { inputs = List<Ref<InputEvent>>(); inputs.push_back(InputEventKey::create_reference(KEY_LEFT | KEY_MASK_ALT)); - default_builtin_cache.insert("ui_text_caret_word_left.OSX", inputs); + default_builtin_cache.insert("ui_text_caret_word_left.osx", inputs); inputs = List<Ref<InputEvent>>(); inputs.push_back(InputEventKey::create_reference(KEY_RIGHT)); @@ -568,7 +568,7 @@ const OrderedHashMap<String, List<Ref<InputEvent>>> &InputMap::get_builtins() { inputs = List<Ref<InputEvent>>(); inputs.push_back(InputEventKey::create_reference(KEY_RIGHT | KEY_MASK_ALT)); - default_builtin_cache.insert("ui_text_caret_word_right.OSX", inputs); + default_builtin_cache.insert("ui_text_caret_word_right.osx", inputs); // Text Caret Movement Up/Down @@ -589,7 +589,7 @@ const OrderedHashMap<String, List<Ref<InputEvent>>> &InputMap::get_builtins() { inputs = List<Ref<InputEvent>>(); inputs.push_back(InputEventKey::create_reference(KEY_A | KEY_MASK_CTRL)); inputs.push_back(InputEventKey::create_reference(KEY_LEFT | KEY_MASK_CMD)); - default_builtin_cache.insert("ui_text_caret_line_start.OSX", inputs); + default_builtin_cache.insert("ui_text_caret_line_start.osx", inputs); inputs = List<Ref<InputEvent>>(); inputs.push_back(InputEventKey::create_reference(KEY_END)); @@ -598,7 +598,7 @@ const OrderedHashMap<String, List<Ref<InputEvent>>> &InputMap::get_builtins() { inputs = List<Ref<InputEvent>>(); inputs.push_back(InputEventKey::create_reference(KEY_E | KEY_MASK_CTRL)); inputs.push_back(InputEventKey::create_reference(KEY_RIGHT | KEY_MASK_CMD)); - default_builtin_cache.insert("ui_text_caret_line_end.OSX", inputs); + default_builtin_cache.insert("ui_text_caret_line_end.osx", inputs); // Text Caret Movement Page Up/Down @@ -618,7 +618,7 @@ const OrderedHashMap<String, List<Ref<InputEvent>>> &InputMap::get_builtins() { inputs = List<Ref<InputEvent>>(); inputs.push_back(InputEventKey::create_reference(KEY_UP | KEY_MASK_CMD)); - default_builtin_cache.insert("ui_text_caret_document_start.OSX", inputs); + default_builtin_cache.insert("ui_text_caret_document_start.osx", inputs); inputs = List<Ref<InputEvent>>(); inputs.push_back(InputEventKey::create_reference(KEY_END | KEY_MASK_CMD)); @@ -626,7 +626,7 @@ const OrderedHashMap<String, List<Ref<InputEvent>>> &InputMap::get_builtins() { inputs = List<Ref<InputEvent>>(); inputs.push_back(InputEventKey::create_reference(KEY_DOWN | KEY_MASK_CMD)); - default_builtin_cache.insert("ui_text_caret_document_end.OSX", inputs); + default_builtin_cache.insert("ui_text_caret_document_end.osx", inputs); // Text Scrolling @@ -636,7 +636,7 @@ const OrderedHashMap<String, List<Ref<InputEvent>>> &InputMap::get_builtins() { inputs = List<Ref<InputEvent>>(); inputs.push_back(InputEventKey::create_reference(KEY_UP | KEY_MASK_CMD | KEY_MASK_ALT)); - default_builtin_cache.insert("ui_text_scroll_up.OSX", inputs); + default_builtin_cache.insert("ui_text_scroll_up.osx", inputs); inputs = List<Ref<InputEvent>>(); inputs.push_back(InputEventKey::create_reference(KEY_DOWN | KEY_MASK_CMD)); @@ -644,7 +644,7 @@ const OrderedHashMap<String, List<Ref<InputEvent>>> &InputMap::get_builtins() { inputs = List<Ref<InputEvent>>(); inputs.push_back(InputEventKey::create_reference(KEY_DOWN | KEY_MASK_CMD | KEY_MASK_ALT)); - default_builtin_cache.insert("ui_text_scroll_down.OSX", inputs); + default_builtin_cache.insert("ui_text_scroll_down.osx", inputs); // Text Misc @@ -702,11 +702,11 @@ const OrderedHashMap<String, List<Ref<InputEvent>>> &InputMap::get_builtins() { void InputMap::load_default() { OrderedHashMap<String, List<Ref<InputEvent>>> builtins = get_builtins(); - // List of Builtins which have an override for OSX. + // List of Builtins which have an override for macOS. Vector<String> osx_builtins; for (OrderedHashMap<String, List<Ref<InputEvent>>>::Element E = builtins.front(); E; E = E.next()) { - if (String(E.key()).ends_with(".OSX")) { - // Strip .OSX from name: some_input_name.OSX -> some_input_name + if (String(E.key()).ends_with(".osx")) { + // Strip .osx from name: some_input_name.osx -> some_input_name osx_builtins.push_back(String(E.key()).split(".")[0]); } } @@ -717,13 +717,13 @@ void InputMap::load_default() { String override_for = fullname.split(".").size() > 1 ? fullname.split(".")[1] : ""; #ifdef APPLE_STYLE_KEYS - if (osx_builtins.has(name) && override_for != "OSX") { - // Name has osx builtin but this particular one is for non-osx systems - so skip. + if (osx_builtins.has(name) && override_for != "osx") { + // Name has `osx` builtin but this particular one is for non-macOS systems - so skip. continue; } #else - if (override_for == "OSX") { - // Override for OSX - not needed on non-osx platforms. + if (override_for == "osx") { + // Override for macOS - not needed on non-macOS platforms. continue; } #endif @@ -746,3 +746,7 @@ InputMap::InputMap() { ERR_FAIL_COND_MSG(singleton, "Singleton in InputMap already exist."); singleton = this; } + +InputMap::~InputMap() { + singleton = nullptr; +} diff --git a/core/input/input_map.h b/core/input/input_map.h index a2d3952f94..c724fdb142 100644 --- a/core/input/input_map.h +++ b/core/input/input_map.h @@ -95,6 +95,7 @@ public: const OrderedHashMap<String, List<Ref<InputEvent>>> &get_builtins(); InputMap(); + ~InputMap(); }; #endif // INPUT_MAP_H diff --git a/core/io/multiplayer_api.cpp b/core/io/multiplayer_api.cpp deleted file mode 100644 index c145225751..0000000000 --- a/core/io/multiplayer_api.cpp +++ /dev/null @@ -1,1150 +0,0 @@ -/*************************************************************************/ -/* multiplayer_api.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#include "multiplayer_api.h" - -#include "core/debugger/engine_debugger.h" -#include "core/io/marshalls.h" -#include "core/io/multiplayer_replicator.h" -#include "scene/main/node.h" - -#include <stdint.h> - -#ifdef DEBUG_ENABLED -#include "core/os/os.h" -#endif - -String _get_rpc_md5(const Node *p_node) { - String rpc_list; - const Vector<MultiplayerAPI::RPCConfig> node_config = p_node->get_node_rpc_methods(); - for (int i = 0; i < node_config.size(); i++) { - rpc_list += String(node_config[i].name); - } - if (p_node->get_script_instance()) { - const Vector<MultiplayerAPI::RPCConfig> script_config = p_node->get_script_instance()->get_rpc_methods(); - for (int i = 0; i < script_config.size(); i++) { - rpc_list += String(script_config[i].name); - } - } - return rpc_list.md5_text(); -} - -const MultiplayerAPI::RPCConfig _get_rpc_config(const Node *p_node, const StringName &p_method, uint16_t &r_id) { - const Vector<MultiplayerAPI::RPCConfig> node_config = p_node->get_node_rpc_methods(); - for (int i = 0; i < node_config.size(); i++) { - if (node_config[i].name == p_method) { - r_id = ((uint16_t)i) | (1 << 15); - return node_config[i]; - } - } - if (p_node->get_script_instance()) { - const Vector<MultiplayerAPI::RPCConfig> script_config = p_node->get_script_instance()->get_rpc_methods(); - for (int i = 0; i < script_config.size(); i++) { - if (script_config[i].name == p_method) { - r_id = (uint16_t)i; - return script_config[i]; - } - } - } - return MultiplayerAPI::RPCConfig(); -} - -const MultiplayerAPI::RPCConfig _get_rpc_config_by_id(Node *p_node, uint16_t p_id) { - Vector<MultiplayerAPI::RPCConfig> config; - uint16_t id = p_id; - if (id & (1 << 15)) { - id = id & ~(1 << 15); - config = p_node->get_node_rpc_methods(); - } else if (p_node->get_script_instance()) { - config = p_node->get_script_instance()->get_rpc_methods(); - } - if (id < config.size()) { - return config[id]; - } - return MultiplayerAPI::RPCConfig(); -} - -_FORCE_INLINE_ bool _can_call_mode(Node *p_node, MultiplayerAPI::RPCMode mode, int p_remote_id) { - switch (mode) { - case MultiplayerAPI::RPC_MODE_DISABLED: { - return false; - } break; - case MultiplayerAPI::RPC_MODE_ANY: { - return true; - } break; - case MultiplayerAPI::RPC_MODE_AUTHORITY: { - return !p_node->is_network_authority() && p_remote_id == p_node->get_network_authority(); - } break; - } - - return false; -} - -void MultiplayerAPI::poll() { - if (!network_peer.is_valid() || network_peer->get_connection_status() == MultiplayerPeer::CONNECTION_DISCONNECTED) { - return; - } - - network_peer->poll(); - - if (!network_peer.is_valid()) { // It's possible that polling might have resulted in a disconnection, so check here. - return; - } - - while (network_peer->get_available_packet_count()) { - int sender = network_peer->get_packet_peer(); - const uint8_t *packet; - int len; - - Error err = network_peer->get_packet(&packet, len); - if (err != OK) { - ERR_PRINT("Error getting packet!"); - break; // Something is wrong! - } - - rpc_sender_id = sender; - _process_packet(sender, packet, len); - rpc_sender_id = 0; - - if (!network_peer.is_valid()) { - break; // It's also possible that a packet or RPC caused a disconnection, so also check here. - } - } - if (network_peer.is_valid() && network_peer->get_connection_status() == MultiplayerPeer::CONNECTION_CONNECTED) { - replicator->poll(); - } -} - -void MultiplayerAPI::clear() { - replicator->clear(); - connected_peers.clear(); - path_get_cache.clear(); - path_send_cache.clear(); - packet_cache.clear(); - last_send_cache_id = 1; -} - -void MultiplayerAPI::set_root_node(Node *p_node) { - root_node = p_node; -} - -Node *MultiplayerAPI::get_root_node() { - return root_node; -} - -void MultiplayerAPI::set_network_peer(const Ref<MultiplayerPeer> &p_peer) { - if (p_peer == network_peer) { - return; // Nothing to do - } - - ERR_FAIL_COND_MSG(p_peer.is_valid() && p_peer->get_connection_status() == MultiplayerPeer::CONNECTION_DISCONNECTED, - "Supplied MultiplayerPeer must be connecting or connected."); - - if (network_peer.is_valid()) { - network_peer->disconnect("peer_connected", callable_mp(this, &MultiplayerAPI::_add_peer)); - network_peer->disconnect("peer_disconnected", callable_mp(this, &MultiplayerAPI::_del_peer)); - network_peer->disconnect("connection_succeeded", callable_mp(this, &MultiplayerAPI::_connected_to_server)); - network_peer->disconnect("connection_failed", callable_mp(this, &MultiplayerAPI::_connection_failed)); - network_peer->disconnect("server_disconnected", callable_mp(this, &MultiplayerAPI::_server_disconnected)); - clear(); - } - - network_peer = p_peer; - - if (network_peer.is_valid()) { - network_peer->connect("peer_connected", callable_mp(this, &MultiplayerAPI::_add_peer)); - network_peer->connect("peer_disconnected", callable_mp(this, &MultiplayerAPI::_del_peer)); - network_peer->connect("connection_succeeded", callable_mp(this, &MultiplayerAPI::_connected_to_server)); - network_peer->connect("connection_failed", callable_mp(this, &MultiplayerAPI::_connection_failed)); - network_peer->connect("server_disconnected", callable_mp(this, &MultiplayerAPI::_server_disconnected)); - } -} - -Ref<MultiplayerPeer> MultiplayerAPI::get_network_peer() const { - return network_peer; -} - -#ifdef DEBUG_ENABLED -void _profile_node_data(const String &p_what, ObjectID p_id) { - if (EngineDebugger::is_profiling("multiplayer")) { - Array values; - values.push_back("node"); - values.push_back(p_id); - values.push_back(p_what); - EngineDebugger::profiler_add_frame_data("multiplayer", values); - } -} - -void _profile_bandwidth_data(const String &p_inout, int p_size) { - if (EngineDebugger::is_profiling("multiplayer")) { - Array values; - values.push_back("bandwidth"); - values.push_back(p_inout); - values.push_back(OS::get_singleton()->get_ticks_msec()); - values.push_back(p_size); - EngineDebugger::profiler_add_frame_data("multiplayer", values); - } -} -#endif - -// Returns the packet size stripping the node path added when the node is not yet cached. -int get_packet_len(uint32_t p_node_target, int p_packet_len) { - if (p_node_target & 0x80000000) { - int ofs = p_node_target & 0x7FFFFFFF; - return p_packet_len - (p_packet_len - ofs); - } else { - return p_packet_len; - } -} - -void MultiplayerAPI::_process_packet(int p_from, const uint8_t *p_packet, int p_packet_len) { - ERR_FAIL_COND_MSG(root_node == nullptr, "Multiplayer root node was not initialized. If you are using custom multiplayer, remember to set the root node via MultiplayerAPI.set_root_node before using it."); - ERR_FAIL_COND_MSG(p_packet_len < 1, "Invalid packet received. Size too small."); - -#ifdef DEBUG_ENABLED - _profile_bandwidth_data("in", p_packet_len); -#endif - - // Extract the `packet_type` from the LSB three bits: - uint8_t packet_type = p_packet[0] & 7; - - switch (packet_type) { - case NETWORK_COMMAND_SIMPLIFY_PATH: { - _process_simplify_path(p_from, p_packet, p_packet_len); - } break; - - case NETWORK_COMMAND_CONFIRM_PATH: { - _process_confirm_path(p_from, p_packet, p_packet_len); - } break; - - case NETWORK_COMMAND_REMOTE_CALL: { - // Extract packet meta - int packet_min_size = 1; - int name_id_offset = 1; - ERR_FAIL_COND_MSG(p_packet_len < packet_min_size, "Invalid packet received. Size too small."); - // Compute the meta size, which depends on the compression level. - int node_id_compression = (p_packet[0] & 24) >> NODE_ID_COMPRESSION_SHIFT; - int name_id_compression = (p_packet[0] & 32) >> NAME_ID_COMPRESSION_SHIFT; - - switch (node_id_compression) { - case NETWORK_NODE_ID_COMPRESSION_8: - packet_min_size += 1; - name_id_offset += 1; - break; - case NETWORK_NODE_ID_COMPRESSION_16: - packet_min_size += 2; - name_id_offset += 2; - break; - case NETWORK_NODE_ID_COMPRESSION_32: - packet_min_size += 4; - name_id_offset += 4; - break; - default: - ERR_FAIL_MSG("Was not possible to extract the node id compression mode."); - } - switch (name_id_compression) { - case NETWORK_NAME_ID_COMPRESSION_8: - packet_min_size += 1; - break; - case NETWORK_NAME_ID_COMPRESSION_16: - packet_min_size += 2; - break; - default: - ERR_FAIL_MSG("Was not possible to extract the name id compression mode."); - } - ERR_FAIL_COND_MSG(p_packet_len < packet_min_size, "Invalid packet received. Size too small."); - - uint32_t node_target = 0; - switch (node_id_compression) { - case NETWORK_NODE_ID_COMPRESSION_8: - node_target = p_packet[1]; - break; - case NETWORK_NODE_ID_COMPRESSION_16: - node_target = decode_uint16(p_packet + 1); - break; - case NETWORK_NODE_ID_COMPRESSION_32: - node_target = decode_uint32(p_packet + 1); - break; - default: - // Unreachable, checked before. - CRASH_NOW(); - } - - Node *node = _process_get_node(p_from, p_packet, node_target, p_packet_len); - ERR_FAIL_COND_MSG(node == nullptr, "Invalid packet received. Requested node was not found."); - - uint16_t name_id = 0; - switch (name_id_compression) { - case NETWORK_NAME_ID_COMPRESSION_8: - name_id = p_packet[name_id_offset]; - break; - case NETWORK_NAME_ID_COMPRESSION_16: - name_id = decode_uint16(p_packet + name_id_offset); - break; - default: - // Unreachable, checked before. - CRASH_NOW(); - } - - const int packet_len = get_packet_len(node_target, p_packet_len); - _process_rpc(node, name_id, p_from, p_packet, packet_len, packet_min_size); - } break; - - case NETWORK_COMMAND_RAW: { - _process_raw(p_from, p_packet, p_packet_len); - } break; - case NETWORK_COMMAND_SPAWN: { - replicator->process_spawn_despawn(p_from, p_packet, p_packet_len, true); - } break; - case NETWORK_COMMAND_DESPAWN: { - replicator->process_spawn_despawn(p_from, p_packet, p_packet_len, false); - } break; - case NETWORK_COMMAND_SYNC: { - replicator->process_sync(p_from, p_packet, p_packet_len); - } break; - } -} - -Node *MultiplayerAPI::_process_get_node(int p_from, const uint8_t *p_packet, uint32_t p_node_target, int p_packet_len) { - Node *node = nullptr; - - if (p_node_target & 0x80000000) { - // Use full path (not cached yet). - int ofs = p_node_target & 0x7FFFFFFF; - - ERR_FAIL_COND_V_MSG(ofs >= p_packet_len, nullptr, "Invalid packet received. Size smaller than declared."); - - String paths; - paths.parse_utf8((const char *)&p_packet[ofs], p_packet_len - ofs); - - NodePath np = paths; - - node = root_node->get_node(np); - - if (!node) { - ERR_PRINT("Failed to get path from RPC: " + String(np) + "."); - } - return node; - } else { - // Use cached path. - return get_cached_node(p_from, p_node_target); - } -} - -void MultiplayerAPI::_process_rpc(Node *p_node, const uint16_t p_rpc_method_id, int p_from, const uint8_t *p_packet, int p_packet_len, int p_offset) { - ERR_FAIL_COND_MSG(p_offset > p_packet_len, "Invalid packet received. Size too small."); - - // Check that remote can call the RPC on this node. - const RPCConfig config = _get_rpc_config_by_id(p_node, p_rpc_method_id); - ERR_FAIL_COND(config.name == StringName()); - - bool can_call = _can_call_mode(p_node, config.rpc_mode, p_from); - ERR_FAIL_COND_MSG(!can_call, "RPC '" + String(config.name) + "' is not allowed on node " + p_node->get_path() + " from: " + itos(p_from) + ". Mode is " + itos((int)config.rpc_mode) + ", authority is " + itos(p_node->get_network_authority()) + "."); - - int argc = 0; - bool byte_only = false; - - const bool byte_only_or_no_args = ((p_packet[0] & 64) >> BYTE_ONLY_OR_NO_ARGS_SHIFT) == 1; - if (byte_only_or_no_args) { - if (p_offset < p_packet_len) { - // This packet contains only bytes. - argc = 1; - byte_only = true; - } else { - // This rpc calls a method without parameters. - } - } else { - // Normal variant, takes the argument count from the packet. - ERR_FAIL_COND_MSG(p_offset >= p_packet_len, "Invalid packet received. Size too small."); - argc = p_packet[p_offset]; - p_offset += 1; - } - - Vector<Variant> args; - Vector<const Variant *> argp; - args.resize(argc); - argp.resize(argc); - -#ifdef DEBUG_ENABLED - _profile_node_data("in_rpc", p_node->get_instance_id()); -#endif - - if (byte_only) { - Vector<uint8_t> pure_data; - const int len = p_packet_len - p_offset; - pure_data.resize(len); - memcpy(pure_data.ptrw(), &p_packet[p_offset], len); - args.write[0] = pure_data; - argp.write[0] = &args[0]; - p_offset += len; - } else { - for (int i = 0; i < argc; i++) { - ERR_FAIL_COND_MSG(p_offset >= p_packet_len, "Invalid packet received. Size too small."); - - int vlen; - Error err = decode_and_decompress_variant(args.write[i], &p_packet[p_offset], p_packet_len - p_offset, &vlen); - ERR_FAIL_COND_MSG(err != OK, "Invalid packet received. Unable to decode RPC argument."); - - argp.write[i] = &args[i]; - p_offset += vlen; - } - } - - Callable::CallError ce; - - p_node->call(config.name, (const Variant **)argp.ptr(), argc, ce); - if (ce.error != Callable::CallError::CALL_OK) { - String error = Variant::get_call_error_text(p_node, config.name, (const Variant **)argp.ptr(), argc, ce); - error = "RPC - " + error; - ERR_PRINT(error); - } -} - -void MultiplayerAPI::_process_simplify_path(int p_from, const uint8_t *p_packet, int p_packet_len) { - ERR_FAIL_COND_MSG(p_packet_len < 38, "Invalid packet received. Size too small."); - int ofs = 1; - - String methods_md5; - methods_md5.parse_utf8((const char *)(p_packet + ofs), 32); - ofs += 33; - - int id = decode_uint32(&p_packet[ofs]); - ofs += 4; - - String paths; - paths.parse_utf8((const char *)(p_packet + ofs), p_packet_len - ofs); - - NodePath path = paths; - - if (!path_get_cache.has(p_from)) { - path_get_cache[p_from] = PathGetCache(); - } - - Node *node = root_node->get_node(path); - ERR_FAIL_COND(node == nullptr); - const bool valid_rpc_checksum = _get_rpc_md5(node) == methods_md5; - if (valid_rpc_checksum == false) { - ERR_PRINT("The rpc node checksum failed. Make sure to have the same methods on both nodes. Node path: " + path); - } - - PathGetCache::NodeInfo ni; - ni.path = path; - - path_get_cache[p_from].nodes[id] = ni; - - // Encode path to send ack. - CharString pname = String(path).utf8(); - int len = encode_cstring(pname.get_data(), nullptr); - - Vector<uint8_t> packet; - - packet.resize(1 + 1 + len); - packet.write[0] = NETWORK_COMMAND_CONFIRM_PATH; - packet.write[1] = valid_rpc_checksum; - encode_cstring(pname.get_data(), &packet.write[2]); - - network_peer->set_transfer_channel(0); - network_peer->set_transfer_mode(MultiplayerPeer::TRANSFER_MODE_RELIABLE); - network_peer->set_target_peer(p_from); - network_peer->put_packet(packet.ptr(), packet.size()); -} - -void MultiplayerAPI::_process_confirm_path(int p_from, const uint8_t *p_packet, int p_packet_len) { - ERR_FAIL_COND_MSG(p_packet_len < 3, "Invalid packet received. Size too small."); - - const bool valid_rpc_checksum = p_packet[1]; - - String paths; - paths.parse_utf8((const char *)&p_packet[2], p_packet_len - 2); - - NodePath path = paths; - - if (valid_rpc_checksum == false) { - ERR_PRINT("The rpc node checksum failed. Make sure to have the same methods on both nodes. Node path: " + path); - } - - PathSentCache *psc = path_send_cache.getptr(path); - ERR_FAIL_COND_MSG(!psc, "Invalid packet received. Tries to confirm a path which was not found in cache."); - - Map<int, bool>::Element *E = psc->confirmed_peers.find(p_from); - ERR_FAIL_COND_MSG(!E, "Invalid packet received. Source peer was not found in cache for the given path."); - E->get() = true; -} - -bool MultiplayerAPI::_send_confirm_path(Node *p_node, NodePath p_path, PathSentCache *psc, int p_target) { - bool has_all_peers = true; - List<int> peers_to_add; // If one is missing, take note to add it. - - for (Set<int>::Element *E = connected_peers.front(); E; E = E->next()) { - if (p_target < 0 && E->get() == -p_target) { - continue; // Continue, excluded. - } - - if (p_target > 0 && E->get() != p_target) { - continue; // Continue, not for this peer. - } - - Map<int, bool>::Element *F = psc->confirmed_peers.find(E->get()); - - if (!F || !F->get()) { - // Path was not cached, or was cached but is unconfirmed. - if (!F) { - // Not cached at all, take note. - peers_to_add.push_back(E->get()); - } - - has_all_peers = false; - } - } - - if (peers_to_add.size() > 0) { - // Those that need to be added, send a message for this. - - // Encode function name. - const CharString path = String(p_path).utf8(); - const int path_len = encode_cstring(path.get_data(), nullptr); - - // Extract MD5 from rpc methods list. - const String methods_md5 = _get_rpc_md5(p_node); - const int methods_md5_len = 33; // 32 + 1 for the `0` that is added by the encoder. - - Vector<uint8_t> packet; - packet.resize(1 + 4 + path_len + methods_md5_len); - int ofs = 0; - - packet.write[ofs] = NETWORK_COMMAND_SIMPLIFY_PATH; - ofs += 1; - - ofs += encode_cstring(methods_md5.utf8().get_data(), &packet.write[ofs]); - - ofs += encode_uint32(psc->id, &packet.write[ofs]); - - ofs += encode_cstring(path.get_data(), &packet.write[ofs]); - - for (int &E : peers_to_add) { - network_peer->set_target_peer(E); // To all of you. - network_peer->set_transfer_channel(0); - network_peer->set_transfer_mode(MultiplayerPeer::TRANSFER_MODE_RELIABLE); - network_peer->put_packet(packet.ptr(), packet.size()); - - psc->confirmed_peers.insert(E, false); // Insert into confirmed, but as false since it was not confirmed. - } - } - - return has_all_peers; -} - -// The variant is compressed and encoded; The first byte contains all the meta -// information and the format is: -// - The first LSB 5 bits are used for the variant type. -// - The next two bits are used to store the encoding mode. -// - The most significant is used to store the boolean value. -#define VARIANT_META_TYPE_MASK 0x1F -#define VARIANT_META_EMODE_MASK 0x60 -#define VARIANT_META_BOOL_MASK 0x80 -#define ENCODE_8 0 << 5 -#define ENCODE_16 1 << 5 -#define ENCODE_32 2 << 5 -#define ENCODE_64 3 << 5 -Error MultiplayerAPI::encode_and_compress_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len) { - // Unreachable because `VARIANT_MAX` == 27 and `ENCODE_VARIANT_MASK` == 31 - CRASH_COND(p_variant.get_type() > VARIANT_META_TYPE_MASK); - - uint8_t *buf = r_buffer; - r_len = 0; - uint8_t encode_mode = 0; - - switch (p_variant.get_type()) { - case Variant::BOOL: { - if (buf) { - // We still have 1 free bit in the meta, so let's use it. - buf[0] = (p_variant.operator bool()) ? (1 << 7) : 0; - buf[0] |= encode_mode | p_variant.get_type(); - } - r_len += 1; - } break; - case Variant::INT: { - if (buf) { - // Reserve the first byte for the meta. - buf += 1; - } - r_len += 1; - int64_t val = p_variant; - if (val <= (int64_t)INT8_MAX && val >= (int64_t)INT8_MIN) { - // Use 8 bit - encode_mode = ENCODE_8; - if (buf) { - buf[0] = val; - } - r_len += 1; - } else if (val <= (int64_t)INT16_MAX && val >= (int64_t)INT16_MIN) { - // Use 16 bit - encode_mode = ENCODE_16; - if (buf) { - encode_uint16(val, buf); - } - r_len += 2; - } else if (val <= (int64_t)INT32_MAX && val >= (int64_t)INT32_MIN) { - // Use 32 bit - encode_mode = ENCODE_32; - if (buf) { - encode_uint32(val, buf); - } - r_len += 4; - } else { - // Use 64 bit - encode_mode = ENCODE_64; - if (buf) { - encode_uint64(val, buf); - } - r_len += 8; - } - // Store the meta - if (buf) { - buf -= 1; - buf[0] = encode_mode | p_variant.get_type(); - } - } break; - default: - // Any other case is not yet compressed. - Error err = encode_variant(p_variant, r_buffer, r_len, allow_object_decoding); - if (err != OK) { - return err; - } - if (r_buffer) { - // The first byte is not used by the marshalling, so store the type - // so we know how to decompress and decode this variant. - r_buffer[0] = p_variant.get_type(); - } - } - - return OK; -} - -Error MultiplayerAPI::decode_and_decompress_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int *r_len) { - const uint8_t *buf = p_buffer; - int len = p_len; - - ERR_FAIL_COND_V(len < 1, ERR_INVALID_DATA); - uint8_t type = buf[0] & VARIANT_META_TYPE_MASK; - uint8_t encode_mode = buf[0] & VARIANT_META_EMODE_MASK; - - ERR_FAIL_COND_V(type >= Variant::VARIANT_MAX, ERR_INVALID_DATA); - - switch (type) { - case Variant::BOOL: { - bool val = (buf[0] & VARIANT_META_BOOL_MASK) > 0; - r_variant = val; - if (r_len) { - *r_len = 1; - } - } break; - case Variant::INT: { - buf += 1; - len -= 1; - if (r_len) { - *r_len = 1; - } - if (encode_mode == ENCODE_8) { - // 8 bits. - ERR_FAIL_COND_V(len < 1, ERR_INVALID_DATA); - int8_t val = buf[0]; - r_variant = val; - if (r_len) { - (*r_len) += 1; - } - } else if (encode_mode == ENCODE_16) { - // 16 bits. - ERR_FAIL_COND_V(len < 2, ERR_INVALID_DATA); - int16_t val = decode_uint16(buf); - r_variant = val; - if (r_len) { - (*r_len) += 2; - } - } else if (encode_mode == ENCODE_32) { - // 32 bits. - ERR_FAIL_COND_V(len < 4, ERR_INVALID_DATA); - int32_t val = decode_uint32(buf); - r_variant = val; - if (r_len) { - (*r_len) += 4; - } - } else { - // 64 bits. - ERR_FAIL_COND_V(len < 8, ERR_INVALID_DATA); - int64_t val = decode_uint64(buf); - r_variant = val; - if (r_len) { - (*r_len) += 8; - } - } - } break; - default: - Error err = decode_variant(r_variant, p_buffer, p_len, r_len, allow_object_decoding); - if (err != OK) { - return err; - } - } - - return OK; -} - -void MultiplayerAPI::_send_rpc(Node *p_from, int p_to, uint16_t p_rpc_id, const RPCConfig &p_config, const StringName &p_name, const Variant **p_arg, int p_argcount) { - ERR_FAIL_COND_MSG(network_peer.is_null(), "Attempt to remote call/set when networking is not active in SceneTree."); - - ERR_FAIL_COND_MSG(network_peer->get_connection_status() == MultiplayerPeer::CONNECTION_CONNECTING, "Attempt to remote call/set when networking is not connected yet in SceneTree."); - - ERR_FAIL_COND_MSG(network_peer->get_connection_status() == MultiplayerPeer::CONNECTION_DISCONNECTED, "Attempt to remote call/set when networking is disconnected."); - - ERR_FAIL_COND_MSG(p_argcount > 255, "Too many arguments >255."); - - if (p_to != 0 && !connected_peers.has(ABS(p_to))) { - ERR_FAIL_COND_MSG(p_to == network_peer->get_unique_id(), "Attempt to remote call/set yourself! unique ID: " + itos(network_peer->get_unique_id()) + "."); - - ERR_FAIL_MSG("Attempt to remote call unexisting ID: " + itos(p_to) + "."); - } - - NodePath from_path = (root_node->get_path()).rel_path_to(p_from->get_path()); - ERR_FAIL_COND_MSG(from_path.is_empty(), "Unable to send RPC. Relative path is empty. THIS IS LIKELY A BUG IN THE ENGINE!"); - - // See if the path is cached. - PathSentCache *psc = path_send_cache.getptr(from_path); - if (!psc) { - // Path is not cached, create. - path_send_cache[from_path] = PathSentCache(); - psc = path_send_cache.getptr(from_path); - psc->id = last_send_cache_id++; - } - - // See if all peers have cached path (if so, call can be fast). - const bool has_all_peers = _send_confirm_path(p_from, from_path, psc, p_to); - - // Create base packet, lots of hardcode because it must be tight. - - int ofs = 0; - -#define MAKE_ROOM(m_amount) \ - if (packet_cache.size() < m_amount) \ - packet_cache.resize(m_amount); - - // Encode meta. - // The meta is composed by a single byte that contains (starting from the least significant bit): - // - `NetworkCommands` in the first three bits. - // - `NetworkNodeIdCompression` in the next 2 bits. - // - `NetworkNameIdCompression` in the next 1 bit. - // - `byte_only_or_no_args` in the next 1 bit. - // - So we still have the last bit free! - uint8_t command_type = NETWORK_COMMAND_REMOTE_CALL; - uint8_t node_id_compression = UINT8_MAX; - uint8_t name_id_compression = UINT8_MAX; - bool byte_only_or_no_args = false; - - MAKE_ROOM(1); - // The meta is composed along the way, so just set 0 for now. - packet_cache.write[0] = 0; - ofs += 1; - - // Encode Node ID. - if (has_all_peers) { - // Compress the node ID only if all the target peers already know it. - if (psc->id >= 0 && psc->id <= 255) { - // We can encode the id in 1 byte - node_id_compression = NETWORK_NODE_ID_COMPRESSION_8; - MAKE_ROOM(ofs + 1); - packet_cache.write[ofs] = static_cast<uint8_t>(psc->id); - ofs += 1; - } else if (psc->id >= 0 && psc->id <= 65535) { - // We can encode the id in 2 bytes - node_id_compression = NETWORK_NODE_ID_COMPRESSION_16; - MAKE_ROOM(ofs + 2); - encode_uint16(static_cast<uint16_t>(psc->id), &(packet_cache.write[ofs])); - ofs += 2; - } else { - // Too big, let's use 4 bytes. - node_id_compression = NETWORK_NODE_ID_COMPRESSION_32; - MAKE_ROOM(ofs + 4); - encode_uint32(psc->id, &(packet_cache.write[ofs])); - ofs += 4; - } - } else { - // The targets don't know the node yet, so we need to use 32 bits int. - node_id_compression = NETWORK_NODE_ID_COMPRESSION_32; - MAKE_ROOM(ofs + 4); - encode_uint32(psc->id, &(packet_cache.write[ofs])); - ofs += 4; - } - - // Encode method ID - if (p_rpc_id <= UINT8_MAX) { - // The ID fits in 1 byte - name_id_compression = NETWORK_NAME_ID_COMPRESSION_8; - MAKE_ROOM(ofs + 1); - packet_cache.write[ofs] = static_cast<uint8_t>(p_rpc_id); - ofs += 1; - } else { - // The ID is larger, let's use 2 bytes - name_id_compression = NETWORK_NAME_ID_COMPRESSION_16; - MAKE_ROOM(ofs + 2); - encode_uint16(p_rpc_id, &(packet_cache.write[ofs])); - ofs += 2; - } - - if (p_argcount == 0) { - byte_only_or_no_args = true; - } else if (p_argcount == 1 && p_arg[0]->get_type() == Variant::PACKED_BYTE_ARRAY) { - byte_only_or_no_args = true; - // Special optimization when only the byte vector is sent. - const Vector<uint8_t> data = *p_arg[0]; - MAKE_ROOM(ofs + data.size()); - memcpy(&(packet_cache.write[ofs]), data.ptr(), sizeof(uint8_t) * data.size()); - ofs += data.size(); - } else { - // Arguments - MAKE_ROOM(ofs + 1); - packet_cache.write[ofs] = p_argcount; - ofs += 1; - for (int i = 0; i < p_argcount; i++) { - int len(0); - Error err = encode_and_compress_variant(*p_arg[i], nullptr, len); - ERR_FAIL_COND_MSG(err != OK, "Unable to encode RPC argument. THIS IS LIKELY A BUG IN THE ENGINE!"); - MAKE_ROOM(ofs + len); - encode_and_compress_variant(*p_arg[i], &(packet_cache.write[ofs]), len); - ofs += len; - } - } - - ERR_FAIL_COND(command_type > 7); - ERR_FAIL_COND(node_id_compression > 3); - ERR_FAIL_COND(name_id_compression > 1); - - // We can now set the meta - packet_cache.write[0] = command_type + (node_id_compression << NODE_ID_COMPRESSION_SHIFT) + (name_id_compression << NAME_ID_COMPRESSION_SHIFT) + ((byte_only_or_no_args ? 1 : 0) << BYTE_ONLY_OR_NO_ARGS_SHIFT); - -#ifdef DEBUG_ENABLED - _profile_bandwidth_data("out", ofs); -#endif - - // Take chance and set transfer mode, since all send methods will use it. - network_peer->set_transfer_channel(p_config.channel); - network_peer->set_transfer_mode(p_config.transfer_mode); - - if (has_all_peers) { - // They all have verified paths, so send fast. - network_peer->set_target_peer(p_to); // To all of you. - network_peer->put_packet(packet_cache.ptr(), ofs); // A message with love. - } else { - // Unreachable because the node ID is never compressed if the peers doesn't know it. - CRASH_COND(node_id_compression != NETWORK_NODE_ID_COMPRESSION_32); - - // Not all verified path, so send one by one. - - // Append path at the end, since we will need it for some packets. - CharString pname = String(from_path).utf8(); - int path_len = encode_cstring(pname.get_data(), nullptr); - MAKE_ROOM(ofs + path_len); - encode_cstring(pname.get_data(), &(packet_cache.write[ofs])); - - for (Set<int>::Element *E = connected_peers.front(); E; E = E->next()) { - if (p_to < 0 && E->get() == -p_to) { - continue; // Continue, excluded. - } - - if (p_to > 0 && E->get() != p_to) { - continue; // Continue, not for this peer. - } - - Map<int, bool>::Element *F = psc->confirmed_peers.find(E->get()); - ERR_CONTINUE(!F); // Should never happen. - - network_peer->set_target_peer(E->get()); // To this one specifically. - - if (F->get()) { - // This one confirmed path, so use id. - encode_uint32(psc->id, &(packet_cache.write[1])); - network_peer->put_packet(packet_cache.ptr(), ofs); - } else { - // This one did not confirm path yet, so use entire path (sorry!). - encode_uint32(0x80000000 | ofs, &(packet_cache.write[1])); // Offset to path and flag. - network_peer->put_packet(packet_cache.ptr(), ofs + path_len); - } - } - } -} - -void MultiplayerAPI::_add_peer(int p_id) { - connected_peers.insert(p_id); - path_get_cache.insert(p_id, PathGetCache()); - if (is_network_server()) { - replicator->spawn_all(p_id); - } - emit_signal(SNAME("network_peer_connected"), p_id); -} - -void MultiplayerAPI::_del_peer(int p_id) { - connected_peers.erase(p_id); - // Cleanup get cache. - path_get_cache.erase(p_id); - // Cleanup sent cache. - // Some refactoring is needed to make this faster and do paths GC. - List<NodePath> keys; - path_send_cache.get_key_list(&keys); - for (const NodePath &E : keys) { - PathSentCache *psc = path_send_cache.getptr(E); - psc->confirmed_peers.erase(p_id); - } - emit_signal(SNAME("network_peer_disconnected"), p_id); -} - -void MultiplayerAPI::_connected_to_server() { - emit_signal(SNAME("connected_to_server")); -} - -void MultiplayerAPI::_connection_failed() { - emit_signal(SNAME("connection_failed")); -} - -void MultiplayerAPI::_server_disconnected() { - emit_signal(SNAME("server_disconnected")); -} - -void MultiplayerAPI::rpcp(Node *p_node, int p_peer_id, bool p_unreliable, const StringName &p_method, const Variant **p_arg, int p_argcount) { - ERR_FAIL_COND_MSG(!network_peer.is_valid(), "Trying to call an RPC while no network peer is active."); - ERR_FAIL_COND_MSG(!p_node->is_inside_tree(), "Trying to call an RPC on a node which is not inside SceneTree."); - ERR_FAIL_COND_MSG(network_peer->get_connection_status() != MultiplayerPeer::CONNECTION_CONNECTED, "Trying to call an RPC via a network peer which is not connected."); - - int node_id = network_peer->get_unique_id(); - bool call_local_native = false; - bool call_local_script = false; - uint16_t rpc_id = UINT16_MAX; - const RPCConfig config = _get_rpc_config(p_node, p_method, rpc_id); - ERR_FAIL_COND_MSG(config.name == StringName(), - vformat("Unable to get the RPC configuration for the function \"%s\" at path: \"%s\". This happens when the method is not marked for RPCs.", p_method, p_node->get_path())); - if (p_peer_id == 0 || p_peer_id == node_id || (p_peer_id < 0 && p_peer_id != -node_id)) { - if (rpc_id & (1 << 15)) { - call_local_native = config.sync; - } else { - call_local_script = config.sync; - } - } - - if (p_peer_id != node_id) { -#ifdef DEBUG_ENABLED - _profile_node_data("out_rpc", p_node->get_instance_id()); -#endif - - _send_rpc(p_node, p_peer_id, rpc_id, config, p_method, p_arg, p_argcount); - } - - if (call_local_native) { - int temp_id = rpc_sender_id; - rpc_sender_id = get_network_unique_id(); - Callable::CallError ce; - p_node->call(p_method, p_arg, p_argcount, ce); - rpc_sender_id = temp_id; - if (ce.error != Callable::CallError::CALL_OK) { - String error = Variant::get_call_error_text(p_node, p_method, p_arg, p_argcount, ce); - error = "rpc() aborted in local call: - " + error + "."; - ERR_PRINT(error); - return; - } - } - - if (call_local_script) { - int temp_id = rpc_sender_id; - rpc_sender_id = get_network_unique_id(); - Callable::CallError ce; - ce.error = Callable::CallError::CALL_OK; - p_node->get_script_instance()->call(p_method, p_arg, p_argcount, ce); - rpc_sender_id = temp_id; - if (ce.error != Callable::CallError::CALL_OK) { - String error = Variant::get_call_error_text(p_node, p_method, p_arg, p_argcount, ce); - error = "rpc() aborted in script local call: - " + error + "."; - ERR_PRINT(error); - return; - } - } - - ERR_FAIL_COND_MSG(p_peer_id == node_id && !config.sync, "RPC '" + p_method + "' on yourself is not allowed by selected mode."); -} - -Error MultiplayerAPI::send_bytes(Vector<uint8_t> p_data, int p_to, MultiplayerPeer::TransferMode p_mode, int p_channel) { - ERR_FAIL_COND_V_MSG(p_data.size() < 1, ERR_INVALID_DATA, "Trying to send an empty raw packet."); - ERR_FAIL_COND_V_MSG(!network_peer.is_valid(), ERR_UNCONFIGURED, "Trying to send a raw packet while no network peer is active."); - ERR_FAIL_COND_V_MSG(network_peer->get_connection_status() != MultiplayerPeer::CONNECTION_CONNECTED, ERR_UNCONFIGURED, "Trying to send a raw packet via a network peer which is not connected."); - - MAKE_ROOM(p_data.size() + 1); - const uint8_t *r = p_data.ptr(); - packet_cache.write[0] = NETWORK_COMMAND_RAW; - memcpy(&packet_cache.write[1], &r[0], p_data.size()); - - network_peer->set_target_peer(p_to); - network_peer->set_transfer_channel(p_channel); - network_peer->set_transfer_mode(p_mode); - - return network_peer->put_packet(packet_cache.ptr(), p_data.size() + 1); -} - -void MultiplayerAPI::_process_raw(int p_from, const uint8_t *p_packet, int p_packet_len) { - ERR_FAIL_COND_MSG(p_packet_len < 2, "Invalid packet received. Size too small."); - - Vector<uint8_t> out; - int len = p_packet_len - 1; - out.resize(len); - { - uint8_t *w = out.ptrw(); - memcpy(&w[0], &p_packet[1], len); - } - emit_signal(SNAME("network_peer_packet"), p_from, out); -} - -bool MultiplayerAPI::send_confirm_path(Node *p_node, NodePath p_path, int p_peer_id, int &r_id) { - // See if the path is cached. - PathSentCache *psc = path_send_cache.getptr(p_path); - if (!psc) { - // Path is not cached, create. - path_send_cache[p_path] = PathSentCache(); - psc = path_send_cache.getptr(p_path); - psc->id = last_send_cache_id++; - } - r_id = psc->id; - - // See if all peers have cached path (if so, call can be fast). - return _send_confirm_path(p_node, p_path, psc, p_peer_id); -} - -Node *MultiplayerAPI::get_cached_node(int p_from, uint32_t p_node_id) { - Map<int, PathGetCache>::Element *E = path_get_cache.find(p_from); - ERR_FAIL_COND_V_MSG(!E, nullptr, vformat("No cache found for peer %d.", p_from)); - - Map<int, PathGetCache::NodeInfo>::Element *F = E->get().nodes.find(p_node_id); - ERR_FAIL_COND_V_MSG(!F, nullptr, vformat("ID %d not found in cache of peer %d.", p_node_id, p_from)); - - PathGetCache::NodeInfo *ni = &F->get(); - Node *node = root_node->get_node(ni->path); - if (!node) { - ERR_PRINT("Failed to get cached path: " + String(ni->path) + "."); - } - return node; -} - -int MultiplayerAPI::get_network_unique_id() const { - ERR_FAIL_COND_V_MSG(!network_peer.is_valid(), 0, "No network peer is assigned. Unable to get unique network ID."); - return network_peer->get_unique_id(); -} - -bool MultiplayerAPI::is_network_server() const { - return network_peer.is_valid() && network_peer->is_server(); -} - -void MultiplayerAPI::set_refuse_new_network_connections(bool p_refuse) { - ERR_FAIL_COND_MSG(!network_peer.is_valid(), "No network peer is assigned. Unable to set 'refuse_new_connections'."); - network_peer->set_refuse_new_connections(p_refuse); -} - -bool MultiplayerAPI::is_refusing_new_network_connections() const { - ERR_FAIL_COND_V_MSG(!network_peer.is_valid(), false, "No network peer is assigned. Unable to get 'refuse_new_connections'."); - return network_peer->is_refusing_new_connections(); -} - -Vector<int> MultiplayerAPI::get_network_connected_peers() const { - ERR_FAIL_COND_V_MSG(!network_peer.is_valid(), Vector<int>(), "No network peer is assigned. Assume no peers are connected."); - - Vector<int> ret; - for (Set<int>::Element *E = connected_peers.front(); E; E = E->next()) { - ret.push_back(E->get()); - } - - return ret; -} - -void MultiplayerAPI::set_allow_object_decoding(bool p_enable) { - allow_object_decoding = p_enable; -} - -bool MultiplayerAPI::is_object_decoding_allowed() const { - return allow_object_decoding; -} - -MultiplayerReplicator *MultiplayerAPI::get_replicator() const { - return replicator; -} - -void MultiplayerAPI::scene_enter_exit_notify(const String &p_scene, Node *p_node, bool p_enter) { - replicator->scene_enter_exit_notify(p_scene, p_node, p_enter); -} - -void MultiplayerAPI::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_root_node", "node"), &MultiplayerAPI::set_root_node); - ClassDB::bind_method(D_METHOD("get_root_node"), &MultiplayerAPI::get_root_node); - ClassDB::bind_method(D_METHOD("send_bytes", "bytes", "id", "mode", "channel"), &MultiplayerAPI::send_bytes, DEFVAL(MultiplayerPeer::TARGET_PEER_BROADCAST), DEFVAL(MultiplayerPeer::TRANSFER_MODE_RELIABLE), DEFVAL(0)); - ClassDB::bind_method(D_METHOD("has_network_peer"), &MultiplayerAPI::has_network_peer); - ClassDB::bind_method(D_METHOD("get_network_peer"), &MultiplayerAPI::get_network_peer); - ClassDB::bind_method(D_METHOD("get_network_unique_id"), &MultiplayerAPI::get_network_unique_id); - ClassDB::bind_method(D_METHOD("is_network_server"), &MultiplayerAPI::is_network_server); - ClassDB::bind_method(D_METHOD("get_rpc_sender_id"), &MultiplayerAPI::get_rpc_sender_id); - ClassDB::bind_method(D_METHOD("set_network_peer", "peer"), &MultiplayerAPI::set_network_peer); - ClassDB::bind_method(D_METHOD("poll"), &MultiplayerAPI::poll); - ClassDB::bind_method(D_METHOD("clear"), &MultiplayerAPI::clear); - - ClassDB::bind_method(D_METHOD("get_network_connected_peers"), &MultiplayerAPI::get_network_connected_peers); - ClassDB::bind_method(D_METHOD("set_refuse_new_network_connections", "refuse"), &MultiplayerAPI::set_refuse_new_network_connections); - ClassDB::bind_method(D_METHOD("is_refusing_new_network_connections"), &MultiplayerAPI::is_refusing_new_network_connections); - ClassDB::bind_method(D_METHOD("set_allow_object_decoding", "enable"), &MultiplayerAPI::set_allow_object_decoding); - ClassDB::bind_method(D_METHOD("is_object_decoding_allowed"), &MultiplayerAPI::is_object_decoding_allowed); - ClassDB::bind_method(D_METHOD("get_replicator"), &MultiplayerAPI::get_replicator); - - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "allow_object_decoding"), "set_allow_object_decoding", "is_object_decoding_allowed"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "refuse_new_network_connections"), "set_refuse_new_network_connections", "is_refusing_new_network_connections"); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "network_peer", PROPERTY_HINT_RESOURCE_TYPE, "MultiplayerPeer", PROPERTY_USAGE_NONE), "set_network_peer", "get_network_peer"); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "root_node", PROPERTY_HINT_RESOURCE_TYPE, "Node", PROPERTY_USAGE_NONE), "set_root_node", "get_root_node"); - ADD_PROPERTY_DEFAULT("refuse_new_network_connections", false); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "replicator", PROPERTY_HINT_RESOURCE_TYPE, "MultiplayerReplicator", PROPERTY_USAGE_NONE), "", "get_replicator"); - - ADD_SIGNAL(MethodInfo("network_peer_connected", PropertyInfo(Variant::INT, "id"))); - ADD_SIGNAL(MethodInfo("network_peer_disconnected", PropertyInfo(Variant::INT, "id"))); - ADD_SIGNAL(MethodInfo("network_peer_packet", PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::PACKED_BYTE_ARRAY, "packet"))); - ADD_SIGNAL(MethodInfo("connected_to_server")); - ADD_SIGNAL(MethodInfo("connection_failed")); - ADD_SIGNAL(MethodInfo("server_disconnected")); - - BIND_ENUM_CONSTANT(RPC_MODE_DISABLED); - BIND_ENUM_CONSTANT(RPC_MODE_ANY); - BIND_ENUM_CONSTANT(RPC_MODE_AUTHORITY); -} - -MultiplayerAPI::MultiplayerAPI() { - replicator = memnew(MultiplayerReplicator(this)); - clear(); -} - -MultiplayerAPI::~MultiplayerAPI() { - clear(); - memdelete(replicator); -} diff --git a/core/math/basis.cpp b/core/math/basis.cpp index 5c42213e61..eec9caf149 100644 --- a/core/math/basis.cpp +++ b/core/math/basis.cpp @@ -775,7 +775,7 @@ Basis::operator String() const { Quaternion Basis::get_quaternion() const { #ifdef MATH_CHECKS - ERR_FAIL_COND_V_MSG(!is_rotation(), Quaternion(), "Basis must be normalized in order to be casted to a Quaternion. Use get_rotation_quaternion() or call orthonormalized() instead."); + ERR_FAIL_COND_V_MSG(!is_rotation(), Quaternion(), "Basis must be normalized in order to be casted to a Quaternion. Use get_rotation_quaternion() or call orthonormalized() if the Basis contains linearly independent vectors."); #endif /* Allow getting a quaternion from an unnormalized transform */ Basis m = *this; diff --git a/core/math/convex_hull.cpp b/core/math/convex_hull.cpp index 21cb0efe20..f67035c803 100644 --- a/core/math/convex_hull.cpp +++ b/core/math/convex_hull.cpp @@ -2260,10 +2260,21 @@ Error ConvexHullComputer::convex_hull(const Vector<Vector3> &p_points, Geometry3 r_mesh.vertices = ch.vertices; - r_mesh.edges.resize(ch.edges.size()); + // Copy the edges over. There's two "half-edges" for every edge, so we pick only one of them. + r_mesh.edges.resize(ch.edges.size() / 2); + uint32_t edges_copied = 0; for (uint32_t i = 0; i < ch.edges.size(); i++) { - r_mesh.edges.write[i].a = (&ch.edges[i])->get_source_vertex(); - r_mesh.edges.write[i].b = (&ch.edges[i])->get_target_vertex(); + uint32_t a = (&ch.edges[i])->get_source_vertex(); + uint32_t b = (&ch.edges[i])->get_target_vertex(); + if (a < b) { // Copy only the "canonical" edge. For the reverse edge, this will be false. + ERR_BREAK(edges_copied >= (uint32_t)r_mesh.edges.size()); + r_mesh.edges.write[edges_copied].a = a; + r_mesh.edges.write[edges_copied].b = b; + edges_copied++; + } + } + if (edges_copied != (uint32_t)r_mesh.edges.size()) { + ERR_PRINT("Invalid edge count."); } r_mesh.faces.resize(ch.faces.size()); diff --git a/core/multiplayer/SCsub b/core/multiplayer/SCsub new file mode 100644 index 0000000000..19a6549225 --- /dev/null +++ b/core/multiplayer/SCsub @@ -0,0 +1,5 @@ +#!/usr/bin/env python + +Import("env") + +env.add_source_files(env.core_sources, "*.cpp") diff --git a/modules/stb_vorbis/register_types.h b/core/multiplayer/multiplayer.h index d36d87606c..00c81d3c9a 100644 --- a/modules/stb_vorbis/register_types.h +++ b/core/multiplayer/multiplayer.h @@ -1,5 +1,5 @@ /*************************************************************************/ -/* register_types.h */ +/* multiplayer.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,10 +28,53 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef STB_VORBIS_REGISTER_TYPES_H -#define STB_VORBIS_REGISTER_TYPES_H +#ifndef MULTIPLAYER_H +#define MULTIPLAYER_H -void register_stb_vorbis_types(); -void unregister_stb_vorbis_types(); +#include "core/variant/binder_common.h" -#endif // STB_VORBIS_REGISTER_TYPES_H +#include "core/string/string_name.h" + +namespace Multiplayer { + +enum TransferMode { + TRANSFER_MODE_UNRELIABLE, + TRANSFER_MODE_ORDERED, + TRANSFER_MODE_RELIABLE +}; + +enum RPCMode { + RPC_MODE_DISABLED, // No rpc for this method, calls to this will be blocked (default) + RPC_MODE_ANY, // Any peer can call this RPC + RPC_MODE_AUTHORITY, // / Only the node's multiplayer authority (server by default) can call this RPC +}; + +struct RPCConfig { + StringName name; + RPCMode rpc_mode = RPC_MODE_DISABLED; + bool sync = false; + TransferMode transfer_mode = TRANSFER_MODE_RELIABLE; + int channel = 0; + + bool operator==(RPCConfig const &p_other) const { + return name == p_other.name; + } +}; + +struct SortRPCConfig { + StringName::AlphCompare compare; + bool operator()(const RPCConfig &p_a, const RPCConfig &p_b) const { + return compare(p_a.name, p_b.name); + } +}; + +}; // namespace Multiplayer + +// This is needed for proper docs generation (i.e. not "Multiplayer."-prefixed). +typedef Multiplayer::RPCMode RPCMode; +typedef Multiplayer::TransferMode TransferMode; + +VARIANT_ENUM_CAST(RPCMode); +VARIANT_ENUM_CAST(TransferMode); + +#endif // MULTIPLAYER_H diff --git a/core/multiplayer/multiplayer_api.cpp b/core/multiplayer/multiplayer_api.cpp new file mode 100644 index 0000000000..9543f77c1e --- /dev/null +++ b/core/multiplayer/multiplayer_api.cpp @@ -0,0 +1,668 @@ +/*************************************************************************/ +/* multiplayer_api.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "multiplayer_api.h" + +#include "core/debugger/engine_debugger.h" +#include "core/io/marshalls.h" +#include "core/multiplayer/multiplayer_replicator.h" +#include "core/multiplayer/rpc_manager.h" +#include "scene/main/node.h" + +#include <stdint.h> + +#ifdef DEBUG_ENABLED +#include "core/os/os.h" +#endif + +#ifdef DEBUG_ENABLED +void MultiplayerAPI::profile_bandwidth(const String &p_inout, int p_size) { + if (EngineDebugger::is_profiling("multiplayer")) { + Array values; + values.push_back("bandwidth"); + values.push_back(p_inout); + values.push_back(OS::get_singleton()->get_ticks_msec()); + values.push_back(p_size); + EngineDebugger::profiler_add_frame_data("multiplayer", values); + } +} +#endif + +void MultiplayerAPI::poll() { + if (!multiplayer_peer.is_valid() || multiplayer_peer->get_connection_status() == MultiplayerPeer::CONNECTION_DISCONNECTED) { + return; + } + + multiplayer_peer->poll(); + + if (!multiplayer_peer.is_valid()) { // It's possible that polling might have resulted in a disconnection, so check here. + return; + } + + while (multiplayer_peer->get_available_packet_count()) { + int sender = multiplayer_peer->get_packet_peer(); + const uint8_t *packet; + int len; + + Error err = multiplayer_peer->get_packet(&packet, len); + if (err != OK) { + ERR_PRINT("Error getting packet!"); + break; // Something is wrong! + } + + remote_sender_id = sender; + _process_packet(sender, packet, len); + remote_sender_id = 0; + + if (!multiplayer_peer.is_valid()) { + break; // It's also possible that a packet or RPC caused a disconnection, so also check here. + } + } + if (multiplayer_peer.is_valid() && multiplayer_peer->get_connection_status() == MultiplayerPeer::CONNECTION_CONNECTED) { + replicator->poll(); + } +} + +void MultiplayerAPI::clear() { + replicator->clear(); + connected_peers.clear(); + path_get_cache.clear(); + path_send_cache.clear(); + packet_cache.clear(); + last_send_cache_id = 1; +} + +void MultiplayerAPI::set_root_node(Node *p_node) { + root_node = p_node; +} + +Node *MultiplayerAPI::get_root_node() { + return root_node; +} + +void MultiplayerAPI::set_multiplayer_peer(const Ref<MultiplayerPeer> &p_peer) { + if (p_peer == multiplayer_peer) { + return; // Nothing to do + } + + ERR_FAIL_COND_MSG(p_peer.is_valid() && p_peer->get_connection_status() == MultiplayerPeer::CONNECTION_DISCONNECTED, + "Supplied MultiplayerPeer must be connecting or connected."); + + if (multiplayer_peer.is_valid()) { + multiplayer_peer->disconnect("peer_connected", callable_mp(this, &MultiplayerAPI::_add_peer)); + multiplayer_peer->disconnect("peer_disconnected", callable_mp(this, &MultiplayerAPI::_del_peer)); + multiplayer_peer->disconnect("connection_succeeded", callable_mp(this, &MultiplayerAPI::_connected_to_server)); + multiplayer_peer->disconnect("connection_failed", callable_mp(this, &MultiplayerAPI::_connection_failed)); + multiplayer_peer->disconnect("server_disconnected", callable_mp(this, &MultiplayerAPI::_server_disconnected)); + clear(); + } + + multiplayer_peer = p_peer; + + if (multiplayer_peer.is_valid()) { + multiplayer_peer->connect("peer_connected", callable_mp(this, &MultiplayerAPI::_add_peer)); + multiplayer_peer->connect("peer_disconnected", callable_mp(this, &MultiplayerAPI::_del_peer)); + multiplayer_peer->connect("connection_succeeded", callable_mp(this, &MultiplayerAPI::_connected_to_server)); + multiplayer_peer->connect("connection_failed", callable_mp(this, &MultiplayerAPI::_connection_failed)); + multiplayer_peer->connect("server_disconnected", callable_mp(this, &MultiplayerAPI::_server_disconnected)); + } +} + +Ref<MultiplayerPeer> MultiplayerAPI::get_multiplayer_peer() const { + return multiplayer_peer; +} + +void MultiplayerAPI::_process_packet(int p_from, const uint8_t *p_packet, int p_packet_len) { + ERR_FAIL_COND_MSG(root_node == nullptr, "Multiplayer root node was not initialized. If you are using custom multiplayer, remember to set the root node via MultiplayerAPI.set_root_node before using it."); + ERR_FAIL_COND_MSG(p_packet_len < 1, "Invalid packet received. Size too small."); + +#ifdef DEBUG_ENABLED + profile_bandwidth("in", p_packet_len); +#endif + + // Extract the `packet_type` from the LSB three bits: + uint8_t packet_type = p_packet[0] & CMD_MASK; + + switch (packet_type) { + case NETWORK_COMMAND_SIMPLIFY_PATH: { + _process_simplify_path(p_from, p_packet, p_packet_len); + } break; + + case NETWORK_COMMAND_CONFIRM_PATH: { + _process_confirm_path(p_from, p_packet, p_packet_len); + } break; + + case NETWORK_COMMAND_REMOTE_CALL: { + rpc_manager->process_rpc(p_from, p_packet, p_packet_len); + } break; + + case NETWORK_COMMAND_RAW: { + _process_raw(p_from, p_packet, p_packet_len); + } break; + case NETWORK_COMMAND_SPAWN: { + replicator->process_spawn_despawn(p_from, p_packet, p_packet_len, true); + } break; + case NETWORK_COMMAND_DESPAWN: { + replicator->process_spawn_despawn(p_from, p_packet, p_packet_len, false); + } break; + case NETWORK_COMMAND_SYNC: { + replicator->process_sync(p_from, p_packet, p_packet_len); + } break; + } +} + +void MultiplayerAPI::_process_simplify_path(int p_from, const uint8_t *p_packet, int p_packet_len) { + ERR_FAIL_COND_MSG(p_packet_len < 38, "Invalid packet received. Size too small."); + int ofs = 1; + + String methods_md5; + methods_md5.parse_utf8((const char *)(p_packet + ofs), 32); + ofs += 33; + + int id = decode_uint32(&p_packet[ofs]); + ofs += 4; + + String paths; + paths.parse_utf8((const char *)(p_packet + ofs), p_packet_len - ofs); + + NodePath path = paths; + + if (!path_get_cache.has(p_from)) { + path_get_cache[p_from] = PathGetCache(); + } + + Node *node = root_node->get_node(path); + ERR_FAIL_COND(node == nullptr); + const bool valid_rpc_checksum = rpc_manager->get_rpc_md5(node) == methods_md5; + if (valid_rpc_checksum == false) { + ERR_PRINT("The rpc node checksum failed. Make sure to have the same methods on both nodes. Node path: " + path); + } + + PathGetCache::NodeInfo ni; + ni.path = path; + + path_get_cache[p_from].nodes[id] = ni; + + // Encode path to send ack. + CharString pname = String(path).utf8(); + int len = encode_cstring(pname.get_data(), nullptr); + + Vector<uint8_t> packet; + + packet.resize(1 + 1 + len); + packet.write[0] = NETWORK_COMMAND_CONFIRM_PATH; + packet.write[1] = valid_rpc_checksum; + encode_cstring(pname.get_data(), &packet.write[2]); + + multiplayer_peer->set_transfer_channel(0); + multiplayer_peer->set_transfer_mode(Multiplayer::TRANSFER_MODE_RELIABLE); + multiplayer_peer->set_target_peer(p_from); + multiplayer_peer->put_packet(packet.ptr(), packet.size()); +} + +void MultiplayerAPI::_process_confirm_path(int p_from, const uint8_t *p_packet, int p_packet_len) { + ERR_FAIL_COND_MSG(p_packet_len < 3, "Invalid packet received. Size too small."); + + const bool valid_rpc_checksum = p_packet[1]; + + String paths; + paths.parse_utf8((const char *)&p_packet[2], p_packet_len - 2); + + NodePath path = paths; + + if (valid_rpc_checksum == false) { + ERR_PRINT("The rpc node checksum failed. Make sure to have the same methods on both nodes. Node path: " + path); + } + + PathSentCache *psc = path_send_cache.getptr(path); + ERR_FAIL_COND_MSG(!psc, "Invalid packet received. Tries to confirm a path which was not found in cache."); + + Map<int, bool>::Element *E = psc->confirmed_peers.find(p_from); + ERR_FAIL_COND_MSG(!E, "Invalid packet received. Source peer was not found in cache for the given path."); + E->get() = true; +} + +bool MultiplayerAPI::_send_confirm_path(Node *p_node, NodePath p_path, PathSentCache *psc, int p_target) { + bool has_all_peers = true; + List<int> peers_to_add; // If one is missing, take note to add it. + + for (Set<int>::Element *E = connected_peers.front(); E; E = E->next()) { + if (p_target < 0 && E->get() == -p_target) { + continue; // Continue, excluded. + } + + if (p_target > 0 && E->get() != p_target) { + continue; // Continue, not for this peer. + } + + Map<int, bool>::Element *F = psc->confirmed_peers.find(E->get()); + + if (!F || !F->get()) { + // Path was not cached, or was cached but is unconfirmed. + if (!F) { + // Not cached at all, take note. + peers_to_add.push_back(E->get()); + } + + has_all_peers = false; + } + } + + if (peers_to_add.size() > 0) { + // Those that need to be added, send a message for this. + + // Encode function name. + const CharString path = String(p_path).utf8(); + const int path_len = encode_cstring(path.get_data(), nullptr); + + // Extract MD5 from rpc methods list. + const String methods_md5 = rpc_manager->get_rpc_md5(p_node); + const int methods_md5_len = 33; // 32 + 1 for the `0` that is added by the encoder. + + Vector<uint8_t> packet; + packet.resize(1 + 4 + path_len + methods_md5_len); + int ofs = 0; + + packet.write[ofs] = NETWORK_COMMAND_SIMPLIFY_PATH; + ofs += 1; + + ofs += encode_cstring(methods_md5.utf8().get_data(), &packet.write[ofs]); + + ofs += encode_uint32(psc->id, &packet.write[ofs]); + + ofs += encode_cstring(path.get_data(), &packet.write[ofs]); + + for (int &E : peers_to_add) { + multiplayer_peer->set_target_peer(E); // To all of you. + multiplayer_peer->set_transfer_channel(0); + multiplayer_peer->set_transfer_mode(Multiplayer::TRANSFER_MODE_RELIABLE); + multiplayer_peer->put_packet(packet.ptr(), packet.size()); + + psc->confirmed_peers.insert(E, false); // Insert into confirmed, but as false since it was not confirmed. + } + } + + return has_all_peers; +} + +// The variant is compressed and encoded; The first byte contains all the meta +// information and the format is: +// - The first LSB 5 bits are used for the variant type. +// - The next two bits are used to store the encoding mode. +// - The most significant is used to store the boolean value. +#define VARIANT_META_TYPE_MASK 0x1F +#define VARIANT_META_EMODE_MASK 0x60 +#define VARIANT_META_BOOL_MASK 0x80 +#define ENCODE_8 0 << 5 +#define ENCODE_16 1 << 5 +#define ENCODE_32 2 << 5 +#define ENCODE_64 3 << 5 +Error MultiplayerAPI::encode_and_compress_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len) { + // Unreachable because `VARIANT_MAX` == 27 and `ENCODE_VARIANT_MASK` == 31 + CRASH_COND(p_variant.get_type() > VARIANT_META_TYPE_MASK); + + uint8_t *buf = r_buffer; + r_len = 0; + uint8_t encode_mode = 0; + + switch (p_variant.get_type()) { + case Variant::BOOL: { + if (buf) { + // We still have 1 free bit in the meta, so let's use it. + buf[0] = (p_variant.operator bool()) ? (1 << 7) : 0; + buf[0] |= encode_mode | p_variant.get_type(); + } + r_len += 1; + } break; + case Variant::INT: { + if (buf) { + // Reserve the first byte for the meta. + buf += 1; + } + r_len += 1; + int64_t val = p_variant; + if (val <= (int64_t)INT8_MAX && val >= (int64_t)INT8_MIN) { + // Use 8 bit + encode_mode = ENCODE_8; + if (buf) { + buf[0] = val; + } + r_len += 1; + } else if (val <= (int64_t)INT16_MAX && val >= (int64_t)INT16_MIN) { + // Use 16 bit + encode_mode = ENCODE_16; + if (buf) { + encode_uint16(val, buf); + } + r_len += 2; + } else if (val <= (int64_t)INT32_MAX && val >= (int64_t)INT32_MIN) { + // Use 32 bit + encode_mode = ENCODE_32; + if (buf) { + encode_uint32(val, buf); + } + r_len += 4; + } else { + // Use 64 bit + encode_mode = ENCODE_64; + if (buf) { + encode_uint64(val, buf); + } + r_len += 8; + } + // Store the meta + if (buf) { + buf -= 1; + buf[0] = encode_mode | p_variant.get_type(); + } + } break; + default: + // Any other case is not yet compressed. + Error err = encode_variant(p_variant, r_buffer, r_len, allow_object_decoding); + if (err != OK) { + return err; + } + if (r_buffer) { + // The first byte is not used by the marshalling, so store the type + // so we know how to decompress and decode this variant. + r_buffer[0] = p_variant.get_type(); + } + } + + return OK; +} + +Error MultiplayerAPI::decode_and_decompress_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int *r_len) { + const uint8_t *buf = p_buffer; + int len = p_len; + + ERR_FAIL_COND_V(len < 1, ERR_INVALID_DATA); + uint8_t type = buf[0] & VARIANT_META_TYPE_MASK; + uint8_t encode_mode = buf[0] & VARIANT_META_EMODE_MASK; + + ERR_FAIL_COND_V(type >= Variant::VARIANT_MAX, ERR_INVALID_DATA); + + switch (type) { + case Variant::BOOL: { + bool val = (buf[0] & VARIANT_META_BOOL_MASK) > 0; + r_variant = val; + if (r_len) { + *r_len = 1; + } + } break; + case Variant::INT: { + buf += 1; + len -= 1; + if (r_len) { + *r_len = 1; + } + if (encode_mode == ENCODE_8) { + // 8 bits. + ERR_FAIL_COND_V(len < 1, ERR_INVALID_DATA); + int8_t val = buf[0]; + r_variant = val; + if (r_len) { + (*r_len) += 1; + } + } else if (encode_mode == ENCODE_16) { + // 16 bits. + ERR_FAIL_COND_V(len < 2, ERR_INVALID_DATA); + int16_t val = decode_uint16(buf); + r_variant = val; + if (r_len) { + (*r_len) += 2; + } + } else if (encode_mode == ENCODE_32) { + // 32 bits. + ERR_FAIL_COND_V(len < 4, ERR_INVALID_DATA); + int32_t val = decode_uint32(buf); + r_variant = val; + if (r_len) { + (*r_len) += 4; + } + } else { + // 64 bits. + ERR_FAIL_COND_V(len < 8, ERR_INVALID_DATA); + int64_t val = decode_uint64(buf); + r_variant = val; + if (r_len) { + (*r_len) += 8; + } + } + } break; + default: + Error err = decode_variant(r_variant, p_buffer, p_len, r_len, allow_object_decoding); + if (err != OK) { + return err; + } + } + + return OK; +} + +void MultiplayerAPI::_add_peer(int p_id) { + connected_peers.insert(p_id); + path_get_cache.insert(p_id, PathGetCache()); + if (is_server()) { + replicator->spawn_all(p_id); + } + emit_signal(SNAME("peer_connected"), p_id); +} + +void MultiplayerAPI::_del_peer(int p_id) { + connected_peers.erase(p_id); + // Cleanup get cache. + path_get_cache.erase(p_id); + // Cleanup sent cache. + // Some refactoring is needed to make this faster and do paths GC. + List<NodePath> keys; + path_send_cache.get_key_list(&keys); + for (const NodePath &E : keys) { + PathSentCache *psc = path_send_cache.getptr(E); + psc->confirmed_peers.erase(p_id); + } + emit_signal(SNAME("peer_disconnected"), p_id); +} + +void MultiplayerAPI::_connected_to_server() { + emit_signal(SNAME("connected_to_server")); +} + +void MultiplayerAPI::_connection_failed() { + emit_signal(SNAME("connection_failed")); +} + +void MultiplayerAPI::_server_disconnected() { + emit_signal(SNAME("server_disconnected")); +} + +Error MultiplayerAPI::send_bytes(Vector<uint8_t> p_data, int p_to, Multiplayer::TransferMode p_mode, int p_channel) { + ERR_FAIL_COND_V_MSG(p_data.size() < 1, ERR_INVALID_DATA, "Trying to send an empty raw packet."); + ERR_FAIL_COND_V_MSG(!multiplayer_peer.is_valid(), ERR_UNCONFIGURED, "Trying to send a raw packet while no multiplayer peer is active."); + ERR_FAIL_COND_V_MSG(multiplayer_peer->get_connection_status() != MultiplayerPeer::CONNECTION_CONNECTED, ERR_UNCONFIGURED, "Trying to send a raw packet via a multiplayer peer which is not connected."); + + if (packet_cache.size() < p_data.size() + 1) { + packet_cache.resize(p_data.size() + 1); + } + + const uint8_t *r = p_data.ptr(); + packet_cache.write[0] = NETWORK_COMMAND_RAW; + memcpy(&packet_cache.write[1], &r[0], p_data.size()); + + multiplayer_peer->set_target_peer(p_to); + multiplayer_peer->set_transfer_channel(p_channel); + multiplayer_peer->set_transfer_mode(p_mode); + + return multiplayer_peer->put_packet(packet_cache.ptr(), p_data.size() + 1); +} + +void MultiplayerAPI::_process_raw(int p_from, const uint8_t *p_packet, int p_packet_len) { + ERR_FAIL_COND_MSG(p_packet_len < 2, "Invalid packet received. Size too small."); + + Vector<uint8_t> out; + int len = p_packet_len - 1; + out.resize(len); + { + uint8_t *w = out.ptrw(); + memcpy(&w[0], &p_packet[1], len); + } + emit_signal(SNAME("peer_packet"), p_from, out); +} + +bool MultiplayerAPI::is_cache_confirmed(NodePath p_path, int p_peer) { + const PathSentCache *psc = path_send_cache.getptr(p_path); + ERR_FAIL_COND_V(!psc, false); + const Map<int, bool>::Element *F = psc->confirmed_peers.find(p_peer); + ERR_FAIL_COND_V(!F, false); // Should never happen. + return F->get(); +} + +bool MultiplayerAPI::send_confirm_path(Node *p_node, NodePath p_path, int p_peer_id, int &r_id) { + // See if the path is cached. + PathSentCache *psc = path_send_cache.getptr(p_path); + if (!psc) { + // Path is not cached, create. + path_send_cache[p_path] = PathSentCache(); + psc = path_send_cache.getptr(p_path); + psc->id = last_send_cache_id++; + } + r_id = psc->id; + + // See if all peers have cached path (if so, call can be fast). + return _send_confirm_path(p_node, p_path, psc, p_peer_id); +} + +Node *MultiplayerAPI::get_cached_node(int p_from, uint32_t p_node_id) { + Map<int, PathGetCache>::Element *E = path_get_cache.find(p_from); + ERR_FAIL_COND_V_MSG(!E, nullptr, vformat("No cache found for peer %d.", p_from)); + + Map<int, PathGetCache::NodeInfo>::Element *F = E->get().nodes.find(p_node_id); + ERR_FAIL_COND_V_MSG(!F, nullptr, vformat("ID %d not found in cache of peer %d.", p_node_id, p_from)); + + PathGetCache::NodeInfo *ni = &F->get(); + Node *node = root_node->get_node(ni->path); + if (!node) { + ERR_PRINT("Failed to get cached path: " + String(ni->path) + "."); + } + return node; +} + +int MultiplayerAPI::get_unique_id() const { + ERR_FAIL_COND_V_MSG(!multiplayer_peer.is_valid(), 0, "No multiplayer peer is assigned. Unable to get unique ID."); + return multiplayer_peer->get_unique_id(); +} + +bool MultiplayerAPI::is_server() const { + return multiplayer_peer.is_valid() && multiplayer_peer->is_server(); +} + +void MultiplayerAPI::set_refuse_new_connections(bool p_refuse) { + ERR_FAIL_COND_MSG(!multiplayer_peer.is_valid(), "No multiplayer peer is assigned. Unable to set 'refuse_new_connections'."); + multiplayer_peer->set_refuse_new_connections(p_refuse); +} + +bool MultiplayerAPI::is_refusing_new_connections() const { + ERR_FAIL_COND_V_MSG(!multiplayer_peer.is_valid(), false, "No multiplayer peer is assigned. Unable to get 'refuse_new_connections'."); + return multiplayer_peer->is_refusing_new_connections(); +} + +Vector<int> MultiplayerAPI::get_peer_ids() const { + ERR_FAIL_COND_V_MSG(!multiplayer_peer.is_valid(), Vector<int>(), "No multiplayer peer is assigned. Assume no peers are connected."); + + Vector<int> ret; + for (Set<int>::Element *E = connected_peers.front(); E; E = E->next()) { + ret.push_back(E->get()); + } + + return ret; +} + +void MultiplayerAPI::set_allow_object_decoding(bool p_enable) { + allow_object_decoding = p_enable; +} + +bool MultiplayerAPI::is_object_decoding_allowed() const { + return allow_object_decoding; +} + +void MultiplayerAPI::scene_enter_exit_notify(const String &p_scene, Node *p_node, bool p_enter) { + replicator->scene_enter_exit_notify(p_scene, p_node, p_enter); +} + +void MultiplayerAPI::rpcp(Node *p_node, int p_peer_id, const StringName &p_method, const Variant **p_arg, int p_argcount) { + rpc_manager->rpcp(p_node, p_peer_id, p_method, p_arg, p_argcount); +} + +void MultiplayerAPI::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_root_node", "node"), &MultiplayerAPI::set_root_node); + ClassDB::bind_method(D_METHOD("get_root_node"), &MultiplayerAPI::get_root_node); + ClassDB::bind_method(D_METHOD("send_bytes", "bytes", "id", "mode", "channel"), &MultiplayerAPI::send_bytes, DEFVAL(MultiplayerPeer::TARGET_PEER_BROADCAST), DEFVAL(Multiplayer::TRANSFER_MODE_RELIABLE), DEFVAL(0)); + ClassDB::bind_method(D_METHOD("has_multiplayer_peer"), &MultiplayerAPI::has_multiplayer_peer); + ClassDB::bind_method(D_METHOD("get_multiplayer_peer"), &MultiplayerAPI::get_multiplayer_peer); + ClassDB::bind_method(D_METHOD("set_multiplayer_peer", "peer"), &MultiplayerAPI::set_multiplayer_peer); + ClassDB::bind_method(D_METHOD("get_unique_id"), &MultiplayerAPI::get_unique_id); + ClassDB::bind_method(D_METHOD("is_server"), &MultiplayerAPI::is_server); + ClassDB::bind_method(D_METHOD("get_remote_sender_id"), &MultiplayerAPI::get_remote_sender_id); + ClassDB::bind_method(D_METHOD("poll"), &MultiplayerAPI::poll); + ClassDB::bind_method(D_METHOD("clear"), &MultiplayerAPI::clear); + + ClassDB::bind_method(D_METHOD("get_peers"), &MultiplayerAPI::get_peer_ids); + ClassDB::bind_method(D_METHOD("set_refuse_new_connections", "refuse"), &MultiplayerAPI::set_refuse_new_connections); + ClassDB::bind_method(D_METHOD("is_refusing_new_connections"), &MultiplayerAPI::is_refusing_new_connections); + ClassDB::bind_method(D_METHOD("set_allow_object_decoding", "enable"), &MultiplayerAPI::set_allow_object_decoding); + ClassDB::bind_method(D_METHOD("is_object_decoding_allowed"), &MultiplayerAPI::is_object_decoding_allowed); + ClassDB::bind_method(D_METHOD("get_replicator"), &MultiplayerAPI::get_replicator); + + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "allow_object_decoding"), "set_allow_object_decoding", "is_object_decoding_allowed"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "refuse_new_connections"), "set_refuse_new_connections", "is_refusing_new_connections"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "multiplayer_peer", PROPERTY_HINT_RESOURCE_TYPE, "MultiplayerPeer", PROPERTY_USAGE_NONE), "set_multiplayer_peer", "get_multiplayer_peer"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "root_node", PROPERTY_HINT_RESOURCE_TYPE, "Node", PROPERTY_USAGE_NONE), "set_root_node", "get_root_node"); + ADD_PROPERTY_DEFAULT("refuse_new_connections", false); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "replicator", PROPERTY_HINT_RESOURCE_TYPE, "MultiplayerReplicator", PROPERTY_USAGE_NONE), "", "get_replicator"); + + ADD_SIGNAL(MethodInfo("peer_connected", PropertyInfo(Variant::INT, "id"))); + ADD_SIGNAL(MethodInfo("peer_disconnected", PropertyInfo(Variant::INT, "id"))); + ADD_SIGNAL(MethodInfo("peer_packet", PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::PACKED_BYTE_ARRAY, "packet"))); + ADD_SIGNAL(MethodInfo("connected_to_server")); + ADD_SIGNAL(MethodInfo("connection_failed")); + ADD_SIGNAL(MethodInfo("server_disconnected")); +} + +MultiplayerAPI::MultiplayerAPI() { + replicator = memnew(MultiplayerReplicator(this)); + rpc_manager = memnew(RPCManager(this)); + clear(); +} + +MultiplayerAPI::~MultiplayerAPI() { + clear(); + memdelete(replicator); + memdelete(rpc_manager); +} diff --git a/core/io/multiplayer_api.h b/core/multiplayer/multiplayer_api.h index 3c96a3eed1..1fb0318403 100644 --- a/core/io/multiplayer_api.h +++ b/core/multiplayer/multiplayer_api.h @@ -31,41 +31,17 @@ #ifndef MULTIPLAYER_API_H #define MULTIPLAYER_API_H -#include "core/io/multiplayer_peer.h" -#include "core/io/resource_uid.h" +#include "core/multiplayer/multiplayer.h" +#include "core/multiplayer/multiplayer_peer.h" #include "core/object/ref_counted.h" class MultiplayerReplicator; +class RPCManager; class MultiplayerAPI : public RefCounted { GDCLASS(MultiplayerAPI, RefCounted); public: - enum RPCMode { - RPC_MODE_DISABLED, // No rpc for this method, calls to this will be blocked (default) - RPC_MODE_ANY, // Any peer can call this rpc() - RPC_MODE_AUTHORITY, // Only the node's network authority (server by default) can call this rpc() - }; - - struct RPCConfig { - StringName name; - RPCMode rpc_mode = RPC_MODE_DISABLED; - bool sync = false; - MultiplayerPeer::TransferMode transfer_mode = MultiplayerPeer::TRANSFER_MODE_RELIABLE; - int channel = 0; - - bool operator==(RPCConfig const &p_other) const { - return name == p_other.name; - } - }; - - struct SortRPCConfig { - StringName::AlphCompare compare; - bool operator()(const RPCConfig &p_a, const RPCConfig &p_b) const { - return compare(p_a.name, p_b.name); - } - }; - enum NetworkCommands { NETWORK_COMMAND_REMOTE_CALL = 0, NETWORK_COMMAND_SIMPLIFY_PATH, @@ -73,24 +49,20 @@ public: NETWORK_COMMAND_RAW, NETWORK_COMMAND_SPAWN, NETWORK_COMMAND_DESPAWN, - NETWORK_COMMAND_SYNC, // This is the max we can have. We should optmize simplify/confirm, possibly spawn/despawn. + NETWORK_COMMAND_SYNC, }; - enum NetworkNodeIdCompression { - NETWORK_NODE_ID_COMPRESSION_8 = 0, - NETWORK_NODE_ID_COMPRESSION_16, - NETWORK_NODE_ID_COMPRESSION_32, - }; - - enum NetworkNameIdCompression { - NETWORK_NAME_ID_COMPRESSION_8 = 0, - NETWORK_NAME_ID_COMPRESSION_16, + // For each command, the 4 MSB can contain custom flags, as defined by subsystems. + enum { + CMD_FLAG_0_SHIFT = 4, + CMD_FLAG_1_SHIFT = 5, + CMD_FLAG_2_SHIFT = 6, + CMD_FLAG_3_SHIFT = 7, }; + // This is the mask that will be used to extract the command. enum { - NODE_ID_COMPRESSION_SHIFT = 3, - NAME_ID_COMPRESSION_SHIFT = 5, - BYTE_ONLY_OR_NO_ARGS_SHIFT = 6, + CMD_MASK = 7, // 0x7 -> 0b00001111 }; private: @@ -110,49 +82,52 @@ private: Map<int, NodeInfo> nodes; }; - Ref<MultiplayerPeer> network_peer; - int rpc_sender_id = 0; + Ref<MultiplayerPeer> multiplayer_peer; Set<int> connected_peers; + int remote_sender_id = 0; + int remote_sender_override = 0; + HashMap<NodePath, PathSentCache> path_send_cache; Map<int, PathGetCache> path_get_cache; int last_send_cache_id; Vector<uint8_t> packet_cache; + Node *root_node = nullptr; bool allow_object_decoding = false; + MultiplayerReplicator *replicator = nullptr; + RPCManager *rpc_manager = nullptr; protected: static void _bind_methods(); + bool _send_confirm_path(Node *p_node, NodePath p_path, PathSentCache *psc, int p_target); void _process_packet(int p_from, const uint8_t *p_packet, int p_packet_len); void _process_simplify_path(int p_from, const uint8_t *p_packet, int p_packet_len); void _process_confirm_path(int p_from, const uint8_t *p_packet, int p_packet_len); - Node *_process_get_node(int p_from, const uint8_t *p_packet, uint32_t p_node_target, int p_packet_len); - void _process_rpc(Node *p_node, const uint16_t p_rpc_method_id, int p_from, const uint8_t *p_packet, int p_packet_len, int p_offset); void _process_raw(int p_from, const uint8_t *p_packet, int p_packet_len); - void _send_rpc(Node *p_from, int p_to, uint16_t p_rpc_id, const RPCConfig &p_config, const StringName &p_name, const Variant **p_arg, int p_argcount); - bool _send_confirm_path(Node *p_node, NodePath p_path, PathSentCache *psc, int p_target); - public: void poll(); void clear(); void set_root_node(Node *p_node); Node *get_root_node(); - void set_network_peer(const Ref<MultiplayerPeer> &p_peer); - Ref<MultiplayerPeer> get_network_peer() const; - Error send_bytes(Vector<uint8_t> p_data, int p_to = MultiplayerPeer::TARGET_PEER_BROADCAST, MultiplayerPeer::TransferMode p_mode = MultiplayerPeer::TRANSFER_MODE_RELIABLE, int p_channel = 0); + void set_multiplayer_peer(const Ref<MultiplayerPeer> &p_peer); + Ref<MultiplayerPeer> get_multiplayer_peer() const; + + Error send_bytes(Vector<uint8_t> p_data, int p_to = MultiplayerPeer::TARGET_PEER_BROADCAST, Multiplayer::TransferMode p_mode = Multiplayer::TRANSFER_MODE_RELIABLE, int p_channel = 0); Error encode_and_compress_variant(const Variant &p_variant, uint8_t *p_buffer, int &r_len); Error decode_and_decompress_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int *r_len); // Called by Node.rpc - void rpcp(Node *p_node, int p_peer_id, bool p_unreliable, const StringName &p_method, const Variant **p_arg, int p_argcount); + void rpcp(Node *p_node, int p_peer_id, const StringName &p_method, const Variant **p_arg, int p_argcount); // Called by Node._notification void scene_enter_exit_notify(const String &p_scene, Node *p_node, bool p_enter); // Called by replicator bool send_confirm_path(Node *p_node, NodePath p_path, int p_target, int &p_id); Node *get_cached_node(int p_from, uint32_t p_node_id); + bool is_cache_confirmed(NodePath p_path, int p_peer); void _add_peer(int p_id); void _del_peer(int p_id); @@ -160,23 +135,28 @@ public: void _connection_failed(); void _server_disconnected(); - bool has_network_peer() const { return network_peer.is_valid(); } - Vector<int> get_network_connected_peers() const; - int get_rpc_sender_id() const { return rpc_sender_id; } - int get_network_unique_id() const; - bool is_network_server() const; - void set_refuse_new_network_connections(bool p_refuse); - bool is_refusing_new_network_connections() const; + bool has_multiplayer_peer() const { return multiplayer_peer.is_valid(); } + Vector<int> get_peer_ids() const; + const Set<int> get_connected_peers() const { return connected_peers; } + int get_remote_sender_id() const { return remote_sender_override ? remote_sender_override : remote_sender_id; } + void set_remote_sender_override(int p_id) { remote_sender_override = p_id; } + int get_unique_id() const; + bool is_server() const; + void set_refuse_new_connections(bool p_refuse); + bool is_refusing_new_connections() const; void set_allow_object_decoding(bool p_enable); bool is_object_decoding_allowed() const; - MultiplayerReplicator *get_replicator() const; + MultiplayerReplicator *get_replicator() const { return replicator; } + RPCManager *get_rpc_manager() const { return rpc_manager; } + +#ifdef DEBUG_ENABLED + void profile_bandwidth(const String &p_inout, int p_size); +#endif MultiplayerAPI(); ~MultiplayerAPI(); }; -VARIANT_ENUM_CAST(MultiplayerAPI::RPCMode); - #endif // MULTIPLAYER_API_H diff --git a/core/io/multiplayer_peer.cpp b/core/multiplayer/multiplayer_peer.cpp index 83cf24d7e3..40847102d8 100644 --- a/core/io/multiplayer_peer.cpp +++ b/core/multiplayer/multiplayer_peer.cpp @@ -75,10 +75,6 @@ void MultiplayerPeer::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "transfer_mode", PROPERTY_HINT_ENUM, "Unreliable,Unreliable Ordered,Reliable"), "set_transfer_mode", "get_transfer_mode"); ADD_PROPERTY(PropertyInfo(Variant::INT, "transfer_channel", PROPERTY_HINT_RANGE, "0,255,1"), "set_transfer_channel", "get_transfer_channel"); - BIND_ENUM_CONSTANT(TRANSFER_MODE_UNRELIABLE); - BIND_ENUM_CONSTANT(TRANSFER_MODE_UNRELIABLE_ORDERED); - BIND_ENUM_CONSTANT(TRANSFER_MODE_RELIABLE); - BIND_ENUM_CONSTANT(CONNECTION_DISCONNECTED); BIND_ENUM_CONSTANT(CONNECTION_CONNECTING); BIND_ENUM_CONSTANT(CONNECTION_CONNECTED); diff --git a/core/io/multiplayer_peer.h b/core/multiplayer/multiplayer_peer.h index 7ca4e7930b..ba00c3b41b 100644 --- a/core/io/multiplayer_peer.h +++ b/core/multiplayer/multiplayer_peer.h @@ -32,6 +32,7 @@ #define NETWORKED_MULTIPLAYER_PEER_H #include "core/io/packet_peer.h" +#include "core/multiplayer/multiplayer.h" class MultiplayerPeer : public PacketPeer { GDCLASS(MultiplayerPeer, PacketPeer); @@ -44,11 +45,6 @@ public: TARGET_PEER_BROADCAST = 0, TARGET_PEER_SERVER = 1 }; - enum TransferMode { - TRANSFER_MODE_UNRELIABLE, - TRANSFER_MODE_UNRELIABLE_ORDERED, - TRANSFER_MODE_RELIABLE, - }; enum ConnectionStatus { CONNECTION_DISCONNECTED, @@ -58,8 +54,8 @@ public: virtual void set_transfer_channel(int p_channel) = 0; virtual int get_transfer_channel() const = 0; - virtual void set_transfer_mode(TransferMode p_mode) = 0; - virtual TransferMode get_transfer_mode() const = 0; + virtual void set_transfer_mode(Multiplayer::TransferMode p_mode) = 0; + virtual Multiplayer::TransferMode get_transfer_mode() const = 0; virtual void set_target_peer(int p_peer_id) = 0; virtual int get_packet_peer() const = 0; @@ -79,7 +75,6 @@ public: MultiplayerPeer() {} }; -VARIANT_ENUM_CAST(MultiplayerPeer::TransferMode) VARIANT_ENUM_CAST(MultiplayerPeer::ConnectionStatus) #endif // NETWORKED_MULTIPLAYER_PEER_H diff --git a/core/io/multiplayer_replicator.cpp b/core/multiplayer/multiplayer_replicator.cpp index 1642aab136..17af2c5ef8 100644 --- a/core/io/multiplayer_replicator.cpp +++ b/core/multiplayer/multiplayer_replicator.cpp @@ -28,7 +28,7 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#include "core/io/multiplayer_replicator.h" +#include "core/multiplayer/multiplayer_replicator.h" #include "core/io/marshalls.h" #include "scene/main/node.h" @@ -88,7 +88,7 @@ Error MultiplayerReplicator::_sync_all_default(const ResourceUID::ID &p_scene_id } int ofs = 0; uint8_t *ptr = packet_cache.ptrw(); - ptr[0] = MultiplayerAPI::NETWORK_COMMAND_SYNC + ((same_size ? 1 : 0) << MultiplayerAPI::BYTE_ONLY_OR_NO_ARGS_SHIFT); + ptr[0] = MultiplayerAPI::NETWORK_COMMAND_SYNC | (same_size ? BYTE_OR_ZERO_FLAG : 0); ofs = 1; ofs += encode_uint64(p_scene_id, &ptr[ofs]); ptr[ofs] = cfg.sync_recv++; @@ -113,19 +113,19 @@ Error MultiplayerReplicator::_sync_all_default(const ResourceUID::ID &p_scene_id ERR_CONTINUE(err); ofs += size; } - Ref<MultiplayerPeer> network_peer = multiplayer->get_network_peer(); - network_peer->set_target_peer(p_peer); - network_peer->set_transfer_channel(0); - network_peer->set_transfer_mode(MultiplayerPeer::TRANSFER_MODE_UNRELIABLE); - return network_peer->put_packet(ptr, ofs); + Ref<MultiplayerPeer> peer = multiplayer->get_multiplayer_peer(); + peer->set_target_peer(p_peer); + peer->set_transfer_channel(0); + peer->set_transfer_mode(Multiplayer::TRANSFER_MODE_UNRELIABLE); + return peer->put_packet(ptr, ofs); } void MultiplayerReplicator::_process_default_sync(const ResourceUID::ID &p_id, const uint8_t *p_packet, int p_packet_len) { ERR_FAIL_COND_MSG(p_packet_len < SYNC_CMD_OFFSET + 5, "Invalid spawn packet received"); ERR_FAIL_COND_MSG(!replications.has(p_id), "Invalid spawn ID received " + itos(p_id)); SceneConfig &cfg = replications[p_id]; - ERR_FAIL_COND_MSG(cfg.mode != REPLICATION_MODE_SERVER || multiplayer->is_network_server(), "The defualt implementation only allows sync packets from the server"); - const bool same_size = ((p_packet[0] & 64) >> MultiplayerAPI::BYTE_ONLY_OR_NO_ARGS_SHIFT) == 1; + ERR_FAIL_COND_MSG(cfg.mode != REPLICATION_MODE_SERVER || multiplayer->is_server(), "The defualt implementation only allows sync packets from the server"); + const bool same_size = p_packet[0] & BYTE_OR_ZERO_FLAG; int ofs = SYNC_CMD_OFFSET; int time = p_packet[ofs]; // Skip old update. @@ -189,6 +189,8 @@ Error MultiplayerReplicator::_send_default_spawn_despawn(int p_peer_id, const Re bool is_raw = false; if (state_variants.size() == 1 && state_variants[0].get_type() == Variant::PACKED_BYTE_ARRAY) { is_raw = true; + const PackedByteArray pba = state_variants[0]; + state_len = pba.size(); } else if (state_variants.size()) { err = _encode_state(state_variants, nullptr, state_len); ERR_FAIL_COND_V(err, err); @@ -216,7 +218,7 @@ Error MultiplayerReplicator::_send_default_spawn_despawn(int p_peer_id, const Re int nlen = encode_cstring(cname.get_data(), nullptr); MAKE_ROOM(SPAWN_CMD_OFFSET + 4 + 4 + nlen + state_len); uint8_t *ptr = packet_cache.ptrw(); - ptr[0] = (p_spawn ? MultiplayerAPI::NETWORK_COMMAND_SPAWN : MultiplayerAPI::NETWORK_COMMAND_DESPAWN) + ((is_raw ? 1 : 0) << MultiplayerAPI::BYTE_ONLY_OR_NO_ARGS_SHIFT); + ptr[0] = (p_spawn ? MultiplayerAPI::NETWORK_COMMAND_SPAWN : MultiplayerAPI::NETWORK_COMMAND_DESPAWN) | (is_raw ? BYTE_OR_ZERO_FLAG : 0); ofs = 1; ofs += encode_uint64(p_scene_id, &ptr[ofs]); ofs += encode_uint32(path_id, &ptr[ofs]); @@ -231,11 +233,11 @@ Error MultiplayerReplicator::_send_default_spawn_despawn(int p_peer_id, const Re memcpy(&ptr[ofs], pba.ptr(), state_len); } - Ref<MultiplayerPeer> network_peer = multiplayer->get_network_peer(); - network_peer->set_target_peer(p_peer_id); - network_peer->set_transfer_channel(0); - network_peer->set_transfer_mode(MultiplayerPeer::TRANSFER_MODE_RELIABLE); - return network_peer->put_packet(ptr, ofs + state_len); + Ref<MultiplayerPeer> peer = multiplayer->get_multiplayer_peer(); + peer->set_target_peer(p_peer_id); + peer->set_transfer_channel(0); + peer->set_transfer_mode(Multiplayer::TRANSFER_MODE_RELIABLE); + return peer->put_packet(ptr, ofs + state_len); } void MultiplayerReplicator::_process_default_spawn_despawn(int p_from, const ResourceUID::ID &p_scene_id, const uint8_t *p_packet, int p_packet_len, bool p_spawn) { @@ -260,7 +262,7 @@ void MultiplayerReplicator::_process_default_spawn_despawn(int p_from, const Res if (cfg.mode == REPLICATION_MODE_SERVER && p_from == 1) { String scene_path = ResourceUID::get_singleton()->get_id_path(p_scene_id); if (p_spawn) { - const bool is_raw = ((p_packet[0] & 64) >> MultiplayerAPI::BYTE_ONLY_OR_NO_ARGS_SHIFT) == 1; + const bool is_raw = ((p_packet[0] & BYTE_OR_ZERO_FLAG) >> BYTE_OR_ZERO_SHIFT) == 1; ERR_FAIL_COND_MSG(parent->has_node(name), vformat("Unable to spawn node. Node already exists: %s/%s", parent->get_path(), name)); RES res = ResourceLoader::load(scene_path); @@ -306,7 +308,7 @@ void MultiplayerReplicator::process_spawn_despawn(int p_from, const uint8_t *p_p const SceneConfig &cfg = replications[id]; if (cfg.on_spawn_despawn_receive.is_valid()) { int ofs = SPAWN_CMD_OFFSET; - bool is_raw = ((p_packet[0] & 64) >> MultiplayerAPI::BYTE_ONLY_OR_NO_ARGS_SHIFT) == 1; + bool is_raw = ((p_packet[0] & BYTE_OR_ZERO_FLAG) >> BYTE_OR_ZERO_SHIFT) == 1; Variant data; int left = p_packet_len - ofs; if (is_raw && left) { @@ -464,7 +466,7 @@ Error MultiplayerReplicator::spawn_config(const ResourceUID::ID &p_id, Replicati SceneConfig cfg; cfg.mode = p_mode; for (int i = 0; i < p_props.size(); i++) { - cfg.properties.push_back(StringName(p_props[i])); + cfg.properties.push_back(p_props[i]); } cfg.on_spawn_despawn_send = p_on_send; cfg.on_spawn_despawn_receive = p_on_recv; @@ -503,7 +505,7 @@ Error MultiplayerReplicator::_send_spawn_despawn(int p_peer_id, const ResourceUI } MAKE_ROOM(SPAWN_CMD_OFFSET + data_size); uint8_t *ptr = packet_cache.ptrw(); - ptr[0] = (p_spawn ? MultiplayerAPI::NETWORK_COMMAND_SPAWN : MultiplayerAPI::NETWORK_COMMAND_DESPAWN) + ((is_raw ? 1 : 0) << MultiplayerAPI::BYTE_ONLY_OR_NO_ARGS_SHIFT); + ptr[0] = (p_spawn ? MultiplayerAPI::NETWORK_COMMAND_SPAWN : MultiplayerAPI::NETWORK_COMMAND_DESPAWN) + ((is_raw ? 1 : 0) << BYTE_OR_ZERO_SHIFT); encode_uint64(p_scene_id, &ptr[1]); if (p_data.get_type() == Variant::PACKED_BYTE_ARRAY) { const PackedByteArray pba = p_data; @@ -511,21 +513,21 @@ Error MultiplayerReplicator::_send_spawn_despawn(int p_peer_id, const ResourceUI } else if (data_size) { encode_variant(p_data, &ptr[SPAWN_CMD_OFFSET], data_size); } - Ref<MultiplayerPeer> network_peer = multiplayer->get_network_peer(); - network_peer->set_target_peer(p_peer_id); - network_peer->set_transfer_channel(0); - network_peer->set_transfer_mode(MultiplayerPeer::TRANSFER_MODE_RELIABLE); - return network_peer->put_packet(ptr, SPAWN_CMD_OFFSET + data_size); + Ref<MultiplayerPeer> peer = multiplayer->get_multiplayer_peer(); + peer->set_target_peer(p_peer_id); + peer->set_transfer_channel(0); + peer->set_transfer_mode(Multiplayer::TRANSFER_MODE_RELIABLE); + return peer->put_packet(ptr, SPAWN_CMD_OFFSET + data_size); } Error MultiplayerReplicator::send_despawn(int p_peer_id, const ResourceUID::ID &p_scene_id, const Variant &p_data, const NodePath &p_path) { - ERR_FAIL_COND_V(!multiplayer->has_network_peer(), ERR_UNCONFIGURED); + ERR_FAIL_COND_V(!multiplayer->has_multiplayer_peer(), ERR_UNCONFIGURED); ERR_FAIL_COND_V_MSG(!replications.has(p_scene_id), ERR_INVALID_PARAMETER, vformat("Spawnable not found: %d", p_scene_id)); const SceneConfig &cfg = replications[p_scene_id]; if (cfg.on_spawn_despawn_send.is_valid()) { return _send_spawn_despawn(p_peer_id, p_scene_id, p_data, true); } else { - ERR_FAIL_COND_V_MSG(cfg.mode == REPLICATION_MODE_SERVER && multiplayer->is_network_server(), ERR_UNAVAILABLE, "Manual despawn is restricted in default server mode implementation. Use custom mode if you desire control over server spawn requests."); + ERR_FAIL_COND_V_MSG(cfg.mode == REPLICATION_MODE_SERVER && multiplayer->is_server(), ERR_UNAVAILABLE, "Manual despawn is restricted in default server mode implementation. Use custom mode if you desire control over server spawn requests."); NodePath path = p_path; Object *obj = p_data.get_type() == Variant::OBJECT ? p_data.get_validated_object() : nullptr; if (path.is_empty() && obj) { @@ -540,13 +542,13 @@ Error MultiplayerReplicator::send_despawn(int p_peer_id, const ResourceUID::ID & } Error MultiplayerReplicator::send_spawn(int p_peer_id, const ResourceUID::ID &p_scene_id, const Variant &p_data, const NodePath &p_path) { - ERR_FAIL_COND_V(!multiplayer->has_network_peer(), ERR_UNCONFIGURED); + ERR_FAIL_COND_V(!multiplayer->has_multiplayer_peer(), ERR_UNCONFIGURED); ERR_FAIL_COND_V_MSG(!replications.has(p_scene_id), ERR_INVALID_PARAMETER, vformat("Spawnable not found: %d", p_scene_id)); const SceneConfig &cfg = replications[p_scene_id]; if (cfg.on_spawn_despawn_send.is_valid()) { return _send_spawn_despawn(p_peer_id, p_scene_id, p_data, false); } else { - ERR_FAIL_COND_V_MSG(cfg.mode == REPLICATION_MODE_SERVER && multiplayer->is_network_server(), ERR_UNAVAILABLE, "Manual spawn is restricted in default server mode implementation. Use custom mode if you desire control over server spawn requests."); + ERR_FAIL_COND_V_MSG(cfg.mode == REPLICATION_MODE_SERVER && multiplayer->is_server(), ERR_UNAVAILABLE, "Manual spawn is restricted in default server mode implementation. Use custom mode if you desire control over server spawn requests."); NodePath path = p_path; Object *obj = p_data.get_type() == Variant::OBJECT ? p_data.get_validated_object() : nullptr; ERR_FAIL_COND_V_MSG(!obj, ERR_INVALID_PARAMETER, "Spawn default implementation requires the data to be an object."); @@ -617,7 +619,7 @@ Error MultiplayerReplicator::decode_state(const ResourceUID::ID &p_scene_id, Obj } void MultiplayerReplicator::scene_enter_exit_notify(const String &p_scene, Node *p_node, bool p_enter) { - if (!multiplayer->has_network_peer()) { + if (!multiplayer->has_multiplayer_peer()) { return; } Node *root_node = multiplayer->get_root_node(); @@ -632,14 +634,14 @@ void MultiplayerReplicator::scene_enter_exit_notify(const String &p_scene, Node } const SceneConfig &cfg = replications[id]; if (p_enter) { - if (cfg.mode == REPLICATION_MODE_SERVER && multiplayer->is_network_server()) { + if (cfg.mode == REPLICATION_MODE_SERVER && multiplayer->is_server()) { replicated_nodes[p_node->get_instance_id()] = id; _track(id, p_node); spawn(id, p_node, 0); } emit_signal(SNAME("replicated_instance_added"), id, p_node); } else { - if (cfg.mode == REPLICATION_MODE_SERVER && multiplayer->is_network_server() && replicated_nodes.has(p_node->get_instance_id())) { + if (cfg.mode == REPLICATION_MODE_SERVER && multiplayer->is_server() && replicated_nodes.has(p_node->get_instance_id())) { replicated_nodes.erase(p_node->get_instance_id()); _untrack(id, p_node); despawn(id, p_node, 0); @@ -664,7 +666,7 @@ void MultiplayerReplicator::poll() { if (!E.value.sync_interval) { continue; } - if (E.value.mode == REPLICATION_MODE_SERVER && !multiplayer->is_network_server()) { + if (E.value.mode == REPLICATION_MODE_SERVER && !multiplayer->is_server()) { continue; } uint64_t time = OS::get_singleton()->get_ticks_usec(); @@ -738,8 +740,8 @@ Error MultiplayerReplicator::sync_all(const ResourceUID::ID &p_scene_id, int p_p return OK; } -Error MultiplayerReplicator::send_sync(int p_peer_id, const ResourceUID::ID &p_scene_id, PackedByteArray p_data, MultiplayerPeer::TransferMode p_transfer_mode, int p_channel) { - ERR_FAIL_COND_V(!multiplayer->has_network_peer(), ERR_UNCONFIGURED); +Error MultiplayerReplicator::send_sync(int p_peer_id, const ResourceUID::ID &p_scene_id, PackedByteArray p_data, Multiplayer::TransferMode p_transfer_mode, int p_channel) { + ERR_FAIL_COND_V(!multiplayer->has_multiplayer_peer(), ERR_UNCONFIGURED); ERR_FAIL_COND_V(!replications.has(p_scene_id), ERR_INVALID_PARAMETER); const SceneConfig &cfg = replications[p_scene_id]; ERR_FAIL_COND_V_MSG(!cfg.on_sync_send.is_valid(), ERR_UNCONFIGURED, "Sending raw sync messages is only available with custom functions"); @@ -747,11 +749,11 @@ Error MultiplayerReplicator::send_sync(int p_peer_id, const ResourceUID::ID &p_s uint8_t *ptr = packet_cache.ptrw(); ptr[0] = MultiplayerAPI::NETWORK_COMMAND_SYNC; encode_uint64(p_scene_id, &ptr[1]); - Ref<MultiplayerPeer> network_peer = multiplayer->get_network_peer(); - network_peer->set_target_peer(p_peer_id); - network_peer->set_transfer_channel(p_channel); - network_peer->set_transfer_mode(p_transfer_mode); - return network_peer->put_packet(ptr, SYNC_CMD_OFFSET + p_data.size()); + Ref<MultiplayerPeer> peer = multiplayer->get_multiplayer_peer(); + peer->set_target_peer(p_peer_id); + peer->set_transfer_channel(p_channel); + peer->set_transfer_mode(p_transfer_mode); + return peer->put_packet(ptr, SYNC_CMD_OFFSET + p_data.size()); } void MultiplayerReplicator::clear() { @@ -766,7 +768,7 @@ void MultiplayerReplicator::_bind_methods() { ClassDB::bind_method(D_METHOD("spawn", "scene_id", "object", "peer_id"), &MultiplayerReplicator::spawn, DEFVAL(0)); ClassDB::bind_method(D_METHOD("send_despawn", "peer_id", "scene_id", "data", "path"), &MultiplayerReplicator::send_despawn, DEFVAL(Variant()), DEFVAL(NodePath())); ClassDB::bind_method(D_METHOD("send_spawn", "peer_id", "scene_id", "data", "path"), &MultiplayerReplicator::send_spawn, DEFVAL(Variant()), DEFVAL(NodePath())); - ClassDB::bind_method(D_METHOD("send_sync", "peer_id", "scene_id", "data", "transfer_mode", "channel"), &MultiplayerReplicator::send_sync, DEFVAL(MultiplayerPeer::TRANSFER_MODE_RELIABLE), DEFVAL(0)); + ClassDB::bind_method(D_METHOD("send_sync", "peer_id", "scene_id", "data", "transfer_mode", "channel"), &MultiplayerReplicator::send_sync, DEFVAL(Multiplayer::TRANSFER_MODE_RELIABLE), DEFVAL(0)); ClassDB::bind_method(D_METHOD("sync_all", "scene_id", "peer_id"), &MultiplayerReplicator::sync_all, DEFVAL(0)); ClassDB::bind_method(D_METHOD("track", "scene_id", "object"), &MultiplayerReplicator::track); ClassDB::bind_method(D_METHOD("untrack", "scene_id", "object"), &MultiplayerReplicator::untrack); diff --git a/core/io/multiplayer_replicator.h b/core/multiplayer/multiplayer_replicator.h index 2630ad7a8a..7fa774fdf4 100644 --- a/core/io/multiplayer_replicator.h +++ b/core/multiplayer/multiplayer_replicator.h @@ -31,8 +31,9 @@ #ifndef MULTIPLAYER_REPLICATOR_H #define MULTIPLAYER_REPLICATOR_H -#include "core/io/multiplayer_api.h" +#include "core/multiplayer/multiplayer_api.h" +#include "core/io/resource_uid.h" #include "core/templates/hash_map.h" #include "core/variant/typed_array.h" @@ -68,6 +69,14 @@ protected: static void _bind_methods(); private: + enum { + BYTE_OR_ZERO_SHIFT = MultiplayerAPI::CMD_FLAG_0_SHIFT, + }; + + enum { + BYTE_OR_ZERO_FLAG = 1 << BYTE_OR_ZERO_SHIFT, + }; + MultiplayerAPI *multiplayer = nullptr; Vector<uint8_t> packet_cache; Map<ResourceUID::ID, SceneConfig> replications; @@ -108,7 +117,7 @@ public: // Sync Error sync_config(const ResourceUID::ID &p_id, uint64_t p_interval, const TypedArray<StringName> &p_props = TypedArray<StringName>(), const Callable &p_on_send = Callable(), const Callable &p_on_recv = Callable()); Error sync_all(const ResourceUID::ID &p_scene_id, int p_peer); - Error send_sync(int p_peer_id, const ResourceUID::ID &p_scene_id, PackedByteArray p_data, MultiplayerPeer::TransferMode p_mode, int p_channel); + Error send_sync(int p_peer_id, const ResourceUID::ID &p_scene_id, PackedByteArray p_data, Multiplayer::TransferMode p_mode, int p_channel); void track(const ResourceUID::ID &p_scene_id, Object *p_object); void untrack(const ResourceUID::ID &p_scene_id, Object *p_object); diff --git a/core/multiplayer/rpc_manager.cpp b/core/multiplayer/rpc_manager.cpp new file mode 100644 index 0000000000..915571375e --- /dev/null +++ b/core/multiplayer/rpc_manager.cpp @@ -0,0 +1,525 @@ +/*************************************************************************/ +/* rpc_manager.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "core/multiplayer/rpc_manager.h" + +#include "core/debugger/engine_debugger.h" +#include "core/io/marshalls.h" +#include "core/multiplayer/multiplayer_api.h" +#include "scene/main/node.h" + +#ifdef DEBUG_ENABLED +_FORCE_INLINE_ void RPCManager::_profile_node_data(const String &p_what, ObjectID p_id) { + if (EngineDebugger::is_profiling("multiplayer")) { + Array values; + values.push_back("node"); + values.push_back(p_id); + values.push_back(p_what); + EngineDebugger::profiler_add_frame_data("multiplayer", values); + } +} +#else +_FORCE_INLINE_ void RPCManager::_profile_node_data(const String &p_what, ObjectID p_id) {} +#endif + +// Returns the packet size stripping the node path added when the node is not yet cached. +int get_packet_len(uint32_t p_node_target, int p_packet_len) { + if (p_node_target & 0x80000000) { + int ofs = p_node_target & 0x7FFFFFFF; + return p_packet_len - (p_packet_len - ofs); + } else { + return p_packet_len; + } +} + +const Multiplayer::RPCConfig _get_rpc_config(const Node *p_node, const StringName &p_method, uint16_t &r_id) { + const Vector<Multiplayer::RPCConfig> node_config = p_node->get_node_rpc_methods(); + for (int i = 0; i < node_config.size(); i++) { + if (node_config[i].name == p_method) { + r_id = ((uint16_t)i) | (1 << 15); + return node_config[i]; + } + } + if (p_node->get_script_instance()) { + const Vector<Multiplayer::RPCConfig> script_config = p_node->get_script_instance()->get_rpc_methods(); + for (int i = 0; i < script_config.size(); i++) { + if (script_config[i].name == p_method) { + r_id = (uint16_t)i; + return script_config[i]; + } + } + } + return Multiplayer::RPCConfig(); +} + +const Multiplayer::RPCConfig _get_rpc_config_by_id(Node *p_node, uint16_t p_id) { + Vector<Multiplayer::RPCConfig> config; + uint16_t id = p_id; + if (id & (1 << 15)) { + id = id & ~(1 << 15); + config = p_node->get_node_rpc_methods(); + } else if (p_node->get_script_instance()) { + config = p_node->get_script_instance()->get_rpc_methods(); + } + if (id < config.size()) { + return config[id]; + } + return Multiplayer::RPCConfig(); +} + +_FORCE_INLINE_ bool _can_call_mode(Node *p_node, Multiplayer::RPCMode mode, int p_remote_id) { + switch (mode) { + case Multiplayer::RPC_MODE_DISABLED: { + return false; + } break; + case Multiplayer::RPC_MODE_ANY: { + return true; + } break; + case Multiplayer::RPC_MODE_AUTHORITY: { + return !p_node->is_multiplayer_authority() && p_remote_id == p_node->get_multiplayer_authority(); + } break; + } + + return false; +} + +String RPCManager::get_rpc_md5(const Node *p_node) { + String rpc_list; + const Vector<Multiplayer::RPCConfig> node_config = p_node->get_node_rpc_methods(); + for (int i = 0; i < node_config.size(); i++) { + rpc_list += String(node_config[i].name); + } + if (p_node->get_script_instance()) { + const Vector<Multiplayer::RPCConfig> script_config = p_node->get_script_instance()->get_rpc_methods(); + for (int i = 0; i < script_config.size(); i++) { + rpc_list += String(script_config[i].name); + } + } + return rpc_list.md5_text(); +} + +Node *RPCManager::_process_get_node(int p_from, const uint8_t *p_packet, uint32_t p_node_target, int p_packet_len) { + Node *node = nullptr; + + if (p_node_target & 0x80000000) { + // Use full path (not cached yet). + int ofs = p_node_target & 0x7FFFFFFF; + + ERR_FAIL_COND_V_MSG(ofs >= p_packet_len, nullptr, "Invalid packet received. Size smaller than declared."); + + String paths; + paths.parse_utf8((const char *)&p_packet[ofs], p_packet_len - ofs); + + NodePath np = paths; + + node = multiplayer->get_root_node()->get_node(np); + + if (!node) { + ERR_PRINT("Failed to get path from RPC: " + String(np) + "."); + } + return node; + } else { + // Use cached path. + return multiplayer->get_cached_node(p_from, p_node_target); + } +} + +void RPCManager::process_rpc(int p_from, const uint8_t *p_packet, int p_packet_len) { + // Extract packet meta + int packet_min_size = 1; + int name_id_offset = 1; + ERR_FAIL_COND_MSG(p_packet_len < packet_min_size, "Invalid packet received. Size too small."); + // Compute the meta size, which depends on the compression level. + int node_id_compression = (p_packet[0] & NODE_ID_COMPRESSION_FLAG) >> NODE_ID_COMPRESSION_SHIFT; + int name_id_compression = (p_packet[0] & NAME_ID_COMPRESSION_FLAG) >> NAME_ID_COMPRESSION_SHIFT; + + switch (node_id_compression) { + case NETWORK_NODE_ID_COMPRESSION_8: + packet_min_size += 1; + name_id_offset += 1; + break; + case NETWORK_NODE_ID_COMPRESSION_16: + packet_min_size += 2; + name_id_offset += 2; + break; + case NETWORK_NODE_ID_COMPRESSION_32: + packet_min_size += 4; + name_id_offset += 4; + break; + default: + ERR_FAIL_MSG("Was not possible to extract the node id compression mode."); + } + switch (name_id_compression) { + case NETWORK_NAME_ID_COMPRESSION_8: + packet_min_size += 1; + break; + case NETWORK_NAME_ID_COMPRESSION_16: + packet_min_size += 2; + break; + default: + ERR_FAIL_MSG("Was not possible to extract the name id compression mode."); + } + ERR_FAIL_COND_MSG(p_packet_len < packet_min_size, "Invalid packet received. Size too small."); + + uint32_t node_target = 0; + switch (node_id_compression) { + case NETWORK_NODE_ID_COMPRESSION_8: + node_target = p_packet[1]; + break; + case NETWORK_NODE_ID_COMPRESSION_16: + node_target = decode_uint16(p_packet + 1); + break; + case NETWORK_NODE_ID_COMPRESSION_32: + node_target = decode_uint32(p_packet + 1); + break; + default: + // Unreachable, checked before. + CRASH_NOW(); + } + + Node *node = _process_get_node(p_from, p_packet, node_target, p_packet_len); + ERR_FAIL_COND_MSG(node == nullptr, "Invalid packet received. Requested node was not found."); + + uint16_t name_id = 0; + switch (name_id_compression) { + case NETWORK_NAME_ID_COMPRESSION_8: + name_id = p_packet[name_id_offset]; + break; + case NETWORK_NAME_ID_COMPRESSION_16: + name_id = decode_uint16(p_packet + name_id_offset); + break; + default: + // Unreachable, checked before. + CRASH_NOW(); + } + + const int packet_len = get_packet_len(node_target, p_packet_len); + _process_rpc(node, name_id, p_from, p_packet, packet_len, packet_min_size); +} + +void RPCManager::_process_rpc(Node *p_node, const uint16_t p_rpc_method_id, int p_from, const uint8_t *p_packet, int p_packet_len, int p_offset) { + ERR_FAIL_COND_MSG(p_offset > p_packet_len, "Invalid packet received. Size too small."); + + // Check that remote can call the RPC on this node. + const Multiplayer::RPCConfig config = _get_rpc_config_by_id(p_node, p_rpc_method_id); + ERR_FAIL_COND(config.name == StringName()); + + bool can_call = _can_call_mode(p_node, config.rpc_mode, p_from); + ERR_FAIL_COND_MSG(!can_call, "RPC '" + String(config.name) + "' is not allowed on node " + p_node->get_path() + " from: " + itos(p_from) + ". Mode is " + itos((int)config.rpc_mode) + ", authority is " + itos(p_node->get_multiplayer_authority()) + "."); + + int argc = 0; + bool byte_only = false; + + const bool byte_only_or_no_args = p_packet[0] & BYTE_ONLY_OR_NO_ARGS_FLAG; + if (byte_only_or_no_args) { + if (p_offset < p_packet_len) { + // This packet contains only bytes. + argc = 1; + byte_only = true; + } else { + // This rpc calls a method without parameters. + } + } else { + // Normal variant, takes the argument count from the packet. + ERR_FAIL_COND_MSG(p_offset >= p_packet_len, "Invalid packet received. Size too small."); + argc = p_packet[p_offset]; + p_offset += 1; + } + + Vector<Variant> args; + Vector<const Variant *> argp; + args.resize(argc); + argp.resize(argc); + +#ifdef DEBUG_ENABLED + _profile_node_data("in_rpc", p_node->get_instance_id()); +#endif + + if (byte_only) { + Vector<uint8_t> pure_data; + const int len = p_packet_len - p_offset; + pure_data.resize(len); + memcpy(pure_data.ptrw(), &p_packet[p_offset], len); + args.write[0] = pure_data; + argp.write[0] = &args[0]; + p_offset += len; + } else { + for (int i = 0; i < argc; i++) { + ERR_FAIL_COND_MSG(p_offset >= p_packet_len, "Invalid packet received. Size too small."); + + int vlen; + Error err = multiplayer->decode_and_decompress_variant(args.write[i], &p_packet[p_offset], p_packet_len - p_offset, &vlen); + ERR_FAIL_COND_MSG(err != OK, "Invalid packet received. Unable to decode RPC argument."); + + argp.write[i] = &args[i]; + p_offset += vlen; + } + } + + Callable::CallError ce; + + p_node->call(config.name, (const Variant **)argp.ptr(), argc, ce); + if (ce.error != Callable::CallError::CALL_OK) { + String error = Variant::get_call_error_text(p_node, config.name, (const Variant **)argp.ptr(), argc, ce); + error = "RPC - " + error; + ERR_PRINT(error); + } +} + +void RPCManager::_send_rpc(Node *p_from, int p_to, uint16_t p_rpc_id, const Multiplayer::RPCConfig &p_config, const StringName &p_name, const Variant **p_arg, int p_argcount) { + Ref<MultiplayerPeer> peer = multiplayer->get_multiplayer_peer(); + ERR_FAIL_COND_MSG(peer.is_null(), "Attempt to call RPC without active multiplayer peer."); + + ERR_FAIL_COND_MSG(peer->get_connection_status() == MultiplayerPeer::CONNECTION_CONNECTING, "Attempt to call RPC while multiplayer peer is not connected yet."); + + ERR_FAIL_COND_MSG(peer->get_connection_status() == MultiplayerPeer::CONNECTION_DISCONNECTED, "Attempt to call RPC while multiplayer peer is disconnected."); + + ERR_FAIL_COND_MSG(p_argcount > 255, "Too many arguments (>255)."); + + if (p_to != 0 && !multiplayer->get_connected_peers().has(ABS(p_to))) { + ERR_FAIL_COND_MSG(p_to == peer->get_unique_id(), "Attempt to call RPC on yourself! Peer unique ID: " + itos(peer->get_unique_id()) + "."); + + ERR_FAIL_MSG("Attempt to call RPC with unknown peer ID: " + itos(p_to) + "."); + } + + NodePath from_path = (multiplayer->get_root_node()->get_path()).rel_path_to(p_from->get_path()); + ERR_FAIL_COND_MSG(from_path.is_empty(), "Unable to send RPC. Relative path is empty. THIS IS LIKELY A BUG IN THE ENGINE!"); + + // See if all peers have cached path (if so, call can be fast). + int psc_id; + const bool has_all_peers = multiplayer->send_confirm_path(p_from, from_path, p_to, psc_id); + + // Create base packet, lots of hardcode because it must be tight. + + int ofs = 0; + +#define MAKE_ROOM(m_amount) \ + if (packet_cache.size() < m_amount) \ + packet_cache.resize(m_amount); + + // Encode meta. + uint8_t command_type = MultiplayerAPI::NETWORK_COMMAND_REMOTE_CALL; + uint8_t node_id_compression = UINT8_MAX; + uint8_t name_id_compression = UINT8_MAX; + bool byte_only_or_no_args = false; + + MAKE_ROOM(1); + // The meta is composed along the way, so just set 0 for now. + packet_cache.write[0] = 0; + ofs += 1; + + // Encode Node ID. + if (has_all_peers) { + // Compress the node ID only if all the target peers already know it. + if (psc_id >= 0 && psc_id <= 255) { + // We can encode the id in 1 byte + node_id_compression = NETWORK_NODE_ID_COMPRESSION_8; + MAKE_ROOM(ofs + 1); + packet_cache.write[ofs] = static_cast<uint8_t>(psc_id); + ofs += 1; + } else if (psc_id >= 0 && psc_id <= 65535) { + // We can encode the id in 2 bytes + node_id_compression = NETWORK_NODE_ID_COMPRESSION_16; + MAKE_ROOM(ofs + 2); + encode_uint16(static_cast<uint16_t>(psc_id), &(packet_cache.write[ofs])); + ofs += 2; + } else { + // Too big, let's use 4 bytes. + node_id_compression = NETWORK_NODE_ID_COMPRESSION_32; + MAKE_ROOM(ofs + 4); + encode_uint32(psc_id, &(packet_cache.write[ofs])); + ofs += 4; + } + } else { + // The targets don't know the node yet, so we need to use 32 bits int. + node_id_compression = NETWORK_NODE_ID_COMPRESSION_32; + MAKE_ROOM(ofs + 4); + encode_uint32(psc_id, &(packet_cache.write[ofs])); + ofs += 4; + } + + // Encode method ID + if (p_rpc_id <= UINT8_MAX) { + // The ID fits in 1 byte + name_id_compression = NETWORK_NAME_ID_COMPRESSION_8; + MAKE_ROOM(ofs + 1); + packet_cache.write[ofs] = static_cast<uint8_t>(p_rpc_id); + ofs += 1; + } else { + // The ID is larger, let's use 2 bytes + name_id_compression = NETWORK_NAME_ID_COMPRESSION_16; + MAKE_ROOM(ofs + 2); + encode_uint16(p_rpc_id, &(packet_cache.write[ofs])); + ofs += 2; + } + + if (p_argcount == 0) { + byte_only_or_no_args = true; + } else if (p_argcount == 1 && p_arg[0]->get_type() == Variant::PACKED_BYTE_ARRAY) { + byte_only_or_no_args = true; + // Special optimization when only the byte vector is sent. + const Vector<uint8_t> data = *p_arg[0]; + MAKE_ROOM(ofs + data.size()); + memcpy(&(packet_cache.write[ofs]), data.ptr(), sizeof(uint8_t) * data.size()); + ofs += data.size(); + } else { + // Arguments + MAKE_ROOM(ofs + 1); + packet_cache.write[ofs] = p_argcount; + ofs += 1; + for (int i = 0; i < p_argcount; i++) { + int len(0); + Error err = multiplayer->encode_and_compress_variant(*p_arg[i], nullptr, len); + ERR_FAIL_COND_MSG(err != OK, "Unable to encode RPC argument. THIS IS LIKELY A BUG IN THE ENGINE!"); + MAKE_ROOM(ofs + len); + multiplayer->encode_and_compress_variant(*p_arg[i], &(packet_cache.write[ofs]), len); + ofs += len; + } + } + + ERR_FAIL_COND(command_type > 7); + ERR_FAIL_COND(node_id_compression > 3); + ERR_FAIL_COND(name_id_compression > 1); + + // We can now set the meta + packet_cache.write[0] = command_type + (node_id_compression << NODE_ID_COMPRESSION_SHIFT) + (name_id_compression << NAME_ID_COMPRESSION_SHIFT) + (byte_only_or_no_args ? BYTE_ONLY_OR_NO_ARGS_FLAG : 0); + +#ifdef DEBUG_ENABLED + multiplayer->profile_bandwidth("out", ofs); +#endif + + // Take chance and set transfer mode, since all send methods will use it. + peer->set_transfer_channel(p_config.channel); + peer->set_transfer_mode(p_config.transfer_mode); + + if (has_all_peers) { + // They all have verified paths, so send fast. + peer->set_target_peer(p_to); // To all of you. + peer->put_packet(packet_cache.ptr(), ofs); // A message with love. + } else { + // Unreachable because the node ID is never compressed if the peers doesn't know it. + CRASH_COND(node_id_compression != NETWORK_NODE_ID_COMPRESSION_32); + + // Not all verified path, so send one by one. + + // Append path at the end, since we will need it for some packets. + CharString pname = String(from_path).utf8(); + int path_len = encode_cstring(pname.get_data(), nullptr); + MAKE_ROOM(ofs + path_len); + encode_cstring(pname.get_data(), &(packet_cache.write[ofs])); + + for (const int &P : multiplayer->get_connected_peers()) { + if (p_to < 0 && P == -p_to) { + continue; // Continue, excluded. + } + + if (p_to > 0 && P != p_to) { + continue; // Continue, not for this peer. + } + + bool confirmed = multiplayer->is_cache_confirmed(from_path, P); + + peer->set_target_peer(P); // To this one specifically. + + if (confirmed) { + // This one confirmed path, so use id. + encode_uint32(psc_id, &(packet_cache.write[1])); + peer->put_packet(packet_cache.ptr(), ofs); + } else { + // This one did not confirm path yet, so use entire path (sorry!). + encode_uint32(0x80000000 | ofs, &(packet_cache.write[1])); // Offset to path and flag. + peer->put_packet(packet_cache.ptr(), ofs + path_len); + } + } + } +} + +void RPCManager::rpcp(Node *p_node, int p_peer_id, const StringName &p_method, const Variant **p_arg, int p_argcount) { + Ref<MultiplayerPeer> peer = multiplayer->get_multiplayer_peer(); + ERR_FAIL_COND_MSG(!peer.is_valid(), "Trying to call an RPC while no multiplayer peer is active."); + ERR_FAIL_COND_MSG(!p_node->is_inside_tree(), "Trying to call an RPC on a node which is not inside SceneTree."); + ERR_FAIL_COND_MSG(peer->get_connection_status() != MultiplayerPeer::CONNECTION_CONNECTED, "Trying to call an RPC via a multiplayer peer which is not connected."); + + int node_id = peer->get_unique_id(); + bool call_local_native = false; + bool call_local_script = false; + uint16_t rpc_id = UINT16_MAX; + const Multiplayer::RPCConfig config = _get_rpc_config(p_node, p_method, rpc_id); + ERR_FAIL_COND_MSG(config.name == StringName(), + vformat("Unable to get the RPC configuration for the function \"%s\" at path: \"%s\". This happens when the method is not marked for RPCs.", p_method, p_node->get_path())); + if (p_peer_id == 0 || p_peer_id == node_id || (p_peer_id < 0 && p_peer_id != -node_id)) { + if (rpc_id & (1 << 15)) { + call_local_native = config.sync; + } else { + call_local_script = config.sync; + } + } + + if (p_peer_id != node_id) { +#ifdef DEBUG_ENABLED + _profile_node_data("out_rpc", p_node->get_instance_id()); +#endif + + _send_rpc(p_node, p_peer_id, rpc_id, config, p_method, p_arg, p_argcount); + } + + if (call_local_native) { + Callable::CallError ce; + + multiplayer->set_remote_sender_override(peer->get_unique_id()); + p_node->call(p_method, p_arg, p_argcount, ce); + multiplayer->set_remote_sender_override(0); + + if (ce.error != Callable::CallError::CALL_OK) { + String error = Variant::get_call_error_text(p_node, p_method, p_arg, p_argcount, ce); + error = "rpc() aborted in local call: - " + error + "."; + ERR_PRINT(error); + return; + } + } + + if (call_local_script) { + Callable::CallError ce; + ce.error = Callable::CallError::CALL_OK; + + multiplayer->set_remote_sender_override(peer->get_unique_id()); + p_node->get_script_instance()->call(p_method, p_arg, p_argcount, ce); + multiplayer->set_remote_sender_override(0); + + if (ce.error != Callable::CallError::CALL_OK) { + String error = Variant::get_call_error_text(p_node, p_method, p_arg, p_argcount, ce); + error = "rpc() aborted in script local call: - " + error + "."; + ERR_PRINT(error); + return; + } + } + + ERR_FAIL_COND_MSG(p_peer_id == node_id && !config.sync, "RPC '" + p_method + "' on yourself is not allowed by selected mode."); +} diff --git a/core/multiplayer/rpc_manager.h b/core/multiplayer/rpc_manager.h new file mode 100644 index 0000000000..7b99dee226 --- /dev/null +++ b/core/multiplayer/rpc_manager.h @@ -0,0 +1,89 @@ +/*************************************************************************/ +/* rpc_manager.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef MULTIPLAYER_RPC_H +#define MULTIPLAYER_RPC_H + +#include "core/multiplayer/multiplayer.h" +#include "core/multiplayer/multiplayer_api.h" +#include "core/object/ref_counted.h" + +class RPCManager : public RefCounted { + GDCLASS(RPCManager, RefCounted); + +private: + enum NetworkNodeIdCompression { + NETWORK_NODE_ID_COMPRESSION_8 = 0, + NETWORK_NODE_ID_COMPRESSION_16, + NETWORK_NODE_ID_COMPRESSION_32, + }; + + enum NetworkNameIdCompression { + NETWORK_NAME_ID_COMPRESSION_8 = 0, + NETWORK_NAME_ID_COMPRESSION_16, + }; + + // The RPC meta is composed by a single byte that contains (starting from the least significant bit): + // - `NetworkCommands` in the first four bits. + // - `NetworkNodeIdCompression` in the next 2 bits. + // - `NetworkNameIdCompression` in the next 1 bit. + // - `byte_only_or_no_args` in the next 1 bit. + enum { + NODE_ID_COMPRESSION_SHIFT = MultiplayerAPI::CMD_FLAG_0_SHIFT, // 2 bits for this. + NAME_ID_COMPRESSION_SHIFT = MultiplayerAPI::CMD_FLAG_2_SHIFT, + BYTE_ONLY_OR_NO_ARGS_SHIFT = MultiplayerAPI::CMD_FLAG_3_SHIFT, + }; + + enum { + NODE_ID_COMPRESSION_FLAG = (1 << NODE_ID_COMPRESSION_SHIFT) | (1 << (NODE_ID_COMPRESSION_SHIFT + 1)), // 2 bits for this. + NAME_ID_COMPRESSION_FLAG = (1 << NAME_ID_COMPRESSION_SHIFT), + BYTE_ONLY_OR_NO_ARGS_FLAG = (1 << BYTE_ONLY_OR_NO_ARGS_SHIFT), + }; + + MultiplayerAPI *multiplayer = nullptr; + Vector<uint8_t> packet_cache; + +protected: + _FORCE_INLINE_ void _profile_node_data(const String &p_what, ObjectID p_id); + void _process_rpc(Node *p_node, const uint16_t p_rpc_method_id, int p_from, const uint8_t *p_packet, int p_packet_len, int p_offset); + + void _send_rpc(Node *p_from, int p_to, uint16_t p_rpc_id, const Multiplayer::RPCConfig &p_config, const StringName &p_name, const Variant **p_arg, int p_argcount); + Node *_process_get_node(int p_from, const uint8_t *p_packet, uint32_t p_node_target, int p_packet_len); + +public: + // Called by Node.rpc + void rpcp(Node *p_node, int p_peer_id, const StringName &p_method, const Variant **p_arg, int p_argcount); + void process_rpc(int p_from, const uint8_t *p_packet, int p_packet_len); + + String get_rpc_md5(const Node *p_node); + RPCManager(MultiplayerAPI *p_multiplayer) { multiplayer = p_multiplayer; } +}; + +#endif // MULTIPLAYER_RPC_H diff --git a/core/object/class_db.cpp b/core/object/class_db.cpp index e268a8d292..8e92340c1e 100644 --- a/core/object/class_db.cpp +++ b/core/object/class_db.cpp @@ -1028,6 +1028,18 @@ void ClassDB::add_property_subgroup(const StringName &p_class, const String &p_n type->property_list.push_back(PropertyInfo(Variant::NIL, p_name, PROPERTY_HINT_NONE, p_prefix, PROPERTY_USAGE_SUBGROUP)); } +void ClassDB::add_property_array_count(const StringName &p_class, const String &p_label, const StringName &p_count_property, const StringName &p_count_setter, const StringName &p_count_getter, const String &p_array_element_prefix, uint32_t p_count_usage) { + add_property(p_class, PropertyInfo(Variant::INT, p_count_property, PROPERTY_HINT_NONE, "", p_count_usage | PROPERTY_USAGE_ARRAY, vformat("%s,%s", p_label, p_array_element_prefix)), p_count_setter, p_count_getter); +} + +void ClassDB::add_property_array(const StringName &p_class, const StringName &p_path, const String &p_array_element_prefix) { + OBJTYPE_WLOCK; + ClassInfo *type = classes.getptr(p_class); + ERR_FAIL_COND(!type); + + type->property_list.push_back(PropertyInfo(Variant::NIL, p_path, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_ARRAY, p_array_element_prefix)); +} + // NOTE: For implementation simplicity reasons, this method doesn't allow setters to have optional arguments at the end. void ClassDB::add_property(const StringName &p_class, const PropertyInfo &p_pinfo, const StringName &p_setter, const StringName &p_getter, int p_index) { lock.read_lock(); diff --git a/core/object/class_db.h b/core/object/class_db.h index 166aa35469..e89c7fffd7 100644 --- a/core/object/class_db.h +++ b/core/object/class_db.h @@ -353,6 +353,8 @@ public: static void add_property_group(const StringName &p_class, const String &p_name, const String &p_prefix = ""); static void add_property_subgroup(const StringName &p_class, const String &p_name, const String &p_prefix = ""); + static void add_property_array_count(const StringName &p_class, const String &p_label, const StringName &p_count_property, const StringName &p_count_setter, const StringName &p_count_getter, const String &p_array_element_prefix, uint32_t p_count_usage = PROPERTY_USAGE_EDITOR); + static void add_property_array(const StringName &p_class, const StringName &p_path, const String &p_array_element_prefix); static void add_property(const StringName &p_class, const PropertyInfo &p_pinfo, const StringName &p_setter, const StringName &p_getter, int p_index = -1); static void set_property_default_value(const StringName &p_class, const StringName &p_name, const Variant &p_default); static void add_linked_property(const StringName &p_class, const String &p_property, const String &p_linked_property); diff --git a/core/object/object.h b/core/object/object.h index 102776a589..a44d921bff 100644 --- a/core/object/object.h +++ b/core/object/object.h @@ -132,6 +132,8 @@ enum PropertyUsageFlags { PROPERTY_USAGE_DEFERRED_SET_RESOURCE = 1 << 26, // when loading, the resource for this property can be set at the end of loading PROPERTY_USAGE_EDITOR_INSTANTIATE_OBJECT = 1 << 27, // For Object properties, instantiate them when creating in editor. PROPERTY_USAGE_EDITOR_BASIC_SETTING = 1 << 28, //for project or editor settings, show when basic settings are selected + PROPERTY_USAGE_READ_ONLY = 1 << 29, // Mark a property as read-only in the inspector. + PROPERTY_USAGE_ARRAY = 1 << 30, // Used in the inspector to group properties as elements of an array. PROPERTY_USAGE_DEFAULT = PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_NETWORK, PROPERTY_USAGE_DEFAULT_INTL = PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_NETWORK | PROPERTY_USAGE_INTERNATIONALIZED, @@ -146,6 +148,10 @@ enum PropertyUsageFlags { #define ADD_SUBGROUP(m_name, m_prefix) ::ClassDB::add_property_subgroup(get_class_static(), m_name, m_prefix) #define ADD_LINKED_PROPERTY(m_property, m_linked_property) ::ClassDB::add_linked_property(get_class_static(), m_property, m_linked_property) +#define ADD_ARRAY_COUNT(m_label, m_count_property, m_count_property_setter, m_count_property_getter, m_prefix) ClassDB::add_property_array_count(get_class_static(), m_label, m_count_property, _scs_create(m_count_property_setter), _scs_create(m_count_property_getter), m_prefix) +#define ADD_ARRAY_COUNT_WITH_USAGE_FLAGS(m_label, m_count_property, m_count_property_setter, m_count_property_getter, m_prefix, m_property_usage_flags) ClassDB::add_property_array_count(get_class_static(), m_label, m_count_property, _scs_create(m_count_property_setter), _scs_create(m_count_property_getter), m_prefix, m_property_usage_flags) +#define ADD_ARRAY(m_array_path, m_prefix) ClassDB::add_property_array(get_class_static(), m_array_path, m_prefix) + struct PropertyInfo { Variant::Type type = Variant::NIL; String name; diff --git a/core/object/script_language.h b/core/object/script_language.h index 385bf79c1a..8d76cbf479 100644 --- a/core/object/script_language.h +++ b/core/object/script_language.h @@ -32,8 +32,8 @@ #define SCRIPT_LANGUAGE_H #include "core/doc_data.h" -#include "core/io/multiplayer_api.h" #include "core/io/resource.h" +#include "core/multiplayer/multiplayer.h" #include "core/templates/map.h" #include "core/templates/pair.h" @@ -159,7 +159,7 @@ public: virtual bool is_placeholder_fallback_enabled() const { return false; } - virtual const Vector<MultiplayerAPI::RPCConfig> get_rpc_methods() const = 0; + virtual const Vector<Multiplayer::RPCConfig> get_rpc_methods() const = 0; Script() {} }; @@ -200,7 +200,7 @@ public: virtual void property_set_fallback(const StringName &p_name, const Variant &p_value, bool *r_valid); virtual Variant property_get_fallback(const StringName &p_name, bool *r_valid); - virtual const Vector<MultiplayerAPI::RPCConfig> get_rpc_methods() const = 0; + virtual const Vector<Multiplayer::RPCConfig> get_rpc_methods() const = 0; virtual ScriptLanguage *get_language() = 0; virtual ~ScriptInstance(); @@ -419,7 +419,7 @@ public: virtual void property_set_fallback(const StringName &p_name, const Variant &p_value, bool *r_valid = nullptr); virtual Variant property_get_fallback(const StringName &p_name, bool *r_valid = nullptr); - virtual const Vector<MultiplayerAPI::RPCConfig> get_rpc_methods() const { return Vector<MultiplayerAPI::RPCConfig>(); } + virtual const Vector<Multiplayer::RPCConfig> get_rpc_methods() const { return Vector<Multiplayer::RPCConfig>(); } PlaceHolderScriptInstance(ScriptLanguage *p_language, Ref<Script> p_script, Object *p_owner); ~PlaceHolderScriptInstance(); diff --git a/core/os/os.cpp b/core/os/os.cpp index 63390919f4..89ba73b35e 100644 --- a/core/os/os.cpp +++ b/core/os/os.cpp @@ -357,9 +357,17 @@ void OS::set_has_server_feature_callback(HasServerFeatureCallback p_callback) { } bool OS::has_feature(const String &p_feature) { - if (p_feature == get_name()) { + // Feature tags are always lowercase for consistency. + if (p_feature == get_name().to_lower()) { return true; } + + // Catch-all `linuxbsd` feature tag that matches on both Linux and BSD. + // This is the one exposed in the project settings dialog. + if (p_feature == "linuxbsd" && (get_name() == "Linux" || get_name() == "FreeBSD" || get_name() == "NetBSD" || get_name() == "OpenBSD" || get_name() == "BSD")) { + return true; + } + #ifdef DEBUG_ENABLED if (p_feature == "debug") { return true; diff --git a/core/register_core_types.cpp b/core/register_core_types.cpp index 6c7d9cbd89..3a037f9dd1 100644 --- a/core/register_core_types.cpp +++ b/core/register_core_types.cpp @@ -48,9 +48,6 @@ #include "core/io/image_loader.h" #include "core/io/json.h" #include "core/io/marshalls.h" -#include "core/io/multiplayer_api.h" -#include "core/io/multiplayer_peer.h" -#include "core/io/multiplayer_replicator.h" #include "core/io/packed_data_container.h" #include "core/io/packet_peer.h" #include "core/io/packet_peer_dtls.h" @@ -70,6 +67,9 @@ #include "core/math/geometry_3d.h" #include "core/math/random_number_generator.h" #include "core/math/triangle_mesh.h" +#include "core/multiplayer/multiplayer_api.h" +#include "core/multiplayer/multiplayer_peer.h" +#include "core/multiplayer/multiplayer_replicator.h" #include "core/object/class_db.h" #include "core/object/undo_redo.h" #include "core/os/main_loop.h" diff --git a/core/string/ustring.cpp b/core/string/ustring.cpp index a3b5356b1d..8416ff929e 100644 --- a/core/string/ustring.cpp +++ b/core/string/ustring.cpp @@ -1653,13 +1653,13 @@ String String::num_scientific(double p_num) { #if defined(__GNUC__) || defined(_MSC_VER) -#if (defined(__MINGW32__) || (defined(_MSC_VER) && _MSC_VER < 1900)) && defined(_TWO_DIGIT_EXPONENT) && !defined(_UCRT) - // MinGW and old MSC require _set_output_format() to conform to C99 output for printf +#if defined(__MINGW32__) && defined(_TWO_DIGIT_EXPONENT) && !defined(_UCRT) + // MinGW requires _set_output_format() to conform to C99 output for printf unsigned int old_exponent_format = _set_output_format(_TWO_DIGIT_EXPONENT); #endif snprintf(buf, 256, "%lg", p_num); -#if (defined(__MINGW32__) || (defined(_MSC_VER) && _MSC_VER < 1900)) && defined(_TWO_DIGIT_EXPONENT) && !defined(_UCRT) +#if defined(__MINGW32__) && defined(_TWO_DIGIT_EXPONENT) && !defined(_UCRT) _set_output_format(old_exponent_format); #endif @@ -4329,23 +4329,39 @@ bool String::is_relative_path() const { } String String::get_base_dir() const { - int basepos = find(":/"); - if (basepos == -1) { - basepos = find(":\\"); + int end = 0; + + // url scheme style base + int basepos = find("://"); + if (basepos != -1) { + end = basepos + 3; } + + // windows top level directory base + if (end == 0) { + basepos = find(":/"); + if (basepos == -1) { + basepos = find(":\\"); + } + if (basepos != -1) { + end = basepos + 2; + } + } + + // unix root directory base + if (end == 0) { + if (begins_with("/")) { + end = 1; + } + } + String rs; String base; - if (basepos != -1) { - int end = basepos + 3; + if (end != 0) { rs = substr(end, length()); base = substr(0, end); } else { - if (begins_with("/")) { - rs = substr(1, length()); - base = "/"; - } else { - rs = *this; - } + rs = *this; } int sep = MAX(rs.rfind("/"), rs.rfind("\\")); diff --git a/core/string/ustring.h b/core/string/ustring.h index 6a4b40da60..24da6b82af 100644 --- a/core/string/ustring.h +++ b/core/string/ustring.h @@ -523,10 +523,10 @@ String DTRN(const String &p_text, const String &p_text_plural, int p_n, const St #define TTRGET(m_value) TTR(m_value) #else -#define TTR(m_value) (String()) -#define TTRN(m_value) (String()) -#define DTR(m_value) (String()) -#define DTRN(m_value) (String()) +#define TTR(m_value) String() +#define TTRN(m_value) String() +#define DTR(m_value) String() +#define DTRN(m_value) String() #define TTRC(m_value) (m_value) #define TTRGET(m_value) (m_value) #endif diff --git a/core/templates/vector.h b/core/templates/vector.h index 033345d04c..2600604eb7 100644 --- a/core/templates/vector.h +++ b/core/templates/vector.h @@ -92,7 +92,7 @@ public: void append_array(Vector<T> p_other); - bool has(const T &p_val) { + bool has(const T &p_val) const { return find(p_val, 0) != -1; } diff --git a/core/variant/callable_bind.cpp b/core/variant/callable_bind.cpp index 10446a5ec1..56eda6e703 100644 --- a/core/variant/callable_bind.cpp +++ b/core/variant/callable_bind.cpp @@ -169,7 +169,8 @@ CallableCustomUnbind::~CallableCustomUnbind() { } Callable callable_bind(const Callable &p_callable, const Variant &p_arg1) { - return p_callable.bind((const Variant **)&p_arg1, 1); + const Variant *args[1] = { &p_arg1 }; + return p_callable.bind(args, 1); } Callable callable_bind(const Callable &p_callable, const Variant &p_arg1, const Variant &p_arg2) { diff --git a/doc/classes/@GlobalScope.xml b/doc/classes/@GlobalScope.xml index 616d88c7f3..755902c709 100644 --- a/doc/classes/@GlobalScope.xml +++ b/doc/classes/@GlobalScope.xml @@ -2425,6 +2425,8 @@ </constant> <constant name="PROPERTY_USAGE_EDITOR_BASIC_SETTING" value="268435456" enum="PropertyUsageFlags"> </constant> + <constant name="PROPERTY_USAGE_ARRAY" value="1073741824" enum="PropertyUsageFlags"> + </constant> <constant name="PROPERTY_USAGE_DEFAULT" value="7" enum="PropertyUsageFlags"> Default usage (storage, editor and network). </constant> @@ -2463,6 +2465,24 @@ <constant name="METHOD_FLAGS_DEFAULT" value="1" enum="MethodFlags"> Default method flags. </constant> + <constant name="RPC_MODE_DISABLED" value="0" enum="RPCMode"> + Used with [method Node.rpc_config] to disable a method or property for all RPC calls, making it unavailable. Default for all methods. + </constant> + <constant name="RPC_MODE_ANY" value="1" enum="RPCMode"> + Used with [method Node.rpc_config] to set a method to be callable remotely by any peer. Analogous to the [code]@rpc(any)[/code] annotation. Calls are accepted from all remote peers, no matter if they are node's authority or not. + </constant> + <constant name="RPC_MODE_AUTH" value="2" enum="RPCMode"> + Used with [method Node.rpc_config] to set a method to be callable remotely only by the current multiplayer authority (which is the server by default). Analogous to the [code]@rpc(auth)[/code] annotation. See [method Node.set_multiplayer_authority]. + </constant> + <constant name="TRANSFER_MODE_UNRELIABLE" value="0" enum="TransferMode"> + Packets are not acknowledged, no resend attempts are made for lost packets. Packets may arrive in any order. Potentially faster than [constant TRANSFER_MODE_ORDERED]. Use for non-critical data, and always consider whether the order matters. + </constant> + <constant name="TRANSFER_MODE_ORDERED" value="1" enum="TransferMode"> + Packets are not acknowledged, no resend attempts are made for lost packets. Packets are received in the order they were sent in. Potentially faster than [constant TRANSFER_MODE_RELIABLE]. Use for non-critical data or data that would be outdated if received late due to resend attempt(s) anyway, for example movement and positional data. + </constant> + <constant name="TRANSFER_MODE_RELIABLE" value="2" enum="TransferMode"> + Packets must be received and resend attempts should be made until the packets are acknowledged. Packets must be received in the order they were sent in. Most reliable transfer mode, but potentially the slowest due to the overhead. Use for critical data that must be transmitted and arrive in order, for example an ability being triggered or a chat message. Consider carefully if the information really is critical, and use sparingly. + </constant> <constant name="TYPE_NIL" value="0" enum="Variant.Type"> Variable is [code]null[/code]. </constant> diff --git a/doc/classes/AnimatableBody2D.xml b/doc/classes/AnimatableBody2D.xml new file mode 100644 index 0000000000..731c702549 --- /dev/null +++ b/doc/classes/AnimatableBody2D.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="AnimatableBody2D" inherits="StaticBody2D" version="4.0"> + <brief_description> + Physics body for 2D physics which moves only by script or animation. Useful for moving platforms and doors. + </brief_description> + <description> + Animatable body for 2D physics. + An animatable body can't be moved by external forces or contacts, but can be moved by script or animation to affect other bodies in its path. It is ideal for implementing moving objects in the environment, such as moving platforms or doors. + When the body is moved manually, either from code or from an [AnimationPlayer] (with [member AnimationPlayer.playback_process_mode] set to [code]physics[/code]), the physics will automatically compute an estimate of their linear and angular velocity. This makes them very useful for moving platforms or other AnimationPlayer-controlled objects (like a door, a bridge that opens, etc). + </description> + <tutorials> + </tutorials> + <methods> + </methods> + <members> + <member name="sync_to_physics" type="bool" setter="set_sync_to_physics" getter="is_sync_to_physics_enabled" default="false"> + If [code]true[/code], the body's movement will be synchronized to the physics frame. This is useful when animating movement via [AnimationPlayer], for example on moving platforms. Do [b]not[/b] use together with [method PhysicsBody2D.move_and_collide]. + </member> + </members> + <constants> + </constants> +</class> diff --git a/doc/classes/AnimatableBody3D.xml b/doc/classes/AnimatableBody3D.xml new file mode 100644 index 0000000000..8192f26057 --- /dev/null +++ b/doc/classes/AnimatableBody3D.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="AnimatableBody3D" inherits="StaticBody3D" version="4.0"> + <brief_description> + Physics body for 3D physics which moves only by script or animation. Useful for moving platforms and doors. + </brief_description> + <description> + Animatable body for 3D physics. + An animatable body can't be moved by external forces or contacts, but can be moved by script or animation to affect other bodies in its path. It is ideal for implementing moving objects in the environment, such as moving platforms or doors. + When the body is moved manually, either from code or from an [AnimationPlayer] (with [member AnimationPlayer.playback_process_mode] set to [code]physics[/code]), the physics will automatically compute an estimate of their linear and angular velocity. This makes them very useful for moving platforms or other AnimationPlayer-controlled objects (like a door, a bridge that opens, etc). + </description> + <tutorials> + <link title="3D Physics Tests Demo">https://godotengine.org/asset-library/asset/675</link> + <link title="Third Person Shooter Demo">https://godotengine.org/asset-library/asset/678</link> + <link title="3D Voxel Demo">https://godotengine.org/asset-library/asset/676</link> + </tutorials> + <methods> + </methods> + <members> + <member name="sync_to_physics" type="bool" setter="set_sync_to_physics" getter="is_sync_to_physics_enabled" default="false"> + If [code]true[/code], the body's movement will be synchronized to the physics frame. This is useful when animating movement via [AnimationPlayer], for example on moving platforms. Do [b]not[/b] use together with [method PhysicsBody3D.move_and_collide]. + </member> + </members> + <constants> + </constants> +</class> diff --git a/doc/classes/Array.xml b/doc/classes/Array.xml index 396490c8eb..02c6b18d55 100644 --- a/doc/classes/Array.xml +++ b/doc/classes/Array.xml @@ -495,7 +495,7 @@ <argument index="2" name="step" type="int" default="1" /> <argument index="3" name="deep" type="bool" default="false" /> <description> - Duplicates the subset described in the function and returns it in an array, deeply copying the array if [code]deep[/code] is [code]true[/code]. Lower and upper index are inclusive, with the [code]step[/code] describing the change between indices while slicing. + Duplicates the subset described in the function and returns it in an array, deeply copying the array if [code]deep[/code] is [code]true[/code]. Lower and upper index are inclusive, with the [code]step[/code] describing the change between indices while slicing. If [code]end[/code] is an invalid value, the end of the array is used. </description> </method> <method name="sort"> diff --git a/doc/classes/AudioStream.xml b/doc/classes/AudioStream.xml index a954a06117..32e51603ee 100644 --- a/doc/classes/AudioStream.xml +++ b/doc/classes/AudioStream.xml @@ -28,12 +28,23 @@ <description> </description> </method> + <method name="_is_monophonic" qualifiers="virtual const"> + <return type="bool" /> + <description> + </description> + </method> <method name="get_length" qualifiers="const"> <return type="float" /> <description> Returns the length of the audio stream in seconds. </description> </method> + <method name="is_monophonic" qualifiers="const"> + <return type="bool" /> + <description> + Returns true if this audio stream only supports monophonic playback, or false if the audio stream supports polyphony. + </description> + </method> </methods> <constants> </constants> diff --git a/doc/classes/AudioStreamPlayer.xml b/doc/classes/AudioStreamPlayer.xml index a6c437f875..b692ae858e 100644 --- a/doc/classes/AudioStreamPlayer.xml +++ b/doc/classes/AudioStreamPlayer.xml @@ -56,6 +56,9 @@ <member name="bus" type="StringName" setter="set_bus" getter="get_bus" default="&"Master""> Bus on which this audio is playing. </member> + <member name="max_polyphony" type="int" setter="set_max_polyphony" getter="get_max_polyphony" default="1"> + The maximum number of sounds this node can play at the same time. Playing additional sounds after this value is reached will cut off the oldest sounds. + </member> <member name="mix_target" type="int" setter="set_mix_target" getter="get_mix_target" enum="AudioStreamPlayer.MixTarget" default="0"> If the audio configuration has more than two speakers, this sets the target channels. See [enum MixTarget] constants. </member> diff --git a/doc/classes/AudioStreamPlayer2D.xml b/doc/classes/AudioStreamPlayer2D.xml index c40c223091..e36a428499 100644 --- a/doc/classes/AudioStreamPlayer2D.xml +++ b/doc/classes/AudioStreamPlayer2D.xml @@ -61,6 +61,9 @@ <member name="max_distance" type="float" setter="set_max_distance" getter="get_max_distance" default="2000.0"> Maximum distance from which audio is still hearable. </member> + <member name="max_polyphony" type="int" setter="set_max_polyphony" getter="get_max_polyphony" default="1"> + The maximum number of sounds this node can play at the same time. Playing additional sounds after this value is reached will cut off the oldest sounds. + </member> <member name="pitch_scale" type="float" setter="set_pitch_scale" getter="get_pitch_scale" default="1.0"> The pitch and the tempo of the audio, as a multiplier of the audio sample's sample rate. </member> diff --git a/doc/classes/AudioStreamPlayer3D.xml b/doc/classes/AudioStreamPlayer3D.xml index 584f03399c..fa2fa5a8e3 100644 --- a/doc/classes/AudioStreamPlayer3D.xml +++ b/doc/classes/AudioStreamPlayer3D.xml @@ -83,6 +83,9 @@ <member name="max_distance" type="float" setter="set_max_distance" getter="get_max_distance" default="0.0"> Sets the distance from which the [member out_of_range_mode] takes effect. Has no effect if set to 0. </member> + <member name="max_polyphony" type="int" setter="set_max_polyphony" getter="get_max_polyphony" default="1"> + The maximum number of sounds this node can play at the same time. Playing additional sounds after this value is reached will cut off the oldest sounds. + </member> <member name="out_of_range_mode" type="int" setter="set_out_of_range_mode" getter="get_out_of_range_mode" enum="AudioStreamPlayer3D.OutOfRangeMode" default="0"> Decides if audio should pause when source is outside of [member max_distance] range. </member> diff --git a/doc/classes/Camera2D.xml b/doc/classes/Camera2D.xml index d0ff66ae06..a3a891cdcb 100644 --- a/doc/classes/Camera2D.xml +++ b/doc/classes/Camera2D.xml @@ -5,6 +5,7 @@ </brief_description> <description> Camera node for 2D scenes. It forces the screen (current layer) to scroll following this node. This makes it easier (and faster) to program scrollable scenes than manually changing the position of [CanvasItem]-based nodes. + Cameras register themselves in the nearest [Viewport] node (when ascending the tree). Only one camera can be active per viewport. If no viewport is available ascending the tree, the camera will register in the global viewport. This node is intended to be a simple helper to get things going quickly, but more functionality may be desired to change how the camera works. To make your own custom camera node, inherit it from [Node2D] and change the transform of the canvas by setting [member Viewport.canvas_transform] in [Viewport] (you can obtain the current [Viewport] by using [method Node.get_viewport]). Note that the [Camera2D] node's [code]position[/code] doesn't represent the actual position of the screen, which may differ due to applied smoothing or limits. You can use [method get_camera_screen_center] to get the real position. </description> @@ -81,7 +82,7 @@ The Camera2D's anchor point. See [enum AnchorMode] constants. </member> <member name="current" type="bool" setter="set_current" getter="is_current" default="false"> - If [code]true[/code], the camera is the active camera for the current scene. Only one camera can be current, so setting a different camera [code]current[/code] will disable this one. + If [code]true[/code], the camera acts as the active camera for its [Viewport] ancestor. Only one camera can be current in a given viewport, so setting a different camera in the same viewport [code]current[/code] will disable whatever camera was already active in that viewport. </member> <member name="custom_viewport" type="Node" setter="set_custom_viewport" getter="get_custom_viewport"> The custom [Viewport] node attached to the [Camera2D]. If [code]null[/code] or not a [Viewport], uses the default viewport instead. diff --git a/doc/classes/CharacterBody2D.xml b/doc/classes/CharacterBody2D.xml index 71e6eeab5a..f98c22a1e9 100644 --- a/doc/classes/CharacterBody2D.xml +++ b/doc/classes/CharacterBody2D.xml @@ -1,12 +1,12 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="CharacterBody2D" inherits="PhysicsBody2D" version="4.0"> <brief_description> - Character body 2D node. + Specialized 2D physics body node for characters moved by script. </brief_description> <description> - Character bodies are special types of bodies that are meant to be user-controlled. They are not affected by physics at all; to other types of bodies, such as a rigid body, these are the same as a static body. However, they have two main uses: - [b]Kinematic characters:[/b] Character bodies have an API for moving objects with walls and slopes detection ([method move_and_slide] method), in addition to collision detection (also done with [method PhysicsBody3D.move_and_collide]). This makes them really useful to implement characters that move in specific ways and collide with the world, but don't require advanced physics. - [b]Kinematic motion:[/b] Character bodies can also be used for kinematic motion (same functionality as [member StaticBody3D.kinematic_motion] when enabled), which allows them to be moved by code and push other bodies on their path. + Character bodies are special types of bodies that are meant to be user-controlled. They are not affected by physics at all; to other types of bodies, such as a rigid body, these are the same as a [AnimatableBody2D]. However, they have two main uses: + [b]Kinematic characters:[/b] Character bodies have an API for moving objects with walls and slopes detection ([method move_and_slide] method), in addition to collision detection (also done with [method PhysicsBody2D.move_and_collide]). This makes them really useful to implement characters that move in specific ways and collide with the world, but don't require advanced physics. + [b]Kinematic motion:[/b] Character bodies can also be used for kinematic motion (same functionality as [AnimatableBody2D]), which allows them to be moved by code and push other bodies on their path. </description> <tutorials> <link title="Kinematic character (2D)">https://docs.godotengine.org/en/latest/tutorials/physics/kinematic_character_2d.html</link> diff --git a/doc/classes/CharacterBody3D.xml b/doc/classes/CharacterBody3D.xml index 85135d5509..81ffbe01c1 100644 --- a/doc/classes/CharacterBody3D.xml +++ b/doc/classes/CharacterBody3D.xml @@ -1,12 +1,12 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="CharacterBody3D" inherits="PhysicsBody3D" version="4.0"> <brief_description> - Character body 3D node. + Specialized 3D physics body node for characters moved by script. </brief_description> <description> - Character bodies are special types of bodies that are meant to be user-controlled. They are not affected by physics at all; to other types of bodies, such as a rigid body, these are the same as a static body. However, they have two main uses: + Character bodies are special types of bodies that are meant to be user-controlled. They are not affected by physics at all; to other types of bodies, such as a rigid body, these are the same as a [AnimatableBody3D]. However, they have two main uses: [b]Kinematic characters:[/b] Character bodies have an API for moving objects with walls and slopes detection ([method move_and_slide] method), in addition to collision detection (also done with [method PhysicsBody3D.move_and_collide]). This makes them really useful to implement characters that move in specific ways and collide with the world, but don't require advanced physics. - [b]Kinematic motion:[/b] Character bodies can also be used for kinematic motion (same functionality as [member StaticBody3D.kinematic_motion] when enabled), which allows them to be moved by code and push other bodies on their path. + [b]Kinematic motion:[/b] Character bodies can also be used for kinematic motion (same functionality as [AnimatableBody3D]), which allows them to be moved by code and push other bodies on their path. </description> <tutorials> <link title="Kinematic character (2D)">https://docs.godotengine.org/en/latest/tutorials/physics/kinematic_character_2d.html</link> diff --git a/doc/classes/ConfigFile.xml b/doc/classes/ConfigFile.xml index d6da4bc248..249e2a8f80 100644 --- a/doc/classes/ConfigFile.xml +++ b/doc/classes/ConfigFile.xml @@ -86,6 +86,7 @@ [/codeblocks] Keep in mind that section and property names can't contain spaces. Anything after a space will be ignored on save and on load. ConfigFiles can also contain manually written comment lines starting with a semicolon ([code];[/code]). Those lines will be ignored when parsing the file. Note that comments will be lost when saving the ConfigFile. This can still be useful for dedicated server configuration files, which are typically never overwritten without explicit user action. + [b]Note:[/b] The file extension given to a ConfigFile does not have any impact on its formatting or behavior. By convention, the [code].cfg[/code] extension is used here, but any other extension such as [code].ini[/code] is also valid. Since neither [code].cfg[/code] nor [code].ini[/code] are standardized, Godot's ConfigFile formatting may differ from files written by other programs. </description> <tutorials> </tutorials> diff --git a/doc/classes/Control.xml b/doc/classes/Control.xml index 5392189f6a..6602764cd4 100644 --- a/doc/classes/Control.xml +++ b/doc/classes/Control.xml @@ -921,7 +921,8 @@ Anchors the top edge of the node to the origin, the center or the end of its parent control. It changes how the top offset updates when the node moves or changes size. You can use one of the [enum Anchor] constants for convenience. </member> <member name="auto_translate" type="bool" setter="set_auto_translate" getter="is_auto_translating" default="true"> - Toggles if any text should automatically change to its translated version depending on the current locale. + Toggles if any text should automatically change to its translated version depending on the current locale. Note that this will not affect any internal nodes (e.g. the popup of a [MenuButton]). + Also decides if the node's strings should be parsed for POT generation. </member> <member name="focus_mode" type="int" setter="set_focus_mode" getter="get_focus_mode" enum="Control.FocusMode" default="0"> The focus access mode for the control (None, Click or All). Only one Control can be focused at the same time, and it will receive keyboard signals. diff --git a/doc/classes/EditorSceneImporterMesh.xml b/doc/classes/EditorSceneImporterMesh.xml index b0f233da2f..c0c53ff255 100644 --- a/doc/classes/EditorSceneImporterMesh.xml +++ b/doc/classes/EditorSceneImporterMesh.xml @@ -27,6 +27,7 @@ }" /> <argument index="4" name="material" type="Material" default="null" /> <argument index="5" name="name" type="String" default="""" /> + <argument index="6" name="flags" type="int" default="0" /> <description> Creates a new surface, analogous to [method ArrayMesh.add_surface_from_arrays]. Surfaces are created to be rendered using a [code]primitive[/code], which may be any of the types defined in [enum Mesh.PrimitiveType]. (As a note, when using indices, it is recommended to only use points, lines, or triangles.) [method Mesh.get_surface_count] will become the [code]surf_idx[/code] for this new surface. @@ -94,6 +95,13 @@ Returns the amount of surfaces that the mesh holds. </description> </method> + <method name="get_surface_format" qualifiers="const"> + <return type="int" /> + <argument index="0" name="surface_idx" type="int" /> + <description> + Returns the format of the surface that the mesh holds. + </description> + </method> <method name="get_surface_lod_count" qualifiers="const"> <return type="int" /> <argument index="0" name="surface_idx" type="int" /> diff --git a/doc/classes/Engine.xml b/doc/classes/Engine.xml index 8b399f64c9..36590093bd 100644 --- a/doc/classes/Engine.xml +++ b/doc/classes/Engine.xml @@ -37,7 +37,7 @@ <method name="get_frames_drawn"> <return type="int" /> <description> - Returns the total number of frames drawn. If the render loop is disabled with [code]--disable-render-loop[/code] via command line, this returns [code]0[/code]. See also [method get_process_frames]. + Returns the total number of frames drawn. On headless platforms, or if the render loop is disabled with [code]--disable-render-loop[/code] via command line, [method get_frames_drawn] always returns [code]0[/code]. See [method get_process_frames]. </description> </method> <method name="get_frames_per_second" qualifiers="const"> @@ -67,7 +67,13 @@ <method name="get_physics_frames" qualifiers="const"> <return type="int" /> <description> - Returns the total number of frames passed since engine initialization which is advanced on each [b]physics frame[/b]. + Returns the total number of frames passed since engine initialization which is advanced on each [b]physics frame[/b]. See also [method get_process_frames]. + [method get_physics_frames] can be used to run expensive logic less often without relying on a [Timer]: + [codeblock] + func _physics_process(_delta): + if Engine.get_physics_frames() % 2 == 0: + pass # Run expensive logic only once every 2 physics frames here. + [/codeblock] </description> </method> <method name="get_physics_interpolation_fraction" qualifiers="const"> @@ -79,7 +85,13 @@ <method name="get_process_frames" qualifiers="const"> <return type="int" /> <description> - Returns the total number of frames passed since engine initialization which is advanced on each [b]process frame[/b], regardless of whether the render loop is enabled. See also [method get_frames_drawn]. + Returns the total number of frames passed since engine initialization which is advanced on each [b]process frame[/b], regardless of whether the render loop is enabled. See also [method get_frames_drawn] and [method get_physics_frames]. + [method get_process_frames] can be used to run expensive logic less often without relying on a [Timer]: + [codeblock] + func _process(_delta): + if Engine.get_process_frames() % 2 == 0: + pass # Run expensive logic only once every 2 process (render) frames here. + [/codeblock] </description> </method> <method name="get_singleton" qualifiers="const"> diff --git a/doc/classes/HScrollBar.xml b/doc/classes/HScrollBar.xml index 3bdd739cdf..36ff070a37 100644 --- a/doc/classes/HScrollBar.xml +++ b/doc/classes/HScrollBar.xml @@ -19,6 +19,9 @@ <theme_item name="decrement_highlight" data_type="icon" type="Texture2D"> Displayed when the mouse cursor hovers over the decrement button. </theme_item> + <theme_item name="decrement_pressed" data_type="icon" type="Texture2D"> + Displayed when the decrement button is being pressed. + </theme_item> <theme_item name="grabber" data_type="style" type="StyleBox"> Used as texture for the grabber, the draggable element representing current scroll. </theme_item> @@ -34,6 +37,9 @@ <theme_item name="increment_highlight" data_type="icon" type="Texture2D"> Displayed when the mouse cursor hovers over the increment button. </theme_item> + <theme_item name="increment_pressed" data_type="icon" type="Texture2D"> + Displayed when the increment button is being pressed. + </theme_item> <theme_item name="scroll" data_type="style" type="StyleBox"> Used as background of this [ScrollBar]. </theme_item> diff --git a/doc/classes/HTTPClient.xml b/doc/classes/HTTPClient.xml index 861627b526..29aaf3c756 100644 --- a/doc/classes/HTTPClient.xml +++ b/doc/classes/HTTPClient.xml @@ -8,6 +8,7 @@ [b]Note:[/b] This client only needs to connect to a host once (see [method connect_to_host]) to send multiple requests. Because of this, methods that take URLs usually take just the part after the host instead of the full URL, as the client is already connected to a host. See [method request] for a full example and to get started. A [HTTPClient] should be reused between multiple requests or to connect to different hosts instead of creating one client per request. Supports SSL and SSL server certificate verification. HTTP status codes in the 2xx range indicate success, 3xx redirection (i.e. "try again, but over here"), 4xx something was wrong with the request, and 5xx something went wrong on the server's side. For more information on HTTP, see https://developer.mozilla.org/en-US/docs/Web/HTTP (or read RFC 2616 to get it straight from the source: https://tools.ietf.org/html/rfc2616). + [b]Note:[/b] It's recommended to use transport encryption (SSL/TLS) and to avoid sending sensitive information (such as login credentials) in HTTP GET URL parameters. Consider using HTTP POST requests or HTTP headers for such information instead. [b]Note:[/b] When performing HTTP requests from a project exported to HTML5, keep in mind the remote server may not allow requests from foreign origins due to [url=https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS]CORS[/url]. If you host the server in question, you should modify its backend to allow requests from foreign origins by adding the [code]Access-Control-Allow-Origin: *[/code] HTTP header. [b]Note:[/b] SSL/TLS support is currently limited to TLS 1.0, TLS 1.1, and TLS 1.2. Attempting to connect to a TLS 1.3-only server will return an error. [b]Warning:[/b] SSL/TLS certificate revocation and certificate pinning are currently not supported. Revoked certificates are accepted as long as they are otherwise valid. If this is a concern, you may want to use automatically managed certificates with a short validity period. diff --git a/doc/classes/HTTPRequest.xml b/doc/classes/HTTPRequest.xml index f45ddd0abb..00927b98c5 100644 --- a/doc/classes/HTTPRequest.xml +++ b/doc/classes/HTTPRequest.xml @@ -192,7 +192,8 @@ <description> Creates request on the underlying [HTTPClient]. If there is no configuration errors, it tries to connect using [method HTTPClient.connect_to_host] and passes parameters onto [method HTTPClient.request]. Returns [constant OK] if request is successfully created. (Does not imply that the server has responded), [constant ERR_UNCONFIGURED] if not in the tree, [constant ERR_BUSY] if still processing previous request, [constant ERR_INVALID_PARAMETER] if given string is not a valid URL format, or [constant ERR_CANT_CONNECT] if not using thread and the [HTTPClient] cannot connect to host. - [b]Note:[/b] The [code]request_data[/code] parameter is ignored if [code]method[/code] is [constant HTTPClient.METHOD_GET]. This is because GET methods can't contain request data. As a workaround, you can pass request data as a query string in the URL. See [method String.uri_encode] for an example. + [b]Note:[/b] When [code]method[/code] is [constant HTTPClient.METHOD_GET], the payload sent via [code]request_data[/code] might be ignored by the server or even cause the server to reject the request (check [url=https://datatracker.ietf.org/doc/html/rfc7231#section-4.3.1]RFC 7231 section 4.3.1[/url] for more details). As a workaround, you can send data as a query string in the URL (see [method String.uri_encode] for an example). + [b]Note:[/b] It's recommended to use transport encryption (SSL/TLS) and to avoid sending sensitive information (such as login credentials) in HTTP GET URL parameters. Consider using HTTP POST requests or HTTP headers for such information instead. </description> </method> <method name="request_raw"> diff --git a/doc/classes/JNISingleton.xml b/doc/classes/JNISingleton.xml index 84ab1a49c1..fbf18ddc03 100644 --- a/doc/classes/JNISingleton.xml +++ b/doc/classes/JNISingleton.xml @@ -1,10 +1,13 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="JNISingleton" inherits="Object" version="4.0"> <brief_description> + Singleton that connects the engine with Android plugins to interface with native Android code. </brief_description> <description> + The JNISingleton is implemented only in the Android export. It's used to call methods and connect signals from an Android plugin written in Java or Kotlin. Methods and signals can be called and connected to the JNISingleton as if it is a Node. See [url=https://en.wikipedia.org/wiki/Java_Native_Interface]Java Native Interface - Wikipedia[/url] for more information. </description> <tutorials> + <link title="Creating Android plugins">https://docs.godotengine.org/en/latest/tutorials/platform/android/android_plugin.html#doc-android-plugin</link> </tutorials> <methods> </methods> diff --git a/doc/classes/Light3D.xml b/doc/classes/Light3D.xml index cd2f4eca18..52359b0ede 100644 --- a/doc/classes/Light3D.xml +++ b/doc/classes/Light3D.xml @@ -47,7 +47,8 @@ The light's strength multiplier (this is not a physical unit). For [OmniLight3D] and [SpotLight3D], changing this value will only change the light color's intensity, not the light's radius. </member> <member name="light_indirect_energy" type="float" setter="set_param" getter="get_param" default="1.0"> - Secondary multiplier used with indirect light (light bounces). Used with [VoxelGI]. + Secondary multiplier used with indirect light (light bounces). Used with [VoxelGI] and SDFGI (see [member Environment.sdfgi_enabled]). + [b]Note:[/b] This property is ignored if [member light_energy] is equal to [code]0.0[/code], as the light won't be present at all in the GI shader. </member> <member name="light_negative" type="bool" setter="set_negative" getter="is_negative" default="false"> If [code]true[/code], the light's effect is reversed, darkening areas and casting bright shadows. diff --git a/doc/classes/Listener2D.xml b/doc/classes/Listener2D.xml new file mode 100644 index 0000000000..27ee63d201 --- /dev/null +++ b/doc/classes/Listener2D.xml @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="Listener2D" inherits="Node2D" version="4.0"> + <brief_description> + Overrides the location sounds are heard from. + </brief_description> + <description> + Once added to the scene tree and enabled using [method make_current], this node will override the location sounds are heard from. Only one [Listener2D] can be current. Using [method make_current] will disable the previous [Listener2D]. + If there is no active [Listener2D] in the current [Viewport], center of the screen will be used as a hearing point for the audio. [Listener2D] needs to be inside [SceneTree] to function. + </description> + <tutorials> + </tutorials> + <methods> + <method name="clear_current"> + <return type="void" /> + <description> + Disables the [Listener2D]. If it's not set as current, this method will have no effect. + </description> + </method> + <method name="is_current" qualifiers="const"> + <return type="bool" /> + <description> + Returns [code]true[/code] if this [Listener2D] is currently active. + </description> + </method> + <method name="make_current"> + <return type="void" /> + <description> + Makes the [Listener2D] active, setting it as the hearing point for the sounds. If there is already another active [Listener2D], it will be disabled. + This method will have no effect if the [Listener2D] is not added to [SceneTree]. + </description> + </method> + </methods> + <constants> + </constants> +</class> diff --git a/doc/classes/Listener3D.xml b/doc/classes/Listener3D.xml index 9cc803f241..5e1b2ce7fc 100644 --- a/doc/classes/Listener3D.xml +++ b/doc/classes/Listener3D.xml @@ -5,7 +5,6 @@ </brief_description> <description> Once added to the scene tree and enabled using [method make_current], this node will override the location sounds are heard from. This can be used to listen from a location different from the [Camera3D]. - [b]Note:[/b] There is no 2D equivalent for this node yet. </description> <tutorials> </tutorials> diff --git a/doc/classes/Mesh.xml b/doc/classes/Mesh.xml index bfa55c2d35..c774528a39 100644 --- a/doc/classes/Mesh.xml +++ b/doc/classes/Mesh.xml @@ -210,6 +210,8 @@ </constant> <constant name="ARRAY_FORMAT_CUSTOM_BASE" value="13" enum="ArrayFormat"> </constant> + <constant name="ARRAY_FORMAT_CUSTOM_BITS" value="3" enum="ArrayFormat"> + </constant> <constant name="ARRAY_FORMAT_CUSTOM0_SHIFT" value="13" enum="ArrayFormat"> </constant> <constant name="ARRAY_FORMAT_CUSTOM1_SHIFT" value="16" enum="ArrayFormat"> diff --git a/doc/classes/MeshLibrary.xml b/doc/classes/MeshLibrary.xml index 9e0292f946..1d07647ea7 100644 --- a/doc/classes/MeshLibrary.xml +++ b/doc/classes/MeshLibrary.xml @@ -45,6 +45,13 @@ Returns the item's mesh. </description> </method> + <method name="get_item_mesh_transform" qualifiers="const"> + <return type="Transform3D" /> + <argument index="0" name="id" type="int" /> + <description> + Returns the transform applied to the item's mesh. + </description> + </method> <method name="get_item_name" qualifiers="const"> <return type="String" /> <argument index="0" name="id" type="int" /> @@ -102,6 +109,14 @@ Sets the item's mesh. </description> </method> + <method name="set_item_mesh_transform"> + <return type="void" /> + <argument index="0" name="id" type="int" /> + <argument index="1" name="mesh_transform" type="Transform3D" /> + <description> + Sets the transform to apply to the item's mesh. + </description> + </method> <method name="set_item_name"> <return type="void" /> <argument index="0" name="id" type="int" /> diff --git a/doc/classes/MultiplayerAPI.xml b/doc/classes/MultiplayerAPI.xml index 70046fc3e9..647233f679 100644 --- a/doc/classes/MultiplayerAPI.xml +++ b/doc/classes/MultiplayerAPI.xml @@ -4,8 +4,8 @@ High-level multiplayer API. </brief_description> <description> - This class implements most of the logic behind the high-level multiplayer API. See also [MultiplayerPeer]. - By default, [SceneTree] has a reference to this class that is used to provide multiplayer capabilities (i.e. RPC/RSET) across the whole scene. + This class implements the high-level multiplayer API. See also [MultiplayerPeer]. + By default, [SceneTree] has a reference to this class that is used to provide multiplayer capabilities (i.e. RPCs) across the whole scene. It is possible to override the MultiplayerAPI instance used by specific Nodes by setting the [member Node.custom_multiplayer] property, effectively allowing to run both client and server in the same scene. [b]Note:[/b] The high-level multiplayer API protocol is an implementation detail and isn't meant to be used by non-Godot servers. It may change without notice. </description> @@ -18,49 +18,49 @@ Clears the current MultiplayerAPI network state (you shouldn't call this unless you know what you are doing). </description> </method> - <method name="get_network_connected_peers" qualifiers="const"> + <method name="get_peers" qualifiers="const"> <return type="PackedInt32Array" /> <description> - Returns the peer IDs of all connected peers of this MultiplayerAPI's [member network_peer]. + Returns the peer IDs of all connected peers of this MultiplayerAPI's [member multiplayer_peer]. </description> </method> - <method name="get_network_unique_id" qualifiers="const"> + <method name="get_remote_sender_id" qualifiers="const"> <return type="int" /> <description> - Returns the unique peer ID of this MultiplayerAPI's [member network_peer]. + Returns the sender's peer ID for the RPC currently being executed. + [b]Note:[/b] If not inside an RPC this method will return 0. </description> </method> - <method name="get_rpc_sender_id" qualifiers="const"> + <method name="get_unique_id" qualifiers="const"> <return type="int" /> <description> - Returns the sender's peer ID for the RPC currently being executed. - [b]Note:[/b] If not inside an RPC this method will return 0. + Returns the unique peer ID of this MultiplayerAPI's [member multiplayer_peer]. </description> </method> - <method name="has_network_peer" qualifiers="const"> + <method name="has_multiplayer_peer" qualifiers="const"> <return type="bool" /> <description> - Returns [code]true[/code] if there is a [member network_peer] set. + Returns [code]true[/code] if there is a [member multiplayer_peer] set. </description> </method> - <method name="is_network_server" qualifiers="const"> + <method name="is_server" qualifiers="const"> <return type="bool" /> <description> - Returns [code]true[/code] if this MultiplayerAPI's [member network_peer] is in server mode (listening for connections). + Returns [code]true[/code] if this MultiplayerAPI's [member multiplayer_peer] is valid and in server mode (listening for connections). </description> </method> <method name="poll"> <return type="void" /> <description> Method used for polling the MultiplayerAPI. You only need to worry about this if you are using [member Node.custom_multiplayer] override or you set [member SceneTree.multiplayer_poll] to [code]false[/code]. By default, [SceneTree] will poll its MultiplayerAPI for you. - [b]Note:[/b] This method results in RPCs and RSETs being called, so they will be executed in the same context of this function (e.g. [code]_process[/code], [code]physics[/code], [Thread]). + [b]Note:[/b] This method results in RPCs being called, so they will be executed in the same context of this function (e.g. [code]_process[/code], [code]physics[/code], [Thread]). </description> </method> <method name="send_bytes"> <return type="int" enum="Error" /> <argument index="0" name="bytes" type="PackedByteArray" /> <argument index="1" name="id" type="int" default="0" /> - <argument index="2" name="mode" type="int" enum="MultiplayerPeer.TransferMode" default="2" /> + <argument index="2" name="mode" type="int" enum="TransferMode" default="2" /> <argument index="3" name="channel" type="int" default="0" /> <description> Sends the given raw [code]bytes[/code] to a specific peer identified by [code]id[/code] (see [method MultiplayerPeer.set_target_peer]). Default ID is [code]0[/code], i.e. broadcast to all peers. @@ -69,14 +69,14 @@ </methods> <members> <member name="allow_object_decoding" type="bool" setter="set_allow_object_decoding" getter="is_object_decoding_allowed" default="false"> - If [code]true[/code], the MultiplayerAPI will allow encoding and decoding of object during RPCs/RSETs. + If [code]true[/code], the MultiplayerAPI will allow encoding and decoding of object during RPCs. [b]Warning:[/b] Deserialized objects can contain code which gets executed. Do not use this option if the serialized object comes from untrusted sources to avoid potential security threats such as remote code execution. </member> - <member name="network_peer" type="MultiplayerPeer" setter="set_network_peer" getter="get_network_peer"> - The peer object to handle the RPC system (effectively enabling networking when set). Depending on the peer itself, the MultiplayerAPI will become a network server (check with [method is_network_server]) and will set root node's network mode to authority, or it will become a regular client peer. All child nodes are set to inherit the network mode by default. Handling of networking-related events (connection, disconnection, new clients) is done by connecting to MultiplayerAPI's signals. + <member name="multiplayer_peer" type="MultiplayerPeer" setter="set_multiplayer_peer" getter="get_multiplayer_peer"> + The peer object to handle the RPC system (effectively enabling networking when set). Depending on the peer itself, the MultiplayerAPI will become a network server (check with [method is_server]) and will set root node's network mode to authority, or it will become a regular client peer. All child nodes are set to inherit the network mode by default. Handling of networking-related events (connection, disconnection, new clients) is done by connecting to MultiplayerAPI's signals. </member> - <member name="refuse_new_network_connections" type="bool" setter="set_refuse_new_network_connections" getter="is_refusing_new_network_connections" default="false"> - If [code]true[/code], the MultiplayerAPI's [member network_peer] refuses new incoming connections. + <member name="refuse_new_connections" type="bool" setter="set_refuse_new_connections" getter="is_refusing_new_connections" default="false"> + If [code]true[/code], the MultiplayerAPI's [member multiplayer_peer] refuses new incoming connections. </member> <member name="replicator" type="MultiplayerReplicator" setter="" getter="get_replicator"> </member> @@ -88,48 +88,39 @@ <signals> <signal name="connected_to_server"> <description> - Emitted when this MultiplayerAPI's [member network_peer] successfully connected to a server. Only emitted on clients. + Emitted when this MultiplayerAPI's [member multiplayer_peer] successfully connected to a server. Only emitted on clients. </description> </signal> <signal name="connection_failed"> <description> - Emitted when this MultiplayerAPI's [member network_peer] fails to establish a connection to a server. Only emitted on clients. + Emitted when this MultiplayerAPI's [member multiplayer_peer] fails to establish a connection to a server. Only emitted on clients. </description> </signal> - <signal name="network_peer_connected"> + <signal name="peer_connected"> <argument index="0" name="id" type="int" /> <description> - Emitted when this MultiplayerAPI's [member network_peer] connects with a new peer. ID is the peer ID of the new peer. Clients get notified when other clients connect to the same server. Upon connecting to a server, a client also receives this signal for the server (with ID being 1). + Emitted when this MultiplayerAPI's [member multiplayer_peer] connects with a new peer. ID is the peer ID of the new peer. Clients get notified when other clients connect to the same server. Upon connecting to a server, a client also receives this signal for the server (with ID being 1). </description> </signal> - <signal name="network_peer_disconnected"> + <signal name="peer_disconnected"> <argument index="0" name="id" type="int" /> <description> - Emitted when this MultiplayerAPI's [member network_peer] disconnects from a peer. Clients get notified when other clients disconnect from the same server. + Emitted when this MultiplayerAPI's [member multiplayer_peer] disconnects from a peer. Clients get notified when other clients disconnect from the same server. </description> </signal> - <signal name="network_peer_packet"> + <signal name="peer_packet"> <argument index="0" name="id" type="int" /> <argument index="1" name="packet" type="PackedByteArray" /> <description> - Emitted when this MultiplayerAPI's [member network_peer] receive a [code]packet[/code] with custom data (see [method send_bytes]). ID is the peer ID of the peer that sent the packet. + Emitted when this MultiplayerAPI's [member multiplayer_peer] receives a [code]packet[/code] with custom data (see [method send_bytes]). ID is the peer ID of the peer that sent the packet. </description> </signal> <signal name="server_disconnected"> <description> - Emitted when this MultiplayerAPI's [member network_peer] disconnects from server. Only emitted on clients. + Emitted when this MultiplayerAPI's [member multiplayer_peer] disconnects from server. Only emitted on clients. </description> </signal> </signals> <constants> - <constant name="RPC_MODE_DISABLED" value="0" enum="RPCMode"> - Used with [method Node.rpc_config] to disable a method or property for all RPC calls, making it unavailable. Default for all methods. - </constant> - <constant name="RPC_MODE_ANY" value="1" enum="RPCMode"> - Used with [method Node.rpc_config] to set a method to be callable remotely by any peer. Analogous to the [code]@rpc(any)[/code] annotation. Calls are accepted from all remote peers, no matter if they are node's authority or not. - </constant> - <constant name="RPC_MODE_AUTHORITY" value="2" enum="RPCMode"> - Used with [method Node.rpc_config] to set a method to be callable remotely only by the current network authority (which is the server by default). Analogous to the [code]@rpc(auth)[/code] annotation. See [method Node.set_network_authority]. - </constant> </constants> </class> diff --git a/doc/classes/MultiplayerPeer.xml b/doc/classes/MultiplayerPeer.xml index adaa359168..411317cdc8 100644 --- a/doc/classes/MultiplayerPeer.xml +++ b/doc/classes/MultiplayerPeer.xml @@ -4,7 +4,7 @@ A high-level network interface to simplify multiplayer interactions. </brief_description> <description> - Manages the connection to network peers. Assigns unique IDs to each client connected to the server. See also [MultiplayerAPI]. + Manages the connection to multiplayer peers. Assigns unique IDs to each client connected to the server. See also [MultiplayerAPI]. [b]Note:[/b] The high-level multiplayer API protocol is an implementation detail and isn't meant to be used by non-Godot servers. It may change without notice. </description> <tutorials> @@ -57,9 +57,9 @@ </member> <member name="transfer_channel" type="int" setter="set_transfer_channel" getter="get_transfer_channel" default="0"> The channel to use to send packets. Many network APIs such as ENet and WebRTC allow the creation of multiple independent channels which behaves, in a way, like separate connections. This means that reliable data will only block delivery of other packets on that channel, and ordering will only be in respect to the channel the packet is being sent on. Using different channels to send [b]different and independent[/b] state updates is a common way to optimize network usage and decrease latency in fast-paced games. - [b]Note:[/b] The default channel ([code]0[/code]) actually works as 3 separate channels (one for each [enum TransferMode]) so that [constant TRANSFER_MODE_RELIABLE] and [constant TRANSFER_MODE_UNRELIABLE_ORDERED] does not interact with each other by default. Refer to the specific network API documentation (e.g. ENet or WebRTC) to learn how to set up channels correctly. + [b]Note:[/b] The default channel ([code]0[/code]) actually works as 3 separate channels (one for each [enum TransferMode]) so that [constant TRANSFER_MODE_RELIABLE] and [constant TRANSFER_MODE_ORDERED] does not interact with each other by default. Refer to the specific network API documentation (e.g. ENet or WebRTC) to learn how to set up channels correctly. </member> - <member name="transfer_mode" type="int" setter="set_transfer_mode" getter="get_transfer_mode" enum="MultiplayerPeer.TransferMode" default="0"> + <member name="transfer_mode" type="int" setter="set_transfer_mode" getter="get_transfer_mode" enum="TransferMode" default="0"> The manner in which to send packets to the [code]target_peer[/code]. See [enum TransferMode]. </member> </members> @@ -93,15 +93,6 @@ </signal> </signals> <constants> - <constant name="TRANSFER_MODE_UNRELIABLE" value="0" enum="TransferMode"> - Packets are not acknowledged, no resend attempts are made for lost packets. Packets may arrive in any order. Potentially faster than [constant TRANSFER_MODE_UNRELIABLE_ORDERED]. Use for non-critical data, and always consider whether the order matters. - </constant> - <constant name="TRANSFER_MODE_UNRELIABLE_ORDERED" value="1" enum="TransferMode"> - Packets are not acknowledged, no resend attempts are made for lost packets. Packets are received in the order they were sent in. Potentially faster than [constant TRANSFER_MODE_RELIABLE]. Use for non-critical data or data that would be outdated if received late due to resend attempt(s) anyway, for example movement and positional data. - </constant> - <constant name="TRANSFER_MODE_RELIABLE" value="2" enum="TransferMode"> - Packets must be received and resend attempts should be made until the packets are acknowledged. Packets must be received in the order they were sent in. Most reliable transfer mode, but potentially the slowest due to the overhead. Use for critical data that must be transmitted and arrive in order, for example an ability being triggered or a chat message. Consider carefully if the information really is critical, and use sparingly. - </constant> <constant name="CONNECTION_DISCONNECTED" value="0" enum="ConnectionStatus"> The ongoing connection disconnected. </constant> diff --git a/doc/classes/MultiplayerReplicator.xml b/doc/classes/MultiplayerReplicator.xml index 0778a7335f..e0c309ef39 100644 --- a/doc/classes/MultiplayerReplicator.xml +++ b/doc/classes/MultiplayerReplicator.xml @@ -44,7 +44,7 @@ <argument index="2" name="data" type="Variant" default="null" /> <argument index="3" name="path" type="NodePath" default="NodePath("")" /> <description> - Sends a despawn request for the scene identified by [code]scene_id[/code] to the given [code]peer_id[/code] (see [method MultiplayerPeer.set_target_peer]). If the scene is configured as [constant REPLICATION_MODE_SERVER] (see [method spawn_config]) and the request is sent by the server (see [method MultiplayerAPI.is_network_server]), the receiving peer(s) will automatically queue for deletion the node at [code]path[/code] and emit the signal [signal despawned]. In all other cases no deletion happens, and the signal [signal despawn_requested] is emitted instead. + Sends a despawn request for the scene identified by [code]scene_id[/code] to the given [code]peer_id[/code] (see [method MultiplayerPeer.set_target_peer]). If the scene is configured as [constant REPLICATION_MODE_SERVER] (see [method spawn_config]) and the request is sent by the server (see [method MultiplayerAPI.is_server]), the receiving peer(s) will automatically queue for deletion the node at [code]path[/code] and emit the signal [signal despawned]. In all other cases no deletion happens, and the signal [signal despawn_requested] is emitted instead. </description> </method> <method name="send_spawn"> @@ -54,7 +54,7 @@ <argument index="2" name="data" type="Variant" default="null" /> <argument index="3" name="path" type="NodePath" default="NodePath("")" /> <description> - Sends a spawn request for the scene identified by [code]scene_id[/code] to the given [code]peer_id[/code] (see [method MultiplayerPeer.set_target_peer]). If the scene is configured as [constant REPLICATION_MODE_SERVER] (see [method spawn_config]) and the request is sent by the server (see [method MultiplayerAPI.is_network_server]), the receiving peer(s) will automatically instantiate that scene, add it to the [SceneTree] at the given [code]path[/code] and emit the signal [signal spawned]. In all other cases no instantiation happens, and the signal [signal spawn_requested] is emitted instead. + Sends a spawn request for the scene identified by [code]scene_id[/code] to the given [code]peer_id[/code] (see [method MultiplayerPeer.set_target_peer]). If the scene is configured as [constant REPLICATION_MODE_SERVER] (see [method spawn_config]) and the request is sent by the server (see [method MultiplayerAPI.is_server]), the receiving peer(s) will automatically instantiate that scene, add it to the [SceneTree] at the given [code]path[/code] and emit the signal [signal spawned]. In all other cases no instantiation happens, and the signal [signal spawn_requested] is emitted instead. </description> </method> <method name="send_sync"> @@ -62,7 +62,7 @@ <argument index="0" name="peer_id" type="int" /> <argument index="1" name="scene_id" type="int" /> <argument index="2" name="data" type="PackedByteArray" /> - <argument index="3" name="transfer_mode" type="int" enum="MultiplayerPeer.TransferMode" default="2" /> + <argument index="3" name="transfer_mode" type="int" enum="TransferMode" default="2" /> <argument index="4" name="channel" type="int" default="0" /> <description> Sends a sync request for the instances of the scene identified by [code]scene_id[/code] to the given [code]peer_id[/code] (see [method MultiplayerPeer.set_target_peer]). This function can only be called manually when overriding the send and receive sync functions (see [method sync_config]). diff --git a/doc/classes/Node.xml b/doc/classes/Node.xml index 7d79c50466..608d76cd9f 100644 --- a/doc/classes/Node.xml +++ b/doc/classes/Node.xml @@ -153,6 +153,7 @@ <description> Adds the node to a group. Groups are helpers to name and organize a subset of nodes, for example "enemies" or "collectables". A node can be in any number of groups. Nodes can be assigned a group at any time, but will not be added until they are inside the scene tree (see [method is_inside_tree]). See notes in the description, and the group methods in [SceneTree]. The [code]persistent[/code] option is used when packing node to [PackedScene] and saving to file. Non-persistent groups aren't stored. + [b]Note:[/b] For performance reasons, the order of node groups is [i]not[/i] guaranteed. The order of node groups should not be relied upon as it can vary across project runs. </description> </method> <method name="can_process" qualifiers="const"> @@ -236,6 +237,7 @@ <return type="Array" /> <description> Returns an array listing the groups that the node is a member of. + [b]Note:[/b] For performance reasons, the order of node groups is [i]not[/i] guaranteed. The order of node groups should not be relied upon as it can vary across project runs. </description> </method> <method name="get_index" qualifiers="const"> @@ -246,10 +248,10 @@ If [code]include_internal[/code] is [code]false[/code], the index won't take internal children into account, i.e. first non-internal child will have index of 0 (see [code]internal[/code] parameter in [method add_child]). </description> </method> - <method name="get_network_authority" qualifiers="const"> + <method name="get_multiplayer_authority" qualifiers="const"> <return type="int" /> <description> - Returns the peer ID of the network authority for this node. See [method set_network_authority]. + Returns the peer ID of the multiplayer authority for this node. See [method set_multiplayer_authority]. </description> </method> <method name="get_node" qualifiers="const"> @@ -417,10 +419,10 @@ Returns [code]true[/code] if this node is currently inside a [SceneTree]. </description> </method> - <method name="is_network_authority" qualifiers="const"> + <method name="is_multiplayer_authority" qualifiers="const"> <return type="bool" /> <description> - Returns [code]true[/code] if the local system is the authority of this node. + Returns [code]true[/code] if the local system is the multiplayer authority of this node. </description> </method> <method name="is_physics_processing" qualifiers="const"> @@ -578,17 +580,17 @@ <argument index="0" name="method" type="StringName" /> <description> Sends a remote procedure call request for the given [code]method[/code] to peers on the network (and locally), optionally sending all additional arguments as arguments to the method called by the RPC. The call request will only be received by nodes with the same [NodePath], including the exact same node name. Behaviour depends on the RPC configuration for the given method, see [method rpc_config]. Methods are not exposed to RPCs by default. Returns an empty [Variant]. - [b]Note:[/b] You can only safely use RPCs on clients after you received the [code]connected_to_server[/code] signal from the [MultiplayerAPI]. You also need to keep track of the connection state, either by the [MultiplayerAPI] signals like [code]server_disconnected[/code] or by checking [code]get_multiplayer().network_peer.get_connection_status() == CONNECTION_CONNECTED[/code]. + [b]Note:[/b] You can only safely use RPCs on clients after you received the [code]connected_to_server[/code] signal from the [MultiplayerAPI]. You also need to keep track of the connection state, either by the [MultiplayerAPI] signals like [code]server_disconnected[/code] or by checking [code]get_multiplayer().peer.get_connection_status() == CONNECTION_CONNECTED[/code]. </description> </method> <method name="rpc_config"> <return type="int" /> <argument index="0" name="method" type="StringName" /> - <argument index="1" name="rpc_mode" type="int" enum="MultiplayerAPI.RPCMode" /> - <argument index="2" name="transfer_mode" type="int" enum="MultiplayerPeer.TransferMode" default="2" /> + <argument index="1" name="rpc_mode" type="int" enum="RPCMode" /> + <argument index="2" name="transfer_mode" type="int" enum="TransferMode" default="2" /> <argument index="3" name="channel" type="int" default="0" /> <description> - Changes the RPC mode for the given [code]method[/code] to the given [code]rpc_mode[/code], optionally specifying the [code]transfer_mode[/code] and [code]channel[/code] (on supported peers). See [enum MultiplayerAPI.RPCMode] and [enum MultiplayerPeer.TransferMode]. An alternative is annotating methods and properties with the corresponding annotation ([code]@rpc(any)[/code], [code]@rpc(auth)[/code]). By default, methods are not exposed to networking (and RPCs). + Changes the RPC mode for the given [code]method[/code] to the given [code]rpc_mode[/code], optionally specifying the [code]transfer_mode[/code] and [code]channel[/code] (on supported peers). See [enum RPCMode] and [enum TransferMode]. An alternative is annotating methods and properties with the corresponding annotation ([code]@rpc(any)[/code], [code]@rpc(auth)[/code]). By default, methods are not exposed to networking (and RPCs). </description> </method> <method name="rpc_id" qualifiers="vararg"> @@ -620,12 +622,12 @@ <description> </description> </method> - <method name="set_network_authority"> + <method name="set_multiplayer_authority"> <return type="void" /> <argument index="0" name="id" type="int" /> <argument index="1" name="recursive" type="bool" default="true" /> <description> - Sets the node's network authority to the peer with the given peer ID. The network authority is the peer that has authority over the node on the network. Useful in conjunction with [method rpc_config] and the [MultiplayerAPI]. Inherited from the parent node by default, which ultimately defaults to peer ID 1 (the server). If [code]recursive[/code], the given peer is recursively set as the authority for all children of this node. + Sets the node's multiplayer authority to the peer with the given peer ID. The multiplayer authority is the peer that has authority over the node on the network. Useful in conjunction with [method rpc_config] and the [MultiplayerAPI]. Inherited from the parent node by default, which ultimately defaults to peer ID 1 (the server). If [code]recursive[/code], the given peer is recursively set as the authority for all children of this node. </description> </method> <method name="set_physics_process"> diff --git a/doc/classes/Object.xml b/doc/classes/Object.xml index c9e9a0699c..ed045f8390 100644 --- a/doc/classes/Object.xml +++ b/doc/classes/Object.xml @@ -92,12 +92,12 @@ Calls the [code]method[/code] on the object and returns the result. This method supports a variable number of arguments, so parameters are passed as a comma separated list. Example: [codeblocks] [gdscript] - var node = Node2D.new() - node.call("set", "position", Vector2(42, 0)) + var node = Node3D.new() + node.call("rotate", Vector3(1.0, 0.0, 0.0), 1.571) [/gdscript] [csharp] - var node = new Node2D(); - node.Call("set", "position", new Vector2(42, 0)); + var node = new Node3D(); + node.Call("rotate", new Vector3(1f, 0f, 0f), 1.571f); [/csharp] [/codeblocks] [b]Note:[/b] In C#, the method name must be specified as snake_case if it is defined by a built-in Godot node. This doesn't apply to user-defined methods where you should use the same convention as in the C# source (typically PascalCase). @@ -110,12 +110,12 @@ Calls the [code]method[/code] on the object during idle time. This method supports a variable number of arguments, so parameters are passed as a comma separated list. Example: [codeblocks] [gdscript] - var node = Node2D.new() - node.call_deferred("set", "position", Vector2(42, 0)) + var node = Node3D.new() + node.call_deferred("rotate", Vector3(1.0, 0.0, 0.0), 1.571) [/gdscript] [csharp] - var node = new Node2D(); - node.CallDeferred("set", "position", new Vector2(42, 0)); + var node = new Node3D(); + node.CallDeferred("rotate", new Vector3(1f, 0f, 0f), 1.571f); [/csharp] [/codeblocks] [b]Note:[/b] In C#, the method name must be specified as snake_case if it is defined by a built-in Godot node. This doesn't apply to user-defined methods where you should use the same convention as in the C# source (typically PascalCase). @@ -129,12 +129,12 @@ Calls the [code]method[/code] on the object and returns the result. Contrarily to [method call], this method does not support a variable number of arguments but expects all parameters to be via a single [Array]. [codeblocks] [gdscript] - var node = Node2D.new() - node.callv("set", ["position", Vector2(42, 0)]) + var node = Node3D.new() + node.callv("rotate", [Vector3(1.0, 0.0, 0.0), 1.571]) [/gdscript] [csharp] - var node = new Node2D(); - node.Callv("set", new Godot.Collections.Array { "position", new Vector2(42, 0) }); + var node = new Node3D(); + node.Callv("rotate", new Godot.Collections.Array { new Vector3(1f, 0f, 0f), 1.571f }); [/csharp] [/codeblocks] </description> @@ -331,7 +331,8 @@ <method name="get_class" qualifiers="const"> <return type="String" /> <description> - Returns the object's class as a [String]. + Returns the object's class as a [String]. See also [method is_class]. + [b]Note:[/b] [method get_class] does not take [code]class_name[/code] declarations into account. If the object has a [code]class_name[/code] defined, the base class name will be returned instead. </description> </method> <method name="get_incoming_connections" qualifiers="const"> @@ -441,7 +442,8 @@ <return type="bool" /> <argument index="0" name="class" type="String" /> <description> - Returns [code]true[/code] if the object inherits from the given [code]class[/code]. + Returns [code]true[/code] if the object inherits from the given [code]class[/code]. See also [method get_class]. + [b]Note:[/b] [method is_class] does not take [code]class_name[/code] declarations into account. If the object has a [code]class_name[/code] defined, [method is_class] will return [code]false[/code] for that name. </description> </method> <method name="is_connected" qualifiers="const"> diff --git a/doc/classes/PackedByteArray.xml b/doc/classes/PackedByteArray.xml index af92590da3..72f134e9e9 100644 --- a/doc/classes/PackedByteArray.xml +++ b/doc/classes/PackedByteArray.xml @@ -270,7 +270,7 @@ Converts UTF-8 encoded array to [String]. Slower than [method get_string_from_ascii] but supports UTF-8 encoded data. Use this function if you are unsure about the source of the data. For user input this function should always be preferred. Returns empty string if source array is not valid UTF-8 string. </description> </method> - <method name="has"> + <method name="has" qualifiers="const"> <return type="bool" /> <argument index="0" name="value" type="int" /> <description> diff --git a/doc/classes/PackedColorArray.xml b/doc/classes/PackedColorArray.xml index a5a5703bfa..2dfaefca23 100644 --- a/doc/classes/PackedColorArray.xml +++ b/doc/classes/PackedColorArray.xml @@ -56,7 +56,7 @@ Assigns the given value to all elements in the array. This can typically be used together with [method resize] to create an array with a given size and initialized elements. </description> </method> - <method name="has"> + <method name="has" qualifiers="const"> <return type="bool" /> <argument index="0" name="value" type="Color" /> <description> diff --git a/doc/classes/PackedFloat32Array.xml b/doc/classes/PackedFloat32Array.xml index 9e7dd8f99e..5c05dd9fa7 100644 --- a/doc/classes/PackedFloat32Array.xml +++ b/doc/classes/PackedFloat32Array.xml @@ -57,7 +57,7 @@ Assigns the given value to all elements in the array. This can typically be used together with [method resize] to create an array with a given size and initialized elements. </description> </method> - <method name="has"> + <method name="has" qualifiers="const"> <return type="bool" /> <argument index="0" name="value" type="float" /> <description> diff --git a/doc/classes/PackedFloat64Array.xml b/doc/classes/PackedFloat64Array.xml index ff4cf0edf8..921ca23859 100644 --- a/doc/classes/PackedFloat64Array.xml +++ b/doc/classes/PackedFloat64Array.xml @@ -57,7 +57,7 @@ Assigns the given value to all elements in the array. This can typically be used together with [method resize] to create an array with a given size and initialized elements. </description> </method> - <method name="has"> + <method name="has" qualifiers="const"> <return type="bool" /> <argument index="0" name="value" type="float" /> <description> diff --git a/doc/classes/PackedInt32Array.xml b/doc/classes/PackedInt32Array.xml index 2e9716b665..dfc9cbf939 100644 --- a/doc/classes/PackedInt32Array.xml +++ b/doc/classes/PackedInt32Array.xml @@ -57,7 +57,7 @@ Assigns the given value to all elements in the array. This can typically be used together with [method resize] to create an array with a given size and initialized elements. </description> </method> - <method name="has"> + <method name="has" qualifiers="const"> <return type="bool" /> <argument index="0" name="value" type="int" /> <description> diff --git a/doc/classes/PackedInt64Array.xml b/doc/classes/PackedInt64Array.xml index 1e7fff3bc6..dd38a4f5c2 100644 --- a/doc/classes/PackedInt64Array.xml +++ b/doc/classes/PackedInt64Array.xml @@ -57,7 +57,7 @@ Assigns the given value to all elements in the array. This can typically be used together with [method resize] to create an array with a given size and initialized elements. </description> </method> - <method name="has"> + <method name="has" qualifiers="const"> <return type="bool" /> <argument index="0" name="value" type="int" /> <description> diff --git a/doc/classes/PackedStringArray.xml b/doc/classes/PackedStringArray.xml index 04113d4a2e..c2055531a5 100644 --- a/doc/classes/PackedStringArray.xml +++ b/doc/classes/PackedStringArray.xml @@ -57,7 +57,7 @@ Assigns the given value to all elements in the array. This can typically be used together with [method resize] to create an array with a given size and initialized elements. </description> </method> - <method name="has"> + <method name="has" qualifiers="const"> <return type="bool" /> <argument index="0" name="value" type="String" /> <description> diff --git a/doc/classes/PackedVector2Array.xml b/doc/classes/PackedVector2Array.xml index 76d3aff20e..51b7c951da 100644 --- a/doc/classes/PackedVector2Array.xml +++ b/doc/classes/PackedVector2Array.xml @@ -57,7 +57,7 @@ Assigns the given value to all elements in the array. This can typically be used together with [method resize] to create an array with a given size and initialized elements. </description> </method> - <method name="has"> + <method name="has" qualifiers="const"> <return type="bool" /> <argument index="0" name="value" type="Vector2" /> <description> diff --git a/doc/classes/PackedVector3Array.xml b/doc/classes/PackedVector3Array.xml index 1a35f71619..04a5e3d42e 100644 --- a/doc/classes/PackedVector3Array.xml +++ b/doc/classes/PackedVector3Array.xml @@ -56,7 +56,7 @@ Assigns the given value to all elements in the array. This can typically be used together with [method resize] to create an array with a given size and initialized elements. </description> </method> - <method name="has"> + <method name="has" qualifiers="const"> <return type="bool" /> <argument index="0" name="value" type="Vector3" /> <description> diff --git a/doc/classes/PhysicsDirectBodyState2D.xml b/doc/classes/PhysicsDirectBodyState2D.xml index 4c6adfca32..01c8933b51 100644 --- a/doc/classes/PhysicsDirectBodyState2D.xml +++ b/doc/classes/PhysicsDirectBodyState2D.xml @@ -7,6 +7,7 @@ Provides direct access to a physics body in the [PhysicsServer2D], allowing safe changes to physics properties. This object is passed via the direct state callback of dynamic bodies, and is intended for changing the direct state of that body. See [method RigidBody2D._integrate_forces]. </description> <tutorials> + <link title="Physics introduction">https://docs.godotengine.org/en/latest/tutorials/physics/physics_introduction.html</link> <link title="Ray-casting">https://docs.godotengine.org/en/latest/tutorials/physics/ray-casting.html</link> </tutorials> <methods> @@ -155,6 +156,9 @@ <member name="angular_velocity" type="float" setter="set_angular_velocity" getter="get_angular_velocity"> The body's rotational velocity. </member> + <member name="center_of_mass" type="Vector2" setter="" getter="get_center_of_mass"> + The body's center of mass. + </member> <member name="inverse_inertia" type="float" setter="" getter="get_inverse_inertia"> The inverse of the inertia of the body. </member> diff --git a/doc/classes/PhysicsDirectBodyState3D.xml b/doc/classes/PhysicsDirectBodyState3D.xml index 271668e339..839a83cfc3 100644 --- a/doc/classes/PhysicsDirectBodyState3D.xml +++ b/doc/classes/PhysicsDirectBodyState3D.xml @@ -7,6 +7,8 @@ Provides direct access to a physics body in the [PhysicsServer3D], allowing safe changes to physics properties. This object is passed via the direct state callback of dynamic bodies, and is intended for changing the direct state of that body. See [method RigidBody3D._integrate_forces]. </description> <tutorials> + <link title="Physics introduction">https://docs.godotengine.org/en/latest/tutorials/physics/physics_introduction.html</link> + <link title="Ray-casting">https://docs.godotengine.org/en/latest/tutorials/physics/ray-casting.html</link> </tutorials> <methods> <method name="add_central_force"> @@ -157,6 +159,7 @@ The body's rotational velocity. </member> <member name="center_of_mass" type="Vector3" setter="" getter="get_center_of_mass"> + The body's center of mass. </member> <member name="inverse_inertia" type="Vector3" setter="" getter="get_inverse_inertia"> The inverse of the inertia of the body. diff --git a/doc/classes/PhysicsDirectSpaceState2D.xml b/doc/classes/PhysicsDirectSpaceState2D.xml index e84b3e0e49..536c7e4e04 100644 --- a/doc/classes/PhysicsDirectSpaceState2D.xml +++ b/doc/classes/PhysicsDirectSpaceState2D.xml @@ -7,7 +7,8 @@ Direct access object to a space in the [PhysicsServer2D]. It's used mainly to do queries against objects and areas residing in a given space. </description> <tutorials> - <link title="Ray-Casting">https://docs.godotengine.org/en/latest/tutorials/physics/ray-casting.html</link> + <link title="Physics introduction">https://docs.godotengine.org/en/latest/tutorials/physics/physics_introduction.html</link> + <link title="Ray-casting">https://docs.godotengine.org/en/latest/tutorials/physics/ray-casting.html</link> </tutorials> <methods> <method name="cast_motion"> @@ -47,7 +48,7 @@ <argument index="0" name="point" type="Vector2" /> <argument index="1" name="max_results" type="int" default="32" /> <argument index="2" name="exclude" type="Array" default="[]" /> - <argument index="3" name="collision_mask" type="int" default="2147483647" /> + <argument index="3" name="collision_mask" type="int" default="4294967295" /> <argument index="4" name="collide_with_bodies" type="bool" default="true" /> <argument index="5" name="collide_with_areas" type="bool" default="false" /> <description> @@ -57,7 +58,7 @@ [code]metadata[/code]: The intersecting shape's metadata. This metadata is different from [method Object.get_meta], and is set with [method PhysicsServer2D.shape_set_data]. [code]rid[/code]: The intersecting object's [RID]. [code]shape[/code]: The shape index of the colliding shape. - Additionally, the method can take an [code]exclude[/code] array of objects or [RID]s that are to be excluded from collisions, a [code]collision_mask[/code] bitmask representing the physics layers to detect, or booleans to determine if the ray should collide with [PhysicsBody2D]s or [Area2D]s, respectively. + Additionally, the method can take an [code]exclude[/code] array of objects or [RID]s that are to be excluded from collisions, a [code]collision_mask[/code] bitmask representing the physics layers to detect (all layers by default), or booleans to determine if the ray should collide with [PhysicsBody2D]s or [Area2D]s, respectively. [b]Note:[/b] [ConcavePolygonShape2D]s and [CollisionPolygon2D]s in [code]Segments[/code] build mode are not solid shapes. Therefore, they will not be detected. </description> </method> @@ -67,10 +68,18 @@ <argument index="1" name="canvas_instance_id" type="int" /> <argument index="2" name="max_results" type="int" default="32" /> <argument index="3" name="exclude" type="Array" default="[]" /> - <argument index="4" name="collision_mask" type="int" default="2147483647" /> + <argument index="4" name="collision_mask" type="int" default="4294967295" /> <argument index="5" name="collide_with_bodies" type="bool" default="true" /> <argument index="6" name="collide_with_areas" type="bool" default="false" /> <description> + Checks whether a point is inside any solid shape, in a specific canvas layer given by [code]canvas_instance_id[/code]. The shapes the point is inside of are returned in an array containing dictionaries with the following fields: + [code]collider[/code]: The colliding object. + [code]collider_id[/code]: The colliding object's ID. + [code]metadata[/code]: The intersecting shape's metadata. This metadata is different from [method Object.get_meta], and is set with [method PhysicsServer2D.shape_set_data]. + [code]rid[/code]: The intersecting object's [RID]. + [code]shape[/code]: The shape index of the colliding shape. + Additionally, the method can take an [code]exclude[/code] array of objects or [RID]s that are to be excluded from collisions, a [code]collision_mask[/code] bitmask representing the physics layers to detect (all layers by default), or booleans to determine if the ray should collide with [PhysicsBody2D]s or [Area2D]s, respectively. + [b]Note:[/b] [ConcavePolygonShape2D]s and [CollisionPolygon2D]s in [code]Segments[/code] build mode are not solid shapes. Therefore, they will not be detected. </description> </method> <method name="intersect_ray"> @@ -78,7 +87,7 @@ <argument index="0" name="from" type="Vector2" /> <argument index="1" name="to" type="Vector2" /> <argument index="2" name="exclude" type="Array" default="[]" /> - <argument index="3" name="collision_mask" type="int" default="2147483647" /> + <argument index="3" name="collision_mask" type="int" default="4294967295" /> <argument index="4" name="collide_with_bodies" type="bool" default="true" /> <argument index="5" name="collide_with_areas" type="bool" default="false" /> <description> @@ -91,7 +100,7 @@ [code]rid[/code]: The intersecting object's [RID]. [code]shape[/code]: The shape index of the colliding shape. If the ray did not intersect anything, then an empty dictionary is returned instead. - Additionally, the method can take an [code]exclude[/code] array of objects or [RID]s that are to be excluded from collisions, a [code]collision_mask[/code] bitmask representing the physics layers to detect, or booleans to determine if the ray should collide with [PhysicsBody2D]s or [Area2D]s, respectively. + Additionally, the method can take an [code]exclude[/code] array of objects or [RID]s that are to be excluded from collisions, a [code]collision_mask[/code] bitmask representing the physics layers to detect (all layers by default), or booleans to determine if the ray should collide with [PhysicsBody2D]s or [Area2D]s, respectively. </description> </method> <method name="intersect_shape"> diff --git a/doc/classes/PhysicsDirectSpaceState3D.xml b/doc/classes/PhysicsDirectSpaceState3D.xml index 13db50a2c7..4e6bd8456f 100644 --- a/doc/classes/PhysicsDirectSpaceState3D.xml +++ b/doc/classes/PhysicsDirectSpaceState3D.xml @@ -7,6 +7,7 @@ Direct access object to a space in the [PhysicsServer3D]. It's used mainly to do queries against objects and areas residing in a given space. </description> <tutorials> + <link title="Physics introduction">https://docs.godotengine.org/en/latest/tutorials/physics/physics_introduction.html</link> <link title="Ray-casting">https://docs.godotengine.org/en/latest/tutorials/physics/ray-casting.html</link> </tutorials> <methods> @@ -47,7 +48,7 @@ <argument index="0" name="from" type="Vector3" /> <argument index="1" name="to" type="Vector3" /> <argument index="2" name="exclude" type="Array" default="[]" /> - <argument index="3" name="collision_mask" type="int" default="2147483647" /> + <argument index="3" name="collision_mask" type="int" default="4294967295" /> <argument index="4" name="collide_with_bodies" type="bool" default="true" /> <argument index="5" name="collide_with_areas" type="bool" default="false" /> <description> @@ -59,7 +60,7 @@ [code]rid[/code]: The intersecting object's [RID]. [code]shape[/code]: The shape index of the colliding shape. If the ray did not intersect anything, then an empty dictionary is returned instead. - Additionally, the method can take an [code]exclude[/code] array of objects or [RID]s that are to be excluded from collisions, a [code]collision_mask[/code] bitmask representing the physics layers to detect, or booleans to determine if the ray should collide with [PhysicsBody3D]s or [Area3D]s, respectively. + Additionally, the method can take an [code]exclude[/code] array of objects or [RID]s that are to be excluded from collisions, a [code]collision_mask[/code] bitmask representing the physics layers to detect (all layers by default), or booleans to determine if the ray should collide with [PhysicsBody3D]s or [Area3D]s, respectively. </description> </method> <method name="intersect_shape"> diff --git a/doc/classes/PhysicsServer2D.xml b/doc/classes/PhysicsServer2D.xml index 9867b98ae6..b3b7fcd956 100644 --- a/doc/classes/PhysicsServer2D.xml +++ b/doc/classes/PhysicsServer2D.xml @@ -373,7 +373,7 @@ </description> </method> <method name="body_get_param" qualifiers="const"> - <return type="float" /> + <return type="Variant" /> <argument index="0" name="body" type="RID" /> <argument index="1" name="param" type="int" enum="PhysicsServer2D.BodyParameter" /> <description> @@ -449,6 +449,13 @@ Removes a shape from a body. The shape is not deleted, so it can be reused afterwards. </description> </method> + <method name="body_reset_mass_properties"> + <return type="void" /> + <argument index="0" name="body" type="RID" /> + <description> + Restores the default inertia and center of mass based on shapes to cancel any custom values previously set using [method body_set_param]. + </description> + </method> <method name="body_set_axis_velocity"> <return type="void" /> <argument index="0" name="body" type="RID" /> @@ -522,7 +529,7 @@ <return type="void" /> <argument index="0" name="body" type="RID" /> <argument index="1" name="param" type="int" enum="PhysicsServer2D.BodyParameter" /> - <argument index="2" name="value" type="float" /> + <argument index="2" name="value" type="Variant" /> <description> Sets a body parameter. See [enum BodyParameter] for a list of available parameters. </description> @@ -913,7 +920,7 @@ This area replaces any gravity/damp calculated so far, but keeps calculating the rest of the areas, down to the default one. </constant> <constant name="BODY_MODE_STATIC" value="0" enum="BodyMode"> - Constant for static bodies. In this mode, a body can be only moved by user code. + Constant for static bodies. In this mode, a body can be only moved by user code and doesn't collide with other bodies along its path when moved. </constant> <constant name="BODY_MODE_KINEMATIC" value="1" enum="BodyMode"> Constant for kinematic bodies. In this mode, a body can be only moved by user code and collides with other bodies along its path. @@ -936,16 +943,19 @@ <constant name="BODY_PARAM_INERTIA" value="3" enum="BodyParameter"> Constant to set/get a body's inertia. </constant> - <constant name="BODY_PARAM_GRAVITY_SCALE" value="4" enum="BodyParameter"> + <constant name="BODY_PARAM_CENTER_OF_MASS" value="4" enum="BodyParameter"> + Constant to set/get a body's center of mass. + </constant> + <constant name="BODY_PARAM_GRAVITY_SCALE" value="5" enum="BodyParameter"> Constant to set/get a body's gravity multiplier. </constant> - <constant name="BODY_PARAM_LINEAR_DAMP" value="5" enum="BodyParameter"> + <constant name="BODY_PARAM_LINEAR_DAMP" value="6" enum="BodyParameter"> Constant to set/get a body's linear dampening factor. </constant> - <constant name="BODY_PARAM_ANGULAR_DAMP" value="6" enum="BodyParameter"> + <constant name="BODY_PARAM_ANGULAR_DAMP" value="7" enum="BodyParameter"> Constant to set/get a body's angular dampening factor. </constant> - <constant name="BODY_PARAM_MAX" value="7" enum="BodyParameter"> + <constant name="BODY_PARAM_MAX" value="8" enum="BodyParameter"> Represents the size of the [enum BodyParameter] enum. </constant> <constant name="BODY_STATE_TRANSFORM" value="0" enum="BodyState"> diff --git a/doc/classes/PhysicsServer3D.xml b/doc/classes/PhysicsServer3D.xml index 46cbe48b28..2fbe84b8b1 100644 --- a/doc/classes/PhysicsServer3D.xml +++ b/doc/classes/PhysicsServer3D.xml @@ -347,7 +347,7 @@ </description> </method> <method name="body_get_param" qualifiers="const"> - <return type="float" /> + <return type="Variant" /> <argument index="0" name="body" type="RID" /> <argument index="1" name="param" type="int" enum="PhysicsServer3D.BodyParameter" /> <description> @@ -430,6 +430,13 @@ Removes a shape from a body. The shape is not deleted, so it can be reused afterwards. </description> </method> + <method name="body_reset_mass_properties"> + <return type="void" /> + <argument index="0" name="body" type="RID" /> + <description> + Restores the default inertia and center of mass based on shapes to cancel any custom values previously set using [method body_set_param]. + </description> + </method> <method name="body_set_axis_lock"> <return type="void" /> <argument index="0" name="body" type="RID" /> @@ -511,7 +518,7 @@ <return type="void" /> <argument index="0" name="body" type="RID" /> <argument index="1" name="param" type="int" enum="PhysicsServer3D.BodyParameter" /> - <argument index="2" name="value" type="float" /> + <argument index="2" name="value" type="Variant" /> <description> Sets a body parameter. A list of available parameters is on the [enum BodyParameter] constants. </description> @@ -1262,7 +1269,7 @@ This area replaces any gravity/damp calculated so far, but keeps calculating the rest of the areas, down to the default one. </constant> <constant name="BODY_MODE_STATIC" value="0" enum="BodyMode"> - Constant for static bodies. In this mode, a body can be only moved by user code. + Constant for static bodies. In this mode, a body can be only moved by user code and doesn't collide with other bodies along its path when moved. </constant> <constant name="BODY_MODE_KINEMATIC" value="1" enum="BodyMode"> Constant for kinematic bodies. In this mode, a body can be only moved by user code and collides with other bodies along its path. @@ -1282,16 +1289,22 @@ <constant name="BODY_PARAM_MASS" value="2" enum="BodyParameter"> Constant to set/get a body's mass. </constant> - <constant name="BODY_PARAM_GRAVITY_SCALE" value="3" enum="BodyParameter"> + <constant name="BODY_PARAM_INERTIA" value="3" enum="BodyParameter"> + Constant to set/get a body's inertia. + </constant> + <constant name="BODY_PARAM_CENTER_OF_MASS" value="4" enum="BodyParameter"> + Constant to set/get a body's center of mass. + </constant> + <constant name="BODY_PARAM_GRAVITY_SCALE" value="5" enum="BodyParameter"> Constant to set/get a body's gravity multiplier. </constant> - <constant name="BODY_PARAM_LINEAR_DAMP" value="4" enum="BodyParameter"> + <constant name="BODY_PARAM_LINEAR_DAMP" value="6" enum="BodyParameter"> Constant to set/get a body's linear dampening factor. </constant> - <constant name="BODY_PARAM_ANGULAR_DAMP" value="5" enum="BodyParameter"> + <constant name="BODY_PARAM_ANGULAR_DAMP" value="7" enum="BodyParameter"> Constant to set/get a body's angular dampening factor. </constant> - <constant name="BODY_PARAM_MAX" value="6" enum="BodyParameter"> + <constant name="BODY_PARAM_MAX" value="8" enum="BodyParameter"> Represents the size of the [enum BodyParameter] enum. </constant> <constant name="BODY_STATE_TRANSFORM" value="0" enum="BodyState"> diff --git a/doc/classes/PhysicsShapeQueryParameters2D.xml b/doc/classes/PhysicsShapeQueryParameters2D.xml index 8b006c68e7..b54de15d15 100644 --- a/doc/classes/PhysicsShapeQueryParameters2D.xml +++ b/doc/classes/PhysicsShapeQueryParameters2D.xml @@ -17,7 +17,7 @@ <member name="collide_with_bodies" type="bool" setter="set_collide_with_bodies" getter="is_collide_with_bodies_enabled" default="true"> If [code]true[/code], the query will take [PhysicsBody2D]s into account. </member> - <member name="collision_mask" type="int" setter="set_collision_mask" getter="get_collision_mask" default="2147483647"> + <member name="collision_mask" type="int" setter="set_collision_mask" getter="get_collision_mask" default="4294967295"> The physics layers the query will detect (as a bitmask). By default, all collision layers are detected. See [url=https://docs.godotengine.org/en/latest/tutorials/physics/physics_introduction.html#collision-layers-and-masks]Collision layers and masks[/url] in the documentation for more information. </member> <member name="exclude" type="Array" setter="set_exclude" getter="get_exclude" default="[]"> diff --git a/doc/classes/PhysicsShapeQueryParameters3D.xml b/doc/classes/PhysicsShapeQueryParameters3D.xml index de9b623591..f74d1b5e48 100644 --- a/doc/classes/PhysicsShapeQueryParameters3D.xml +++ b/doc/classes/PhysicsShapeQueryParameters3D.xml @@ -17,7 +17,7 @@ <member name="collide_with_bodies" type="bool" setter="set_collide_with_bodies" getter="is_collide_with_bodies_enabled" default="true"> If [code]true[/code], the query will take [PhysicsBody3D]s into account. </member> - <member name="collision_mask" type="int" setter="set_collision_mask" getter="get_collision_mask" default="2147483647"> + <member name="collision_mask" type="int" setter="set_collision_mask" getter="get_collision_mask" default="4294967295"> The physics layers the query will detect (as a bitmask). By default, all collision layers are detected. See [url=https://docs.godotengine.org/en/latest/tutorials/physics/physics_introduction.html#collision-layers-and-masks]Collision layers and masks[/url] in the documentation for more information. </member> <member name="exclude" type="Array" setter="set_exclude" getter="get_exclude" default="[]"> diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index 58c9d9e44b..6fdce591ec 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -328,6 +328,9 @@ <member name="debug/gdscript/warnings/deprecated_keyword" type="bool" setter="" getter="" default="true"> If [code]true[/code], enables warnings when deprecated keywords are used. </member> + <member name="debug/gdscript/warnings/empty_file" type="bool" setter="" getter="" default="true"> + If [code]true[/code], enables warnings when an empty file is parsed. + </member> <member name="debug/gdscript/warnings/enable" type="bool" setter="" getter="" default="true"> If [code]true[/code], enables specific GDScript warnings (see [code]debug/gdscript/warnings/*[/code] settings). If [code]false[/code], disables all GDScript warnings. </member> @@ -625,19 +628,19 @@ </member> <member name="input/ui_text_backspace_all_to_left" type="Dictionary" setter="" getter=""> </member> - <member name="input/ui_text_backspace_all_to_left.OSX" type="Dictionary" setter="" getter=""> + <member name="input/ui_text_backspace_all_to_left.osx" type="Dictionary" setter="" getter=""> </member> <member name="input/ui_text_backspace_word" type="Dictionary" setter="" getter=""> </member> - <member name="input/ui_text_backspace_word.OSX" type="Dictionary" setter="" getter=""> + <member name="input/ui_text_backspace_word.osx" type="Dictionary" setter="" getter=""> </member> <member name="input/ui_text_caret_document_end" type="Dictionary" setter="" getter=""> </member> - <member name="input/ui_text_caret_document_end.OSX" type="Dictionary" setter="" getter=""> + <member name="input/ui_text_caret_document_end.osx" type="Dictionary" setter="" getter=""> </member> <member name="input/ui_text_caret_document_start" type="Dictionary" setter="" getter=""> </member> - <member name="input/ui_text_caret_document_start.OSX" type="Dictionary" setter="" getter=""> + <member name="input/ui_text_caret_document_start.osx" type="Dictionary" setter="" getter=""> </member> <member name="input/ui_text_caret_down" type="Dictionary" setter="" getter=""> </member> @@ -645,11 +648,11 @@ </member> <member name="input/ui_text_caret_line_end" type="Dictionary" setter="" getter=""> </member> - <member name="input/ui_text_caret_line_end.OSX" type="Dictionary" setter="" getter=""> + <member name="input/ui_text_caret_line_end.osx" type="Dictionary" setter="" getter=""> </member> <member name="input/ui_text_caret_line_start" type="Dictionary" setter="" getter=""> </member> - <member name="input/ui_text_caret_line_start.OSX" type="Dictionary" setter="" getter=""> + <member name="input/ui_text_caret_line_start.osx" type="Dictionary" setter="" getter=""> </member> <member name="input/ui_text_caret_page_down" type="Dictionary" setter="" getter=""> </member> @@ -661,11 +664,11 @@ </member> <member name="input/ui_text_caret_word_left" type="Dictionary" setter="" getter=""> </member> - <member name="input/ui_text_caret_word_left.OSX" type="Dictionary" setter="" getter=""> + <member name="input/ui_text_caret_word_left.osx" type="Dictionary" setter="" getter=""> </member> <member name="input/ui_text_caret_word_right" type="Dictionary" setter="" getter=""> </member> - <member name="input/ui_text_caret_word_right.OSX" type="Dictionary" setter="" getter=""> + <member name="input/ui_text_caret_word_right.osx" type="Dictionary" setter="" getter=""> </member> <member name="input/ui_text_completion_accept" type="Dictionary" setter="" getter=""> </member> @@ -679,11 +682,11 @@ </member> <member name="input/ui_text_delete_all_to_right" type="Dictionary" setter="" getter=""> </member> - <member name="input/ui_text_delete_all_to_right.OSX" type="Dictionary" setter="" getter=""> + <member name="input/ui_text_delete_all_to_right.osx" type="Dictionary" setter="" getter=""> </member> <member name="input/ui_text_delete_word" type="Dictionary" setter="" getter=""> </member> - <member name="input/ui_text_delete_word.OSX" type="Dictionary" setter="" getter=""> + <member name="input/ui_text_delete_word.osx" type="Dictionary" setter="" getter=""> </member> <member name="input/ui_text_indent" type="Dictionary" setter="" getter=""> </member> @@ -695,11 +698,11 @@ </member> <member name="input/ui_text_scroll_down" type="Dictionary" setter="" getter=""> </member> - <member name="input/ui_text_scroll_down.OSX" type="Dictionary" setter="" getter=""> + <member name="input/ui_text_scroll_down.osx" type="Dictionary" setter="" getter=""> </member> <member name="input/ui_text_scroll_up" type="Dictionary" setter="" getter=""> </member> - <member name="input/ui_text_scroll_up.OSX" type="Dictionary" setter="" getter=""> + <member name="input/ui_text_scroll_up.osx" type="Dictionary" setter="" getter=""> </member> <member name="input/ui_text_select_all" type="Dictionary" setter="" getter=""> </member> @@ -726,7 +729,7 @@ <member name="input_devices/pen_tablet/driver" type="String" setter="" getter=""> Specifies the tablet driver to use. If left empty, the default driver will be used. </member> - <member name="input_devices/pen_tablet/driver.Windows" type="String" setter="" getter=""> + <member name="input_devices/pen_tablet/driver.windows" type="String" setter="" getter=""> Override for [member input_devices/pen_tablet/driver] on Windows. </member> <member name="input_devices/pointing/emulate_mouse_from_touch" type="bool" setter="" getter="" default="true"> diff --git a/doc/classes/RenderingDevice.xml b/doc/classes/RenderingDevice.xml index 24f7f4274e..428fa2575c 100644 --- a/doc/classes/RenderingDevice.xml +++ b/doc/classes/RenderingDevice.xml @@ -359,6 +359,14 @@ <description> </description> </method> + <method name="get_driver_resource"> + <return type="int" /> + <argument index="0" name="resource" type="int" enum="RenderingDevice.DriverResource" /> + <argument index="1" name="rid" type="RID" /> + <argument index="2" name="index" type="int" /> + <description> + </description> + </method> <method name="get_frame_delay" qualifiers="const"> <return type="int" /> <description> @@ -646,6 +654,32 @@ </constant> <constant name="BARRIER_MASK_NO_BARRIER" value="8"> </constant> + <constant name="DRIVER_RESOURCE_VULKAN_DEVICE" value="0" enum="DriverResource"> + </constant> + <constant name="DRIVER_RESOURCE_VULKAN_PHYSICAL_DEVICE" value="1" enum="DriverResource"> + </constant> + <constant name="DRIVER_RESOURCE_VULKAN_INSTANCE" value="2" enum="DriverResource"> + </constant> + <constant name="DRIVER_RESOURCE_VULKAN_QUEUE" value="3" enum="DriverResource"> + </constant> + <constant name="DRIVER_RESOURCE_VULKAN_QUEUE_FAMILY_INDEX" value="4" enum="DriverResource"> + </constant> + <constant name="DRIVER_RESOURCE_VULKAN_IMAGE" value="5" enum="DriverResource"> + </constant> + <constant name="DRIVER_RESOURCE_VULKAN_IMAGE_VIEW" value="6" enum="DriverResource"> + </constant> + <constant name="DRIVER_RESOURCE_VULKAN_IMAGE_NATIVE_TEXTURE_FORMAT" value="7" enum="DriverResource"> + </constant> + <constant name="DRIVER_RESOURCE_VULKAN_SAMPLER" value="8" enum="DriverResource"> + </constant> + <constant name="DRIVER_RESOURCE_VULKAN_DESCRIPTOR_SET" value="9" enum="DriverResource"> + </constant> + <constant name="DRIVER_RESOURCE_VULKAN_BUFFER" value="10" enum="DriverResource"> + </constant> + <constant name="DRIVER_RESOURCE_VULKAN_COMPUTE_PIPELINE" value="11" enum="DriverResource"> + </constant> + <constant name="DRIVER_RESOURCE_VULKAN_RENDER_PIPELINE" value="12" enum="DriverResource"> + </constant> <constant name="DATA_FORMAT_R4G4_UNORM_PACK8" value="0" enum="DataFormat"> </constant> <constant name="DATA_FORMAT_R4G4B4A4_UNORM_PACK16" value="1" enum="DataFormat"> diff --git a/doc/classes/RenderingServer.xml b/doc/classes/RenderingServer.xml index 638c657492..b5be04fb01 100644 --- a/doc/classes/RenderingServer.xml +++ b/doc/classes/RenderingServer.xml @@ -1156,6 +1156,11 @@ <description> </description> </method> + <method name="get_rendering_device" qualifiers="const"> + <return type="RenderingDevice" /> + <description> + </description> + </method> <method name="get_rendering_info"> <return type="int" /> <argument index="0" name="info" type="int" enum="RenderingServer.RenderingInfo" /> @@ -3796,21 +3801,18 @@ <constant name="VIEWPORT_SDF_SCALE_MAX" value="3" enum="ViewportSDFScale"> </constant> <constant name="VIEWPORT_MSAA_DISABLED" value="0" enum="ViewportMSAA"> - Multisample antialiasing is disabled. + Multisample antialiasing for 3D is disabled. This is the default value, and also the fastest setting. </constant> <constant name="VIEWPORT_MSAA_2X" value="1" enum="ViewportMSAA"> - Multisample antialiasing uses 2 samples per pixel. + Multisample antialiasing uses 2 samples per pixel for 3D. This has a moderate impact on performance. </constant> <constant name="VIEWPORT_MSAA_4X" value="2" enum="ViewportMSAA"> - Multisample antialiasing uses 4 samples per pixel. + Multisample antialiasing uses 4 samples per pixel for 3D. This has a high impact on performance. </constant> <constant name="VIEWPORT_MSAA_8X" value="3" enum="ViewportMSAA"> - Multisample antialiasing uses 8 samples per pixel. - </constant> - <constant name="VIEWPORT_MSAA_16X" value="4" enum="ViewportMSAA"> - Multisample antialiasing uses 16 samples per pixel. + Multisample antialiasing uses 8 samples per pixel for 3D. This has a very high impact on performance. Likely unsupported on low-end and older hardware. </constant> - <constant name="VIEWPORT_MSAA_MAX" value="5" enum="ViewportMSAA"> + <constant name="VIEWPORT_MSAA_MAX" value="4" enum="ViewportMSAA"> </constant> <constant name="VIEWPORT_SCREEN_SPACE_AA_DISABLED" value="0" enum="ViewportScreenSpaceAA"> </constant> diff --git a/doc/classes/RigidBody2D.xml b/doc/classes/RigidBody2D.xml index db16552db3..0702955495 100644 --- a/doc/classes/RigidBody2D.xml +++ b/doc/classes/RigidBody2D.xml @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="RigidBody2D" inherits="PhysicsBody2D" version="4.0"> <brief_description> - A body that is controlled by the 2D physics engine. + Physics Body which is moved by 2D physics simulation. Useful for objects that have gravity and can be pushed by other objects. </brief_description> <description> This node implements simulated 2D physics. You do not control a RigidBody2D directly. Instead, you apply forces to it (gravity, impulses, etc.) and the physics simulation calculates the resulting movement based on its mass, friction, and other physical properties. @@ -99,6 +99,13 @@ <member name="can_sleep" type="bool" setter="set_can_sleep" getter="is_able_to_sleep" default="true"> If [code]true[/code], the body can enter sleep mode when there is no movement. See [member sleeping]. </member> + <member name="center_of_mass" type="Vector2" setter="set_center_of_mass" getter="get_center_of_mass" default="Vector2(0, 0)"> + The body's custom center of mass, relative to the body's origin position, when [member center_of_mass_mode] is set to [constant CENTER_OF_MASS_MODE_CUSTOM]. This is the balanced point of the body, where applied forces only cause linear acceleration. Applying forces outside of the center of mass causes angular acceleration. + When [member center_of_mass_mode] is set to [constant CENTER_OF_MASS_MODE_AUTO] (default value), the center of mass is automatically computed. + </member> + <member name="center_of_mass_mode" type="int" setter="set_center_of_mass_mode" getter="get_center_of_mass_mode" enum="RigidBody2D.CenterOfMassMode" default="0"> + Defines the way the body's center of mass is set. See [enum CenterOfMassMode] for possible values. + </member> <member name="contact_monitor" type="bool" setter="set_contact_monitor" getter="is_contact_monitor_enabled" default="false"> If [code]true[/code], the body will emit signals when it collides with another RigidBody2D. See also [member contacts_reported]. </member> @@ -116,8 +123,9 @@ <member name="gravity_scale" type="float" setter="set_gravity_scale" getter="get_gravity_scale" default="1.0"> Multiplies the gravity applied to the body. The body's gravity is calculated from the [b]Default Gravity[/b] value in [b]Project > Project Settings > Physics > 2d[/b] and/or any additional gravity vector applied by [Area2D]s. </member> - <member name="inertia" type="float" setter="set_inertia" getter="get_inertia"> - The body's moment of inertia. This is like mass, but for rotation: it determines how much torque it takes to rotate the body. The moment of inertia is usually computed automatically from the mass and the shapes, but this function allows you to set a custom value. Set 0 inertia to return to automatically computing it. + <member name="inertia" type="float" setter="set_inertia" getter="get_inertia" default="0.0"> + The body's moment of inertia. This is like mass, but for rotation: it determines how much torque it takes to rotate the body. The moment of inertia is usually computed automatically from the mass and the shapes, but this property allows you to set a custom value. + If set to [code]0[/code], inertia is automatically computed (default value). </member> <member name="linear_damp" type="float" setter="set_linear_damp" getter="get_linear_damp" default="-1.0"> Damps the body's [member linear_velocity]. If [code]-1[/code], the body will use the [b]Default Linear Damp[/b] in [b]Project > Project Settings > Physics > 2d[/b]. @@ -131,6 +139,7 @@ </member> <member name="mode" type="int" setter="set_mode" getter="get_mode" enum="RigidBody2D.Mode" default="0"> The body's mode. See [enum Mode] for possible values. + For a body that uses only Static or Kinematic mode, use [StaticBody2D] or [AnimatableBody2D] instead. </member> <member name="physics_material_override" type="PhysicsMaterial" setter="set_physics_material_override" getter="get_physics_material_override"> The physics material override for the body. @@ -199,7 +208,13 @@ Locked dynamic body mode. Similar to [constant MODE_DYNAMIC], but the body can not rotate. </constant> <constant name="MODE_KINEMATIC" value="3" enum="Mode"> - Kinematic body mode. The body behaves like a [StaticBody2D] with [member StaticBody2D.kinematic_motion] enabled, and must be moved by user code. + Kinematic body mode. The body behaves like a [AnimatableBody2D], and must be moved by code. + </constant> + <constant name="CENTER_OF_MASS_MODE_AUTO" value="0" enum="CenterOfMassMode"> + In this mode, the body's center of mass is calculated automatically based on its shapes. + </constant> + <constant name="CENTER_OF_MASS_MODE_CUSTOM" value="1" enum="CenterOfMassMode"> + In this mode, the body's center of mass is set through [member center_of_mass]. Defaults to the body's origin position. </constant> <constant name="CCD_MODE_DISABLED" value="0" enum="CCDMode"> Continuous collision detection disabled. This is the fastest way to detect body collisions, but can miss small, fast-moving objects. diff --git a/doc/classes/RigidBody3D.xml b/doc/classes/RigidBody3D.xml index f4299335bf..1be35b0576 100644 --- a/doc/classes/RigidBody3D.xml +++ b/doc/classes/RigidBody3D.xml @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="RigidBody3D" inherits="PhysicsBody3D" version="4.0"> <brief_description> - Physics Body whose position is determined through physics simulation in 3D space. + Physics Body which is moved by 3D physics simulation. Useful for objects that have gravity and can be pushed by other objects. </brief_description> <description> This is the node that implements full 3D physics. This means that you do not control a RigidBody3D directly. Instead, you can apply forces to it (gravity, impulses, etc.), and the physics simulation will calculate the resulting movement, collision, bouncing, rotating, etc. @@ -102,6 +102,13 @@ <member name="can_sleep" type="bool" setter="set_can_sleep" getter="is_able_to_sleep" default="true"> If [code]true[/code], the body can enter sleep mode when there is no movement. See [member sleeping]. </member> + <member name="center_of_mass" type="Vector3" setter="set_center_of_mass" getter="get_center_of_mass" default="Vector3(0, 0, 0)"> + The body's custom center of mass, relative to the body's origin position, when [member center_of_mass_mode] is set to [constant CENTER_OF_MASS_MODE_CUSTOM]. This is the balanced point of the body, where applied forces only cause linear acceleration. Applying forces outside of the center of mass causes angular acceleration. + When [member center_of_mass_mode] is set to [constant CENTER_OF_MASS_MODE_AUTO] (default value), the center of mass is automatically computed. + </member> + <member name="center_of_mass_mode" type="int" setter="set_center_of_mass_mode" getter="get_center_of_mass_mode" enum="RigidBody3D.CenterOfMassMode" default="0"> + Defines the way the body's center of mass is set. See [enum CenterOfMassMode] for possible values. + </member> <member name="contact_monitor" type="bool" setter="set_contact_monitor" getter="is_contact_monitor_enabled" default="false"> If [code]true[/code], the RigidBody3D will emit signals when it collides with another RigidBody3D. See also [member contacts_reported]. </member> @@ -119,6 +126,10 @@ <member name="gravity_scale" type="float" setter="set_gravity_scale" getter="get_gravity_scale" default="1.0"> This is multiplied by the global 3D gravity setting found in [b]Project > Project Settings > Physics > 3d[/b] to produce RigidBody3D's gravity. For example, a value of 1 will be normal gravity, 2 will apply double gravity, and 0.5 will apply half gravity to this object. </member> + <member name="inertia" type="Vector3" setter="set_inertia" getter="get_inertia" default="Vector3(0, 0, 0)"> + The body's moment of inertia. This is like mass, but for rotation: it determines how much torque it takes to rotate the body on each axis. The moment of inertia is usually computed automatically from the mass and the shapes, but this property allows you to set a custom value. + If set to [code]Vector3.ZERO[/code], inertia is automatically computed (default value). + </member> <member name="linear_damp" type="float" setter="set_linear_damp" getter="get_linear_damp" default="-1.0"> The body's linear damp. Cannot be less than -1.0. If this value is different from -1.0, any linear damp derived from the world or areas will be overridden. See [member ProjectSettings.physics/3d/default_linear_damp] for more details about damping. @@ -130,7 +141,8 @@ The body's mass. </member> <member name="mode" type="int" setter="set_mode" getter="get_mode" enum="RigidBody3D.Mode" default="0"> - The body mode. See [enum Mode] for possible values. + The body's mode. See [enum Mode] for possible values. + For a body that uses only Static or Kinematic mode, use [StaticBody3D] or [AnimatableBody3D] instead. </member> <member name="physics_material_override" type="PhysicsMaterial" setter="set_physics_material_override" getter="get_physics_material_override"> The physics material override for the body. @@ -201,7 +213,13 @@ Locked dynamic body mode. Similar to [constant MODE_DYNAMIC], but the body can not rotate. </constant> <constant name="MODE_KINEMATIC" value="3" enum="Mode"> - Kinematic body mode. The body behaves like a [StaticBody3D] with [member StaticBody3D.kinematic_motion] enabled, and can only move by user code. + Kinematic body mode. The body behaves like a [AnimatableBody3D], and can only move by user code. + </constant> + <constant name="CENTER_OF_MASS_MODE_AUTO" value="0" enum="CenterOfMassMode"> + In this mode, the body's center of mass is calculated automatically based on its shapes. + </constant> + <constant name="CENTER_OF_MASS_MODE_CUSTOM" value="1" enum="CenterOfMassMode"> + In this mode, the body's center of mass is set through [member center_of_mass]. Defaults to the body's origin position. </constant> </constants> </class> diff --git a/doc/classes/SceneTree.xml b/doc/classes/SceneTree.xml index 59e3190213..e81eff35ac 100644 --- a/doc/classes/SceneTree.xml +++ b/doc/classes/SceneTree.xml @@ -215,7 +215,7 @@ </member> <member name="multiplayer_poll" type="bool" setter="set_multiplayer_poll_enabled" getter="is_multiplayer_poll_enabled" default="true"> If [code]true[/code] (default value), enables automatic polling of the [MultiplayerAPI] for this SceneTree during [signal process_frame]. - If [code]false[/code], you need to manually call [method MultiplayerAPI.poll] to process network packets and deliver RPCs/RSETs. This allows running RPCs/RSETs in a different loop (e.g. physics, thread, specific time step) and for manual [Mutex] protection when accessing the [MultiplayerAPI] from threads. + If [code]false[/code], you need to manually call [method MultiplayerAPI.poll] to process network packets and deliver RPCs. This allows running RPCs in a different loop (e.g. physics, thread, specific time step) and for manual [Mutex] protection when accessing the [MultiplayerAPI] from threads. </member> <member name="paused" type="bool" setter="set_pause" getter="is_paused" default="false"> If [code]true[/code], the [SceneTree] is paused. Doing so will have the following behavior: diff --git a/doc/classes/StaticBody2D.xml b/doc/classes/StaticBody2D.xml index 326bf58e22..0344c3e0d1 100644 --- a/doc/classes/StaticBody2D.xml +++ b/doc/classes/StaticBody2D.xml @@ -1,14 +1,14 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="StaticBody2D" inherits="PhysicsBody2D" version="4.0"> <brief_description> - Static body for 2D physics. + Physics body for 2D physics which is static or moves only by script. Useful for floor and walls. </brief_description> <description> - Static body for 2D physics. A static body is a simple body that can't be moved by external forces or contacts. It is ideal for implementing objects in the environment, such as walls or platforms. In contrast to [RigidBody2D], they don't consume any CPU resources as long as they don't move. - They however have extra functionalities to move and affect other bodies: - [b]Constant velocity:[/b] [member constant_linear_velocity] and [member constant_angular_velocity] can be set for the static body, so even if it doesn't move, it affects other bodies as if it was moving (this is useful for simulating conveyor belts or conveyor wheels). - [b]Transform change:[/b] Static bodies can be also moved by code. Unless [member kinematic_motion] is enabled, they are just teleported in this case and don't affect other bodies on their path. - [b]Kinematic motion:[/b] Static bodies can have [member kinematic_motion] enabled to make them kinematic bodies that can be moved by code and push other bodies on their path. + Static body for 2D physics. + A static body is a simple body that can't be moved by external forces or contacts. It is ideal for implementing objects in the environment, such as walls or platforms. In contrast to [RigidBody2D], it doesn't consume any CPU resources as long as they don't move. + They have extra functionalities to move and affect other bodies: + [b]Static transform change:[/b] Static bodies can be moved by animation or script. In this case, they are just teleported and don't affect other bodies on their path. + [b]Constant velocity:[/b] When [member constant_linear_velocity] or [member constant_angular_velocity] is set, static bodies don't move themselves but affect touching bodies as if they were moving. This is useful for simulating conveyor belts or conveyor wheels. </description> <tutorials> </tutorials> @@ -16,22 +16,15 @@ </methods> <members> <member name="constant_angular_velocity" type="float" setter="set_constant_angular_velocity" getter="get_constant_angular_velocity" default="0.0"> - The body's constant angular velocity. This does not rotate the body (unless [member kinematic_motion] is enabled), but affects other bodies that touch it, as if it were rotating. + The body's constant angular velocity. This does not rotate the body, but affects touching bodies, as if it were rotating. </member> <member name="constant_linear_velocity" type="Vector2" setter="set_constant_linear_velocity" getter="get_constant_linear_velocity" default="Vector2(0, 0)"> - The body's constant linear velocity. This does not move the body (unless [member kinematic_motion] is enabled), but affects other bodies that touch it, as if it were moving. - </member> - <member name="kinematic_motion" type="bool" setter="set_kinematic_motion_enabled" getter="is_kinematic_motion_enabled" default="false"> - If [code]true[/code], the body will act the same as a [RigidBody2D] in [constant RigidBody2D.MODE_KINEMATIC] mode. - When the body is moved manually, either from code or from an [AnimationPlayer] (with [member AnimationPlayer.playback_process_mode] set to [code]physics[/code]), the physics will automatically compute an estimate of their linear and angular velocity. This makes them very useful for moving platforms or other AnimationPlayer-controlled objects (like a door, a bridge that opens, etc). + The body's constant linear velocity. This does not move the body, but affects touching bodies, as if it were moving. </member> <member name="physics_material_override" type="PhysicsMaterial" setter="set_physics_material_override" getter="get_physics_material_override"> The physics material override for the body. If a material is assigned to this property, it will be used instead of any other physics material, such as an inherited one. </member> - <member name="sync_to_physics" type="bool" setter="set_sync_to_physics" getter="is_sync_to_physics_enabled" default="false"> - If [code]true[/code] and [member kinematic_motion] is enabled, the body's movement will be synchronized to the physics frame. This is useful when animating movement via [AnimationPlayer], for example on moving platforms. Do [b]not[/b] use together with [method PhysicsBody2D.move_and_collide]. - </member> </members> <constants> </constants> diff --git a/doc/classes/StaticBody3D.xml b/doc/classes/StaticBody3D.xml index 69c123002f..4cb51b60ec 100644 --- a/doc/classes/StaticBody3D.xml +++ b/doc/classes/StaticBody3D.xml @@ -1,14 +1,14 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="StaticBody3D" inherits="PhysicsBody3D" version="4.0"> <brief_description> - Static body for 3D physics. + Physics body for 3D physics which is static or moves only by script. Useful for floor and walls. </brief_description> <description> - Static body for 3D physics. A static body is a simple body that can't be moved by external forces or contacts. It is ideal for implementing objects in the environment, such as walls or platforms. In contrast to [RigidBody3D], they don't consume any CPU resources as long as they don't move. - They however have extra functionalities to move and affect other bodies: - [b]Constant velocity:[/b] [member constant_linear_velocity] and [member constant_angular_velocity] can be set for the static body, so even if it doesn't move, it affects other bodies as if it was moving (this is useful for simulating conveyor belts or conveyor wheels). - [b]Transform change:[/b] Static bodies can be also moved by code. Unless [member kinematic_motion] is enabled, they are just teleported in this case and don't affect other bodies on their path. - [b]Kinematic motion:[/b] Static bodies can have [member kinematic_motion] enabled to make them kinematic bodies that can be moved by code and push other bodies on their path. + Static body for 3D physics. + A static body is a simple body that can't be moved by external forces or contacts. It is ideal for implementing objects in the environment, such as walls or platforms. In contrast to [RigidBody3D], it doesn't consume any CPU resources as long as they don't move. + They have extra functionalities to move and affect other bodies: + [b]Static transform change:[/b] Static bodies can be moved by animation or script. In this case, they are just teleported and don't affect other bodies on their path. + [b]Constant velocity:[/b] When [member constant_linear_velocity] or [member constant_angular_velocity] is set, static bodies don't move themselves but affect touching bodies as if they were moving. This is useful for simulating conveyor belts or conveyor wheels. </description> <tutorials> <link title="3D Physics Tests Demo">https://godotengine.org/asset-library/asset/675</link> @@ -19,22 +19,15 @@ </methods> <members> <member name="constant_angular_velocity" type="Vector3" setter="set_constant_angular_velocity" getter="get_constant_angular_velocity" default="Vector3(0, 0, 0)"> - The body's constant angular velocity. This does not rotate the body (unless [member kinematic_motion] is enabled), but affects other bodies that touch it, as if it were rotating. + The body's constant angular velocity. This does not rotate the body, but affects touching bodies, as if it were rotating. </member> <member name="constant_linear_velocity" type="Vector3" setter="set_constant_linear_velocity" getter="get_constant_linear_velocity" default="Vector3(0, 0, 0)"> - The body's constant linear velocity. This does not move the body (unless [member kinematic_motion] is enabled), but affects other bodies that touch it, as if it were moving. - </member> - <member name="kinematic_motion" type="bool" setter="set_kinematic_motion_enabled" getter="is_kinematic_motion_enabled" default="false"> - If [code]true[/code], the body will act the same as a [RigidBody3D] in [constant RigidBody3D.MODE_KINEMATIC] mode. - When the body is moved manually, either from code or from an [AnimationPlayer] (with [member AnimationPlayer.playback_process_mode] set to [code]physics[/code]), the physics will automatically compute an estimate of their linear and angular velocity. This makes them very useful for moving platforms or other AnimationPlayer-controlled objects (like a door, a bridge that opens, etc). + The body's constant linear velocity. This does not move the body, but affects touching bodies, as if it were moving. </member> <member name="physics_material_override" type="PhysicsMaterial" setter="set_physics_material_override" getter="get_physics_material_override"> The physics material override for the body. If a material is assigned to this property, it will be used instead of any other physics material, such as an inherited one. </member> - <member name="sync_to_physics" type="bool" setter="set_sync_to_physics" getter="is_sync_to_physics_enabled" default="false"> - If [code]true[/code] and [member kinematic_motion] is enabled, the body's movement will be synchronized to the physics frame. This is useful when animating movement via [AnimationPlayer], for example on moving platforms. Do [b]not[/b] use together with [method PhysicsBody3D.move_and_collide]. - </member> </members> <constants> </constants> diff --git a/doc/classes/TabContainer.xml b/doc/classes/TabContainer.xml index fbda005865..77bd7b1a0a 100644 --- a/doc/classes/TabContainer.xml +++ b/doc/classes/TabContainer.xml @@ -57,6 +57,13 @@ Returns the [Texture2D] for the tab at index [code]tab_idx[/code] or [code]null[/code] if the tab has no [Texture2D]. </description> </method> + <method name="get_tab_idx_at_point" qualifiers="const"> + <return type="int" /> + <argument index="0" name="point" type="Vector2" /> + <description> + Returns the index of the tab at local coordinates [code]point[/code]. Returns [code]-1[/code] if the point is outside the control boundaries or if there's no tab at the queried position. + </description> + </method> <method name="get_tab_title" qualifiers="const"> <return type="String" /> <argument index="0" name="tab_idx" type="int" /> diff --git a/doc/classes/Thread.xml b/doc/classes/Thread.xml index b553aad518..9c9119c664 100644 --- a/doc/classes/Thread.xml +++ b/doc/classes/Thread.xml @@ -40,6 +40,8 @@ <return type="Variant" /> <description> Joins the [Thread] and waits for it to finish. Returns what the method called returned. + Should either be used when you want to retrieve the value returned from the method called by the [Thread] or before freeing the instance that contains the [Thread]. + [b]Note:[/b] After the [Thread] finishes joining it will be disposed. If you want to use it again you will have to create a new instance of it. </description> </method> </methods> diff --git a/doc/classes/TileData.xml b/doc/classes/TileData.xml index b5031cfc63..b18ca29a8c 100644 --- a/doc/classes/TileData.xml +++ b/doc/classes/TileData.xml @@ -11,6 +11,7 @@ <return type="void" /> <argument index="0" name="layer_id" type="int" /> <description> + Adds a collision polygon to the tile on the given TileSet physics layer. </description> </method> <method name="get_collision_polygon_one_way_margin" qualifiers="const"> @@ -18,6 +19,7 @@ <argument index="0" name="layer_id" type="int" /> <argument index="1" name="polygon_index" type="int" /> <description> + Returns the one-way margin (for one-way platforms) of the polygon at index [code]polygon_index[/code] for TileSet physics layer with index [code]layer_id[/code]. </description> </method> <method name="get_collision_polygon_points" qualifiers="const"> @@ -25,42 +27,49 @@ <argument index="0" name="layer_id" type="int" /> <argument index="1" name="polygon_index" type="int" /> <description> + Returns the points of the polygon at index [code]polygon_index[/code] for TileSet physics layer with index [code]layer_id[/code]. </description> </method> <method name="get_collision_polygons_count" qualifiers="const"> <return type="int" /> <argument index="0" name="layer_id" type="int" /> <description> + Returns how many polygons the tile has for TileSet physics layer with index [code]layer_id[/code]. </description> </method> <method name="get_custom_data" qualifiers="const"> <return type="Variant" /> <argument index="0" name="layer_name" type="String" /> <description> + Returns the custom data value for custom data layer named [code]layer_name[/code]. </description> </method> <method name="get_custom_data_by_layer_id" qualifiers="const"> <return type="Variant" /> <argument index="0" name="layer_id" type="int" /> <description> + Returns the custom data value for custom data layer with index [code]layer_id[/code]. </description> </method> <method name="get_navigation_polygon" qualifiers="const"> <return type="NavigationPolygon" /> <argument index="0" name="layer_id" type="int" /> <description> + Returns the navigation polygon of the tile for the TileSet navigation layer with index [code]layer_id[/code]. </description> </method> <method name="get_occluder" qualifiers="const"> <return type="OccluderPolygon2D" /> <argument index="0" name="layer_id" type="int" /> <description> + Returns the occluder polygon of the tile for the TileSet occlusion layer with index [code]layer_id[/code]. </description> </method> <method name="get_peering_bit_terrain" qualifiers="const"> <return type="int" /> <argument index="0" name="peering_bit" type="int" enum="TileSet.CellNeighbor" /> <description> + Returns the tile's terrain bit for the given [code]peering_bit[/code] direction. </description> </method> <method name="is_collision_polygon_one_way" qualifiers="const"> @@ -68,6 +77,7 @@ <argument index="0" name="layer_id" type="int" /> <argument index="1" name="polygon_index" type="int" /> <description> + Returns whether one-way collisions are enabled for the polygon at index [code]polygon_index[/code] for TileSet physics layer with index [code]layer_id[/code]. </description> </method> <method name="remove_collision_polygon"> @@ -75,6 +85,7 @@ <argument index="0" name="layer_id" type="int" /> <argument index="1" name="polygon_index" type="int" /> <description> + Removes the polygon at index [code]polygon_index[/code] for TileSet physics layer with index [code]layer_id[/code]. </description> </method> <method name="set_collision_polygon_one_way"> @@ -83,6 +94,7 @@ <argument index="1" name="polygon_index" type="int" /> <argument index="2" name="one_way" type="bool" /> <description> + Enables/disables one-way collisions on the polygon at index [code]polygon_index[/code] for TileSet physics layer with index [code]layer_id[/code]. </description> </method> <method name="set_collision_polygon_one_way_margin"> @@ -91,6 +103,7 @@ <argument index="1" name="polygon_index" type="int" /> <argument index="2" name="one_way_margin" type="float" /> <description> + Enables/disables one-way collisions on the polygon at index [code]polygon_index[/code] for TileSet physics layer with index [code]layer_id[/code]. </description> </method> <method name="set_collision_polygon_points"> @@ -99,6 +112,7 @@ <argument index="1" name="polygon_index" type="int" /> <argument index="2" name="polygon" type="PackedVector2Array" /> <description> + Sets the points of the polygon at index [code]polygon_index[/code] for TileSet physics layer with index [code]layer_id[/code]. </description> </method> <method name="set_collision_polygons_count"> @@ -106,6 +120,7 @@ <argument index="0" name="layer_id" type="int" /> <argument index="1" name="polygons_count" type="int" /> <description> + Sets the polygons count for TileSet physics layer with index [code]layer_id[/code]. </description> </method> <method name="set_custom_data"> @@ -113,6 +128,7 @@ <argument index="0" name="layer_name" type="String" /> <argument index="1" name="value" type="Variant" /> <description> + Sets the tile's custom data value for the TileSet custom data layer with name [code]layer_name[/code]. </description> </method> <method name="set_custom_data_by_layer_id"> @@ -120,6 +136,7 @@ <argument index="0" name="layer_id" type="int" /> <argument index="1" name="value" type="Variant" /> <description> + Sets the tile's custom data value for the TileSet custom data layer with index [code]layer_id[/code]. </description> </method> <method name="set_navigation_polygon"> @@ -127,6 +144,7 @@ <argument index="0" name="layer_id" type="int" /> <argument index="1" name="navigation_polygon" type="NavigationPolygon" /> <description> + Sets the navigation polygon for the TileSet navigation layer with index [code]layer_id[/code]. </description> </method> <method name="set_occluder"> @@ -134,6 +152,7 @@ <argument index="0" name="layer_id" type="int" /> <argument index="1" name="occluder_polygon" type="OccluderPolygon2D" /> <description> + Sets the occluder for the TileSet occlusion layer with index [code]layer_id[/code]. </description> </method> <method name="set_peering_bit_terrain"> @@ -141,17 +160,7 @@ <argument index="0" name="peering_bit" type="int" enum="TileSet.CellNeighbor" /> <argument index="1" name="terrain" type="int" /> <description> - </description> - </method> - <method name="tile_get_material" qualifiers="const"> - <return type="ShaderMaterial" /> - <description> - </description> - </method> - <method name="tile_set_material"> - <return type="void" /> - <argument index="0" name="material" type="ShaderMaterial" /> - <description> + Sets the tile's terrain bit for the given [code]peering_bit[/code] direction. </description> </method> </methods> @@ -160,6 +169,8 @@ </member> <member name="flip_v" type="bool" setter="set_flip_v" getter="get_flip_v" default="false"> </member> + <member name="material" type="ShaderMaterial" setter="set_material" getter="get_material"> + </member> <member name="modulate" type="Color" setter="set_modulate" getter="get_modulate" default="Color(1, 1, 1, 1)"> </member> <member name="probability" type="float" setter="set_probability" getter="get_probability" default="1.0"> diff --git a/doc/classes/TileMap.xml b/doc/classes/TileMap.xml index f3c64c3c7d..4621d138ac 100644 --- a/doc/classes/TileMap.xml +++ b/doc/classes/TileMap.xml @@ -4,8 +4,7 @@ Node for 2D tile-based maps. </brief_description> <description> - Node for 2D tile-based maps. Tilemaps use a [TileSet] which contain a list of tiles (textures plus optional collision, navigation, and/or occluder shapes) which are used to create grid-based maps. - When doing physics queries against the tilemap, the cell coordinates are encoded as [code]metadata[/code] for each detected collision shape returned by methods such as [method PhysicsDirectSpaceState2D.intersect_shape], [method PhysicsDirectBodyState2D.get_contact_collider_shape_metadata] etc. + Node for 2D tile-based maps. Tilemaps use a [TileSet] which contain a list of tiles which are used to create grid-based maps. A TileMap may have several layers, layouting tiles on top of each other. </description> <tutorials> <link title="Using Tilemaps">https://docs.godotengine.org/en/latest/tutorials/2d/using_tilemaps.html</link> @@ -17,6 +16,13 @@ <link title="2D Kinematic Character Demo">https://godotengine.org/asset-library/asset/113</link> </tutorials> <methods> + <method name="add_layer"> + <return type="void" /> + <argument index="0" name="to_position" type="int" /> + <description> + Adds a layer at the given position [code]to_position[/code] in the array. If [code]to_position[/code] is -1, adds it at the end of the array. + </description> + </method> <method name="clear"> <return type="void" /> <description> @@ -35,6 +41,7 @@ <argument index="1" name="coords" type="Vector2i" /> <argument index="2" name="use_proxies" type="bool" /> <description> + Returns the tile alternative ID of the cell on layer [code]layer[/code] at [code]coords[/code]. If [code]use_proxies[/code] is [code]false[/code], ignores the [TileSet]'s tile proxies, returning the raw alternative identifier. See [method TileSet.map_tile_proxy]. </description> </method> <method name="get_cell_atlas_coords" qualifiers="const"> @@ -43,6 +50,7 @@ <argument index="1" name="coords" type="Vector2i" /> <argument index="2" name="use_proxies" type="bool" /> <description> + Returns the tile atlas coordinates ID of the cell on layer [code]layer[/code] at coordinates [code]coords[/code]. If [code]use_proxies[/code] is [code]false[/code], ignores the [TileSet]'s tile proxies, returning the raw alternative identifier. See [method TileSet.map_tile_proxy]. </description> </method> <method name="get_cell_source_id" qualifiers="const"> @@ -51,24 +59,33 @@ <argument index="1" name="coords" type="Vector2i" /> <argument index="2" name="use_proxies" type="bool" /> <description> + Returns the tile source ID of the cell on layer [code]layer[/code] at coordinates [code]coords[/code]. If [code]use_proxies[/code] is [code]false[/code], ignores the [TileSet]'s tile proxies, returning the raw alternative identifier. See [method TileSet.map_tile_proxy]. </description> </method> <method name="get_layer_name" qualifiers="const"> <return type="String" /> <argument index="0" name="layer" type="int" /> <description> + Returns a TileMap layer's name. </description> </method> <method name="get_layer_y_sort_origin" qualifiers="const"> <return type="int" /> <argument index="0" name="layer" type="int" /> <description> + Returns a TileMap layer's Y sort origin. </description> </method> - <method name="get_layer_z_indexd" qualifiers="const"> + <method name="get_layer_z_index" qualifiers="const"> <return type="int" /> <argument index="0" name="layer" type="int" /> <description> + Returns a TileMap layer's Z-index value. + </description> + </method> + <method name="get_layers_count" qualifiers="const"> + <return type="int" /> + <description> </description> </method> <method name="get_neighbor_cell" qualifiers="const"> @@ -76,37 +93,41 @@ <argument index="0" name="coords" type="Vector2i" /> <argument index="1" name="neighbor" type="int" enum="TileSet.CellNeighbor" /> <description> + Returns the neighboring cell to the one at coordinates [code]coords[/code], indentified by the [code]neighbor[/code] direction. This method takes into account the different layouts a TileMap can take. </description> </method> <method name="get_surrounding_tiles"> <return type="Vector2i[]" /> <argument index="0" name="coords" type="Vector2i" /> <description> + Returns the list of all neighbourings cells to the one at [code]coords[/code] </description> </method> <method name="get_used_cells" qualifiers="const"> <return type="Vector2i[]" /> <argument index="0" name="layer" type="int" /> <description> - Returns a [Vector2] array with the positions of all cells containing a tile from the tileset (i.e. a tile index different from [code]-1[/code]). + Returns a [Vector2] array with the positions of all cells containing a tile in the given layer. A cell is considered empty if its source identifier equals -1, its atlas coordinates identifiers is [code]Vector2(-1, -1)[/code] and its alternative identifier is -1. </description> </method> <method name="get_used_rect"> <return type="Rect2" /> <description> - Returns a rectangle enclosing the used (non-empty) tiles of the map. + Returns a rectangle enclosing the used (non-empty) tiles of the map, including all layers. </description> </method> <method name="is_layer_enabled" qualifiers="const"> <return type="bool" /> <argument index="0" name="layer" type="int" /> <description> + Returns if a layer is enabled. </description> </method> <method name="is_layer_y_sort_enabled" qualifiers="const"> <return type="bool" /> <argument index="0" name="layer" type="int" /> <description> + Returns if a layer Y-sorts its tiles. </description> </method> <method name="map_to_world" qualifiers="const"> @@ -116,6 +137,21 @@ Returns the local position corresponding to the given tilemap (grid-based) coordinates. </description> </method> + <method name="move_layer"> + <return type="void" /> + <argument index="0" name="layer" type="int" /> + <argument index="1" name="to_position" type="int" /> + <description> + Moves the layer at index [code]layer_index[/code] to the given position [code]to_position[/code] in the array. + </description> + </method> + <method name="remove_layer"> + <return type="void" /> + <argument index="0" name="layer" type="int" /> + <description> + Moves the layer at index [code]layer_index[/code] to the given position [code]to_position[/code] in the array. + </description> + </method> <method name="set_cell"> <return type="void" /> <argument index="0" name="layer" type="int" /> @@ -124,7 +160,10 @@ <argument index="3" name="atlas_coords" type="Vector2i" default="Vector2i(-1, -1)" /> <argument index="4" name="alternative_tile" type="int" default="-1" /> <description> - Sets the tile index for the cell given by a Vector2i. + Sets the tile indentifiers for the cell on layer [code]layer[/code] at coordinates [code]coords[/code]. Each tile of the [TileSet] is identified using three parts: + - The source indentifier [code]source_id[/code] identifies a [TileSetSource] identifier. See [method TileSet.set_source_id], + - The atlas coordinates identifier [code]atlas_coords[/code] identifies a tile coordinates in the atlas (if the source is a [TileSetAtlasSource]. For [TileSetScenesCollectionSource] it should be 0), + - The alternative tile identifier [code]alternative_tile[/code] identifies a tile alternative the source is a [TileSetAtlasSource], and the scene for a [TileSetScenesCollectionSource]. </description> </method> <method name="set_layer_enabled"> @@ -132,6 +171,7 @@ <argument index="0" name="layer" type="int" /> <argument index="1" name="enabled" type="bool" /> <description> + Enables or disables the layer [code]layer[/code]. A disabled layer is not processed at all (no rendering, no physics, etc...). </description> </method> <method name="set_layer_name"> @@ -139,6 +179,7 @@ <argument index="0" name="layer" type="int" /> <argument index="1" name="name" type="String" /> <description> + Sets a layer's name. This is mostly useful in the editor. </description> </method> <method name="set_layer_y_sort_enabled"> @@ -146,6 +187,8 @@ <argument index="0" name="layer" type="int" /> <argument index="1" name="y_sort_enabled" type="bool" /> <description> + Enables or disables a layer's Y-sorting. If a layer is Y-sorted, the layer will behave as a CanvasItem node where each of its tile gets Y-sorted. + Y-sorted layers should usually be on different Z-index values than not Y-sorted layers, otherwise, each of those layer will be Y-sorted as whole with the Y-sorted one. This is usually an undesired behvaior. </description> </method> <method name="set_layer_y_sort_origin"> @@ -153,6 +196,8 @@ <argument index="0" name="layer" type="int" /> <argument index="1" name="y_sort_origin" type="int" /> <description> + Sets a layer's Y-sort origin value. This Y-sort origin value is added to each tile's Y-sort origin value. + This allows, for example, to fake a different height level on each layer. This can be useful for top-down view games. </description> </method> <method name="set_layer_z_index"> @@ -160,6 +205,7 @@ <argument index="0" name="layer" type="int" /> <argument index="1" name="z_index" type="int" /> <description> + Sets a layers Z-index value. This Z-index is added to each tile's Z-index value. </description> </method> <method name="world_to_map" qualifiers="const"> @@ -175,10 +221,10 @@ The TileMap's quadrant size. Optimizes drawing by batching, using chunks of this size. </member> <member name="collision_visibility_mode" type="int" setter="set_collision_visibility_mode" getter="get_collision_visibility_mode" enum="TileMap.VisibilityMode" default="0"> - </member> - <member name="layers_count" type="int" setter="set_layers_count" getter="get_layers_count" default="1"> + Show or hide the TileMap's collision shapes. If set to [code]VISIBILITY_MODE_DEFAULT[/code], this depends on the show collision debug settings. </member> <member name="navigation_visibility_mode" type="int" setter="set_navigation_visibility_mode" getter="get_navigation_visibility_mode" enum="TileMap.VisibilityMode" default="0"> + Show or hide the TileMap's collision shapes. If set to [code]VISIBILITY_MODE_DEFAULT[/code], this depends on the show navigation debug settings. </member> <member name="tile_set" type="TileSet" setter="set_tileset" getter="get_tileset"> The assigned [TileSet]. @@ -193,10 +239,13 @@ </signals> <constants> <constant name="VISIBILITY_MODE_DEFAULT" value="0" enum="VisibilityMode"> + Use the debug settings to determine visibility. </constant> <constant name="VISIBILITY_MODE_FORCE_HIDE" value="2" enum="VisibilityMode"> + Always hide. </constant> <constant name="VISIBILITY_MODE_FORCE_SHOW" value="1" enum="VisibilityMode"> + Always show. </constant> </constants> </class> diff --git a/doc/classes/TileSet.xml b/doc/classes/TileSet.xml index 439c6e3830..02baded019 100644 --- a/doc/classes/TileSet.xml +++ b/doc/classes/TileSet.xml @@ -4,8 +4,13 @@ Tile library for tilemaps. </brief_description> <description> - A TileSet is a library of tiles for a [TileMap]. It contains a list of tiles, each consisting of a sprite and optional collision shapes. - Tiles are referenced by a unique integer ID. + A TileSet is a library of tiles for a [TileMap]. A TileSet handles a list of [TileSetSource], each of them storing a set of tiles. + Tiles can either be from a [TileSetAtlasSource], that render tiles out of a texture with support for physics, navigation, etc... or from a [TileSetScenesCollectionSource] which exposes scene-based tiles. + Tiles are referenced by using three IDs: their source ID, their atlas coordinates ID and their alternative tile ID. + + A TileSet can be configured so that its tiles expose more or less properties. To do so, the TileSet resources uses property layers, that you can add or remove depending on your needs. + For example, adding a physics layer allows giving collision shapes to your tiles. Each layer having dedicated properties (physics layer an mask), you may add several TileSet physics layers for each type of collision you need. + See the functions to add new layers for more information. </description> <tutorials> <link title="Using Tilemaps">https://docs.godotengine.org/en/latest/tutorials/2d/using_tilemaps.html</link> @@ -17,21 +22,72 @@ <link title="2D Kinematic Character Demo">https://godotengine.org/asset-library/asset/113</link> </tutorials> <methods> + <method name="add_custom_data_layer"> + <return type="void" /> + <argument index="0" name="to_position" type="int" default="-1" /> + <description> + Adds a custom data layer to the TileSet at the given position [code]to_position[/code] in the array. If [code]to_position[/code] is -1, adds it at the end of the array. + Custom data layers allow assigning custom properties to atlas tiles. + </description> + </method> + <method name="add_navigation_layer"> + <return type="void" /> + <argument index="0" name="to_position" type="int" default="-1" /> + <description> + Adds a navigation layer to the TileSet at the given position [code]to_position[/code] in the array. If [code]to_position[/code] is -1, adds it at the end of the array. + Navigation layers allow assigning a navigable area to atlas tiles. + </description> + </method> + <method name="add_occlusion_layer"> + <return type="void" /> + <argument index="0" name="to_position" type="int" default="-1" /> + <description> + Adds an occlusion layer to the TileSet at the given position [code]to_position[/code] in the array. If [code]to_position[/code] is -1, adds it at the end of the array. + Occlusion layers allow assigning occlusion polygons to atlas tiles. + </description> + </method> + <method name="add_physics_layer"> + <return type="void" /> + <argument index="0" name="to_position" type="int" default="-1" /> + <description> + Adds a physics layer to the TileSet at the given position [code]to_position[/code] in the array. If [code]to_position[/code] is -1, adds it at the end of the array. + Physics layers allow assigning collision polygons to atlas tiles. + </description> + </method> <method name="add_source"> <return type="int" /> - <argument index="0" name="atlas_source_id_override" type="TileSetSource" /> - <argument index="1" name="arg1" type="int" default="-1" /> + <argument index="0" name="source" type="TileSetSource" /> + <argument index="1" name="atlas_source_id_override" type="int" default="-1" /> <description> + Adds a [TileSetSource] to the TileSet. If [code]atlas_source_id_override[/code] is not -1, also set its source ID. Otherwise, a unique identifier is automatically generated. + The function returns the added source source ID or -1 if the source could not be added. + </description> + </method> + <method name="add_terrain"> + <return type="void" /> + <argument index="0" name="terrain_set" type="int" /> + <argument index="1" name="to_position" type="int" default="-1" /> + <description> + Adds a new terrain to the given terrain set [code]terrain_set[/code] at the given position [code]to_position[/code] in the array. If [code]to_position[/code] is -1, adds it at the end of the array. + </description> + </method> + <method name="add_terrain_set"> + <return type="void" /> + <argument index="0" name="to_position" type="int" default="-1" /> + <description> + Adds a new terrain set at the given position [code]to_position[/code] in the array. If [code]to_position[/code] is -1, adds it at the end of the array. </description> </method> <method name="cleanup_invalid_tile_proxies"> <return type="void" /> <description> + Clears tile proxies pointing to invalid tiles. </description> </method> <method name="clear_tile_proxies"> <return type="void" /> <description> + Clears all tile proxies. </description> </method> <method name="get_alternative_level_tile_proxy"> @@ -40,6 +96,8 @@ <argument index="1" name="coords_from" type="Vector2i" /> <argument index="2" name="alternative_from" type="int" /> <description> + Returns the alternative-level proxy for the given identifiers. The returned array contains the three proxie's target identifiers (source ID, atlas coords ID and alternative tile ID). + If the TileSet has no proxy for the given identifiers, returns an empty Array. </description> </method> <method name="get_coords_level_tile_proxy"> @@ -47,70 +105,108 @@ <argument index="0" name="source_from" type="int" /> <argument index="1" name="coords_from" type="Vector2i" /> <description> + Returns the coodinate-level proxy for the given identifiers. The returned array contains the two proxie's target identifiers (source ID and atlas coords ID). + If the TileSet has no proxy for the given identifiers, returns an empty Array. + </description> + </method> + <method name="get_custom_data_layers_count" qualifiers="const"> + <return type="int" /> + <description> + Returns the custom data layers count. </description> </method> <method name="get_navigation_layer_layers" qualifiers="const"> <return type="int" /> <argument index="0" name="layer_index" type="int" /> <description> + Returns the navigation layers (as in the Navigation server) of the gives TileSet navigation layer. + </description> + </method> + <method name="get_navigation_layers_count" qualifiers="const"> + <return type="int" /> + <description> + Returns the navigation layers count. </description> </method> <method name="get_next_source_id" qualifiers="const"> <return type="int" /> <description> + Returns a new unused source ID. This generated ID is the same that a call to [code]add_source[/code] would return. </description> </method> <method name="get_occlusion_layer_light_mask" qualifiers="const"> <return type="int" /> - <argument index="0" name="arg0" type="int" /> + <argument index="0" name="layer_index" type="int" /> <description> + Returns the light mask of the occlusion layer. </description> </method> <method name="get_occlusion_layer_sdf_collision" qualifiers="const"> <return type="bool" /> - <argument index="0" name="arg0" type="int" /> + <argument index="0" name="layer_index" type="int" /> + <description> + Returns if the occluders from this layer use [code]sdf_collision[/code]. + </description> + </method> + <method name="get_occlusion_layers_count" qualifiers="const"> + <return type="int" /> <description> + Returns the occlusion layers count. </description> </method> <method name="get_physics_layer_collision_layer" qualifiers="const"> <return type="int" /> <argument index="0" name="layer_index" type="int" /> <description> + Returns the collision layer (as in the physics server) bodies on the given TileSet's physics layer are in. </description> </method> <method name="get_physics_layer_collision_mask" qualifiers="const"> <return type="int" /> <argument index="0" name="layer_index" type="int" /> <description> + Returns the collision mask of bodies on the given TileSet's physics layer. </description> </method> <method name="get_physics_layer_physics_material" qualifiers="const"> <return type="PhysicsMaterial" /> <argument index="0" name="layer_index" type="int" /> <description> + Returns the physics material of bodies on the given TileSet's physics layer. + </description> + </method> + <method name="get_physics_layers_count" qualifiers="const"> + <return type="int" /> + <description> + Returns the physics layers count. </description> </method> <method name="get_source" qualifiers="const"> <return type="TileSetSource" /> - <argument index="0" name="index" type="int" /> + <argument index="0" name="source_id" type="int" /> <description> + Returns the [TileSetSource] with ID [code]source_id[/code]. </description> </method> <method name="get_source_count" qualifiers="const"> <return type="int" /> <description> + Returns the number of [TileSetSource] in this TileSet. </description> </method> <method name="get_source_id" qualifiers="const"> <return type="int" /> <argument index="0" name="index" type="int" /> <description> + Returns the source ID for source with index [code]index[/code]. </description> </method> <method name="get_source_level_tile_proxy"> <return type="int" /> <argument index="0" name="source_from" type="int" /> <description> + Returns the source-level proxy for the given source identifier. + If the TileSet has no proxy for the given identifier, returns -1. </description> </method> <method name="get_terrain_color" qualifiers="const"> @@ -118,6 +214,7 @@ <argument index="0" name="terrain_set" type="int" /> <argument index="1" name="terrain_index" type="int" /> <description> + Returns a terrain's color. </description> </method> <method name="get_terrain_name" qualifiers="const"> @@ -125,18 +222,27 @@ <argument index="0" name="terrain_set" type="int" /> <argument index="1" name="terrain_index" type="int" /> <description> + Returns a terrain's name. </description> </method> <method name="get_terrain_set_mode" qualifiers="const"> <return type="int" enum="TileSet.TerrainMode" /> <argument index="0" name="terrain_set" type="int" /> <description> + Returns a terrain set mode. + </description> + </method> + <method name="get_terrain_sets_count" qualifiers="const"> + <return type="int" /> + <description> + Returns the terrain sets count. </description> </method> <method name="get_terrains_count" qualifiers="const"> <return type="int" /> <argument index="0" name="terrain_set" type="int" /> <description> + Returns the number of terrains in the given terrain set. </description> </method> <method name="has_alternative_level_tile_proxy"> @@ -145,6 +251,7 @@ <argument index="1" name="coords_from" type="Vector2i" /> <argument index="2" name="alternative_from" type="int" /> <description> + Returns if there is and alternative-level proxy for the given identifiers. </description> </method> <method name="has_coords_level_tile_proxy"> @@ -152,18 +259,21 @@ <argument index="0" name="source_from" type="int" /> <argument index="1" name="coords_from" type="Vector2i" /> <description> + Returns if there is a coodinates-level proxy for the given identifiers. </description> </method> <method name="has_source" qualifiers="const"> <return type="bool" /> - <argument index="0" name="index" type="int" /> + <argument index="0" name="source_id" type="int" /> <description> + Returns if this TileSet has a source for the given source ID. </description> </method> <method name="has_source_level_tile_proxy"> <return type="bool" /> <argument index="0" name="source_from" type="int" /> <description> + Returns if there is a source-level proxy for the given source ID. </description> </method> <method name="map_tile_proxy" qualifiers="const"> @@ -172,6 +282,58 @@ <argument index="1" name="coords_from" type="Vector2i" /> <argument index="2" name="alternative_from" type="int" /> <description> + According to the configured proxies, maps the provided indentifiers to a new set of identifiers. The source ID, atlas coordinates ID and alternative tile ID are returned as a 3 elements Array. + This function first look for matching alternative-level proxies, then coordinates-level proxies, then source-level proxies. + If no proxy corresponding to provided identifiers are found, returns the same values the ones used as arguments. + </description> + </method> + <method name="move_custom_data_layer"> + <return type="void" /> + <argument index="0" name="layer_index" type="int" /> + <argument index="1" name="to_position" type="int" /> + <description> + Moves the custom data layer at index [code]layer_index[/code] to the given position [code]to_position[/code] in the array. Also updates the atlas tiles accordingly. + </description> + </method> + <method name="move_navigation_layer"> + <return type="void" /> + <argument index="0" name="layer_index" type="int" /> + <argument index="1" name="to_position" type="int" /> + <description> + Moves the navigation layer at index [code]layer_index[/code] to the given position [code]to_position[/code] in the array. Also updates the atlas tiles accordingly. + </description> + </method> + <method name="move_occlusion_layer"> + <return type="void" /> + <argument index="0" name="layer_index" type="int" /> + <argument index="1" name="to_position" type="int" /> + <description> + Moves the occlusion layer at index [code]layer_index[/code] to the given position [code]to_position[/code] in the array. Also updates the atlas tiles accordingly. + </description> + </method> + <method name="move_physics_layer"> + <return type="void" /> + <argument index="0" name="layer_index" type="int" /> + <argument index="1" name="to_position" type="int" /> + <description> + Moves the physics layer at index [code]layer_index[/code] to the given position [code]to_position[/code] in the array. Also updates the atlas tiles accordingly. + </description> + </method> + <method name="move_terrain"> + <return type="void" /> + <argument index="0" name="terrain_set" type="int" /> + <argument index="1" name="terrain_index" type="int" /> + <argument index="2" name="to_position" type="int" /> + <description> + Moves the terrain at index [code]terrain_index[/code] for terrain set [code]terrain_set[/code] to the given position [code]to_position[/code] in the array. Also updates the atlas tiles accordingly. + </description> + </method> + <method name="move_terrain_set"> + <return type="void" /> + <argument index="0" name="terrain_set" type="int" /> + <argument index="1" name="to_position" type="int" /> + <description> + Moves the terrain set at index [code]terrain_set[/code] to the given position [code]to_position[/code] in the array. Also updates the atlas tiles accordingly. </description> </method> <method name="remove_alternative_level_tile_proxy"> @@ -180,6 +342,7 @@ <argument index="1" name="coords_from" type="Vector2i" /> <argument index="2" name="alternative_from" type="int" /> <description> + Removes an alternative-level proxy for the given identifiers. </description> </method> <method name="remove_coords_level_tile_proxy"> @@ -187,18 +350,64 @@ <argument index="0" name="source_from" type="int" /> <argument index="1" name="coords_from" type="Vector2i" /> <description> + Removes a coordinates-level proxy for the given identifiers. + </description> + </method> + <method name="remove_custom_data_layer"> + <return type="void" /> + <argument index="0" name="layer_index" type="int" /> + <description> + Removes the custom data layer at index [code]layer_index[/code]. Also updates the atlas tiles accordingly. + </description> + </method> + <method name="remove_navigation_layer"> + <return type="void" /> + <argument index="0" name="layer_index" type="int" /> + <description> + Removes the navigation layer at index [code]layer_index[/code]. Also updates the atlas tiles accordingly. + </description> + </method> + <method name="remove_occlusion_layer"> + <return type="void" /> + <argument index="0" name="layer_index" type="int" /> + <description> + Removes the occlusion layer at index [code]layer_index[/code]. Also updates the atlas tiles accordingly. + </description> + </method> + <method name="remove_physics_layer"> + <return type="void" /> + <argument index="0" name="layer_index" type="int" /> + <description> + Removes the physics layer at index [code]layer_index[/code]. Also updates the atlas tiles accordingly. </description> </method> <method name="remove_source"> <return type="void" /> <argument index="0" name="source_id" type="int" /> <description> + Removes the source with the given source ID. </description> </method> <method name="remove_source_level_tile_proxy"> <return type="void" /> <argument index="0" name="source_from" type="int" /> <description> + Removes a source-level tile proxy. + </description> + </method> + <method name="remove_terrain"> + <return type="void" /> + <argument index="0" name="terrain_set" type="int" /> + <argument index="1" name="terrain_index" type="int" /> + <description> + Removes the terrain at index [code]terrain_index[/code] in the given terrain set [code]terrain_set[/code]. Also updates the atlas tiles accordingly. + </description> + </method> + <method name="remove_terrain_set"> + <return type="void" /> + <argument index="0" name="terrain_set" type="int" /> + <description> + Removes the terrain set at index [code]terrain_set[/code]. Also updates the atlas tiles accordingly. </description> </method> <method name="set_alternative_level_tile_proxy"> @@ -210,6 +419,9 @@ <argument index="4" name="coords_to" type="Vector2i" /> <argument index="5" name="alternative_to" type="int" /> <description> + Create an alternative-level proxy for the given identifiers. A proxy will map set of tile identifiers to another set of identifiers. + This can be used to replace a tile in all TileMaps using this TileSet, as TileMap nodes will find and use the proxy's target tile when one is available. + Proxied tiles can be automatically replaced in TileMap nodes using the editor. </description> </method> <method name="set_coords_level_tile_proxy"> @@ -219,6 +431,9 @@ <argument index="2" name="source_to" type="int" /> <argument index="3" name="coords_to" type="Vector2i" /> <description> + Creates a coordinates-level proxy for the given identifiers. A proxy will map set of tile identifiers to another set of identifiers. The alternative tile ID is kept the same when using coordinates-level proxies. + This can be used to replace a tile in all TileMaps using this TileSet, as TileMap nodes will find and use the proxy's target tile when one is available. + Proxied tiles can be automatically replaced in TileMap nodes using the editor. </description> </method> <method name="set_navigation_layer_layers"> @@ -226,6 +441,7 @@ <argument index="0" name="layer_index" type="int" /> <argument index="1" name="layers" type="int" /> <description> + Sets the navigation layers (as in the navigation server) for navigation regions is the given TileSet navigation layer. </description> </method> <method name="set_occlusion_layer_light_mask"> @@ -233,13 +449,15 @@ <argument index="0" name="layer_index" type="int" /> <argument index="1" name="light_mask" type="int" /> <description> + Sets the occlusion layer (as in the rendering server) for occluders in the given TileSet occlusion layer. </description> </method> <method name="set_occlusion_layer_sdf_collision"> <return type="void" /> <argument index="0" name="layer_index" type="int" /> - <argument index="1" name="sdf_collision" type="int" /> + <argument index="1" name="sdf_collision" type="bool" /> <description> + Enables or disables sdf collision for occluders in the given TileSet occlusion layer. </description> </method> <method name="set_physics_layer_collision_layer"> @@ -247,6 +465,7 @@ <argument index="0" name="layer_index" type="int" /> <argument index="1" name="layer" type="int" /> <description> + Sets the physics layer (as in the physics server) for bodies in the given TileSet physics layer. </description> </method> <method name="set_physics_layer_collision_mask"> @@ -254,6 +473,7 @@ <argument index="0" name="layer_index" type="int" /> <argument index="1" name="mask" type="int" /> <description> + Sets the physics layer (as in the physics server) for bodies in the given TileSet physics layer. </description> </method> <method name="set_physics_layer_physics_material"> @@ -261,13 +481,15 @@ <argument index="0" name="layer_index" type="int" /> <argument index="1" name="physics_material" type="PhysicsMaterial" /> <description> + Sets the physics material for bodies in the given TileSet physics layer. </description> </method> <method name="set_source_id"> <return type="void" /> <argument index="0" name="source_id" type="int" /> - <argument index="1" name="arg1" type="int" /> + <argument index="1" name="new_source_id" type="int" /> <description> + Changes a source's ID. </description> </method> <method name="set_source_level_tile_proxy"> @@ -275,6 +497,9 @@ <argument index="0" name="source_from" type="int" /> <argument index="1" name="source_to" type="int" /> <description> + Creates a source-level proxy for the given source ID. A proxy will map set of tile identifiers to another set of identifiers. Both the atlac coordinates ID and the alternative tile ID are kept the same when using source-level proxies. + This can be used to replace a source in all TileMaps using this TileSet, as TileMap nodes will find and use the proxy's target source when one is available. + Proxied tiles can be automatically replaced in TileMap nodes using the editor. </description> </method> <method name="set_terrain_color"> @@ -283,6 +508,7 @@ <argument index="1" name="terrain_index" type="int" /> <argument index="2" name="color" type="Color" /> <description> + Sets a terrain's color. This color is used for identifying the different terrains in the TileSet editor. </description> </method> <method name="set_terrain_name"> @@ -291,6 +517,7 @@ <argument index="1" name="terrain_index" type="int" /> <argument index="2" name="name" type="String" /> <description> + Sets a terrain's name. </description> </method> <method name="set_terrain_set_mode"> @@ -298,103 +525,120 @@ <argument index="0" name="terrain_set" type="int" /> <argument index="1" name="mode" type="int" enum="TileSet.TerrainMode" /> <description> - </description> - </method> - <method name="set_terrains_count"> - <return type="void" /> - <argument index="0" name="terrain_set" type="int" /> - <argument index="1" name="terrains_count" type="int" /> - <description> + Sets a terrain mode. Each mode determines which bits of a tile shape is used to match the neighbouring tiles' terrains. </description> </method> </methods> <members> - <member name="custom_data_layers_count" type="int" setter="set_custom_data_layers_count" getter="get_custom_data_layers_count" default="0"> - </member> - <member name="navigation_layers_count" type="int" setter="set_navigation_layers_count" getter="get_navigation_layers_count" default="0"> - </member> - <member name="occlusion_layers_count" type="int" setter="set_occlusion_layers_count" getter="get_occlusion_layers_count" default="0"> - </member> - <member name="physics_layers_count" type="int" setter="set_physics_layers_count" getter="get_physics_layers_count" default="0"> - </member> - <member name="terrains_sets_count" type="int" setter="set_terrain_sets_count" getter="get_terrain_sets_count" default="0"> - </member> <member name="tile_layout" type="int" setter="set_tile_layout" getter="get_tile_layout" enum="TileSet.TileLayout" default="0"> + For all half-offset shapes (Isometric, Hexagonal and Half-Offset square), changes the way tiles are indexed in the TileMap grid. </member> <member name="tile_offset_axis" type="int" setter="set_tile_offset_axis" getter="get_tile_offset_axis" enum="TileSet.TileOffsetAxis" default="0"> + For all half-offset shapes (Isometric, Hexagonal and Half-Offset square), determines the offset axis. </member> <member name="tile_shape" type="int" setter="set_tile_shape" getter="get_tile_shape" enum="TileSet.TileShape" default="0"> + The tile shape. </member> <member name="tile_size" type="Vector2i" setter="set_tile_size" getter="get_tile_size" default="Vector2i(16, 16)"> + The tile size, in pixels. For all tile shapes, this size corresponds to the encompassing rectangle of the tile shape. This is thus the minimal cell size required in an atlas. </member> <member name="uv_clipping" type="bool" setter="set_uv_clipping" getter="is_uv_clipping" default="false"> + Enables/Disable uv clipping when rendering the tiles. </member> </members> <constants> <constant name="TILE_SHAPE_SQUARE" value="0" enum="TileShape"> - Orthogonal orientation mode. + Rectangular tile shape. </constant> <constant name="TILE_SHAPE_ISOMETRIC" value="1" enum="TileShape"> - Isometric orientation mode. + Diamond tile shape (for isometric look). </constant> <constant name="TILE_SHAPE_HALF_OFFSET_SQUARE" value="2" enum="TileShape"> + Rectangular tile shape with one row/colum out of two offset by half a tile. </constant> <constant name="TILE_SHAPE_HEXAGON" value="3" enum="TileShape"> - Hexagon orientation mode. + Hexagonal tile shape. </constant> <constant name="TILE_LAYOUT_STACKED" value="0" enum="TileLayout"> + Tile coordinates layout where both axis stay consistent with their respective local horizontal and vertical axis. </constant> <constant name="TILE_LAYOUT_STACKED_OFFSET" value="1" enum="TileLayout"> + Same as [code]TILE_LAYOUT_STAKED[/code], but the first half-offset is negative instead of positive. </constant> <constant name="TILE_LAYOUT_STAIRS_RIGHT" value="2" enum="TileLayout"> + Tile coordinates layout where the horizontal axis stay horizontal, and the vertical one goes down-right. </constant> <constant name="TILE_LAYOUT_STAIRS_DOWN" value="3" enum="TileLayout"> + Tile coordinates layout where the vertical axis stay vertical, and the horizontal one goes down-right. </constant> <constant name="TILE_LAYOUT_DIAMOND_RIGHT" value="4" enum="TileLayout"> + Tile coordinates layout where the horizontal axis goes up-right, and the vertical one goes down-right. </constant> <constant name="TILE_LAYOUT_DIAMOND_DOWN" value="5" enum="TileLayout"> + Tile coordinates layout where the horizontal axis goes down-right, and the vertical one goes down-left. </constant> <constant name="TILE_OFFSET_AXIS_HORIZONTAL" value="0" enum="TileOffsetAxis"> + Horizontal half-offset. </constant> <constant name="TILE_OFFSET_AXIS_VERTICAL" value="1" enum="TileOffsetAxis"> + Vertical half-offset. </constant> <constant name="CELL_NEIGHBOR_RIGHT_SIDE" value="0" enum="CellNeighbor"> + Neighbor on the right side. </constant> <constant name="CELL_NEIGHBOR_RIGHT_CORNER" value="1" enum="CellNeighbor"> + Neighbor in the right corner. </constant> <constant name="CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE" value="2" enum="CellNeighbor"> + Neighbor on the bottom right side. </constant> <constant name="CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER" value="3" enum="CellNeighbor"> + Neighbor in the bottom right corner. </constant> <constant name="CELL_NEIGHBOR_BOTTOM_SIDE" value="4" enum="CellNeighbor"> + Neighbor on the bottom side. </constant> <constant name="CELL_NEIGHBOR_BOTTOM_CORNER" value="5" enum="CellNeighbor"> + Neighbor in the bottom corner. </constant> <constant name="CELL_NEIGHBOR_BOTTOM_LEFT_SIDE" value="6" enum="CellNeighbor"> + Neighbor on the bottom left side. </constant> <constant name="CELL_NEIGHBOR_BOTTOM_LEFT_CORNER" value="7" enum="CellNeighbor"> + Neighbor in the bottom left corner. </constant> <constant name="CELL_NEIGHBOR_LEFT_SIDE" value="8" enum="CellNeighbor"> + Neighbor on the left side. </constant> <constant name="CELL_NEIGHBOR_LEFT_CORNER" value="9" enum="CellNeighbor"> + Neighbor in the left corner. </constant> <constant name="CELL_NEIGHBOR_TOP_LEFT_SIDE" value="10" enum="CellNeighbor"> + Neighbor on the top left side. </constant> <constant name="CELL_NEIGHBOR_TOP_LEFT_CORNER" value="11" enum="CellNeighbor"> + Neighbor in the top left corner. </constant> <constant name="CELL_NEIGHBOR_TOP_SIDE" value="12" enum="CellNeighbor"> + Neighbor on the top side. </constant> <constant name="CELL_NEIGHBOR_TOP_CORNER" value="13" enum="CellNeighbor"> + Neighbor in the top corner. </constant> <constant name="CELL_NEIGHBOR_TOP_RIGHT_SIDE" value="14" enum="CellNeighbor"> + Neighbor on the top right side. </constant> <constant name="CELL_NEIGHBOR_TOP_RIGHT_CORNER" value="15" enum="CellNeighbor"> + Neighbor in the top right corner. </constant> <constant name="TERRAIN_MODE_MATCH_CORNERS_AND_SIDES" value="0" enum="TerrainMode"> + Requires both corners and side to match with neighboring tiles' terrains. </constant> <constant name="TERRAIN_MODE_MATCH_CORNERS" value="1" enum="TerrainMode"> + Requires corners to match with neighboring tiles' terrains. </constant> <constant name="TERRAIN_MODE_MATCH_SIDES" value="2" enum="TerrainMode"> + Requires sides to match with neighboring tiles' terrains. </constant> </constants> </class> diff --git a/doc/classes/TileSetAtlasSource.xml b/doc/classes/TileSetAtlasSource.xml index 8caa3a7c39..fd3dbd1e4d 100644 --- a/doc/classes/TileSetAtlasSource.xml +++ b/doc/classes/TileSetAtlasSource.xml @@ -1,8 +1,16 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="TileSetAtlasSource" inherits="TileSetSource" version="4.0"> <brief_description> + Exposes a 2D atlas texture as a set of tiles for a [TileSet] resource. </brief_description> <description> + An atlas is a grid of tiles laid out on a texture. Each tile in the grid must be exposed using [method create_tile]. Those tiles are then indexed using their coordinates in the grid. + Each tile can also have a size in the grid coordinates, making it more or less cells in the atlas. + + Alternatives version of a tile can be created using [method create_alternative_tile], which are then indexed using an alternative ID. The main tile (the one in the grid), is accessed with an alternative ID equal to 0. + + Each tile alternate has a set of properties that is defined by the source's [TileSet] layers. Those properties are stored in a TileData object that can be accessed and modified using [method get_tile_data]. + As TileData properties are stored directly in the TileSetAtlasSource resource, their properties might also be set using [code]TileSetAtlasSource.set("<coords_x>:<coords_y>/<alternative_id>/<tile_data_property>")[/code]. </description> <tutorials> </tutorials> @@ -13,11 +21,14 @@ <argument index="1" name="new_atlas_coords" type="Vector2i" default="Vector2i(-1, -1)" /> <argument index="2" name="new_size" type="Vector2i" default="Vector2i(-1, -1)" /> <description> + Returns true if the tile at the [code]atlas_coords[/code] coordinates can be moved to the [code]new_atlas_coords[/code] coordinates with the [code]new_size[/code] size. This functions returns false if a tile is already present in the given area, or if this area is outside the atlas boundaries. + If [code]new_atlas_coords[/code] is [code]Vector2i(-1, -1)[/code], keeps the tile's coordinates. If [code]new_size[/code] is [code]Vector2i(-1, -1)[/code], keeps the tile's size. </description> </method> <method name="clear_tiles_outside_texture"> <return type="void" /> <description> + Clears all tiles that are defined outside the texture boundaries. </description> </method> <method name="create_alternative_tile"> @@ -25,6 +36,8 @@ <argument index="0" name="atlas_coords" type="Vector2i" /> <argument index="1" name="alternative_id_override" type="int" default="-1" /> <description> + Creates an alternative tile for the tile at coords [code]atlas_coords[/code]. If [code]alternative_id_override[/code] is -1, give it an automatically generated unique ID, or assigns it the given ID otherwise. + Returns the new alternative identifier, or -1 if the alternative could not be created with a provided [code]alternative_id_override[/code]. </description> </method> <method name="create_tile"> @@ -32,84 +45,55 @@ <argument index="0" name="atlas_coords" type="Vector2i" /> <argument index="1" name="size" type="Vector2i" default="Vector2i(1, 1)" /> <description> - </description> - </method> - <method name="get_alternative_tile_id" qualifiers="const"> - <return type="int" /> - <argument index="0" name="atlas_coords" type="Vector2i" /> - <argument index="1" name="index" type="int" /> - <description> - </description> - </method> - <method name="get_alternative_tiles_count" qualifiers="const"> - <return type="int" /> - <argument index="0" name="atlas_coords" type="Vector2i" /> - <description> + Creates a new tile at coords [code]atlas_coords[/code] with size [code]size[/code]. </description> </method> <method name="get_atlas_grid_size" qualifiers="const"> <return type="Vector2i" /> <description> + Returns the atlas grid size, which depends on how many tiles can fit in the texture. It thus depends on the Texture's size, the atlas [code]margins[/code] the tiles' [code]texture_region_size[/code]. </description> </method> <method name="get_next_alternative_tile_id" qualifiers="const"> <return type="int" /> <argument index="0" name="atlas_coords" type="Vector2i" /> <description> + Returns the alternative ID a following call to [method create_alternative_tile] would return. </description> </method> <method name="get_tile_at_coords" qualifiers="const"> <return type="Vector2i" /> <argument index="0" name="atlas_coords" type="Vector2i" /> <description> + If there is a tile covering the [code]atlas_coords[/code] coordinates, returns the top-left coordinates of the tile (thus its coordinate ID). Returns [code]Vector2i(-1, -1)[/code] otherwise. </description> </method> <method name="get_tile_data" qualifiers="const"> <return type="Object" /> <argument index="0" name="atlas_coords" type="Vector2i" /> - <argument index="1" name="index" type="int" /> - <description> - </description> - </method> - <method name="get_tile_id" qualifiers="const"> - <return type="Vector2i" /> - <argument index="0" name="index" type="int" /> + <argument index="1" name="alternative_tile" type="int" /> <description> + Returns the [TileData] object for the given atlas coordinates and alternative ID. </description> </method> <method name="get_tile_size_in_atlas" qualifiers="const"> <return type="Vector2i" /> <argument index="0" name="atlas_coords" type="Vector2i" /> <description> + Returns the size of the tile (in the grid coordinates system) at coordinates [code]atlas_coords[/code]. </description> </method> <method name="get_tile_texture_region" qualifiers="const"> <return type="Rect2i" /> <argument index="0" name="atlas_coords" type="Vector2i" /> <description> - </description> - </method> - <method name="get_tiles_count" qualifiers="const"> - <return type="int" /> - <description> - </description> - </method> - <method name="has_alternative_tile" qualifiers="const"> - <return type="bool" /> - <argument index="0" name="atlas_coords" type="Vector2i" /> - <argument index="1" name="alternative_tile" type="int" /> - <description> - </description> - </method> - <method name="has_tile" qualifiers="const"> - <return type="bool" /> - <argument index="0" name="atlas_coords" type="Vector2i" /> - <description> + Returns a tile's texture region in the atlas texture. </description> </method> <method name="has_tiles_outside_texture"> <return type="bool" /> <description> + Returns if this atlas has tiles outside of its texture. </description> </method> <method name="move_tile_in_atlas"> @@ -118,6 +102,9 @@ <argument index="1" name="new_atlas_coords" type="Vector2i" default="Vector2i(-1, -1)" /> <argument index="2" name="new_size" type="Vector2i" default="Vector2i(-1, -1)" /> <description> + Move the tile and its alternatives at the [code]atlas_coords[/code] coordinates to the [code]new_atlas_coords[/code] coordinates with the [code]new_size[/code] size. This functions will fail if a tile is already present in the given area. + If [code]new_atlas_coords[/code] is [code]Vector2i(-1, -1)[/code], keeps the tile's coordinates. If [code]new_size[/code] is [code]Vector2i(-1, -1)[/code], keeps the tile's size. + To avoid an error, first check if a move is possible using [method can_move_tile_in_atlas]. </description> </method> <method name="remove_alternative_tile"> @@ -125,12 +112,15 @@ <argument index="0" name="atlas_coords" type="Vector2i" /> <argument index="1" name="alternative_tile" type="int" /> <description> + Remove a tile's alternative with alternative ID [code]alternative_tile[/code]. + Calling this function with [code]alternative_tile[/code] equals to 0 will fail, as the base tile alternative cannot be removed. </description> </method> <method name="remove_tile"> <return type="void" /> <argument index="0" name="atlas_coords" type="Vector2i" /> <description> + Remove a tile and its alternative at coordinates [code]atlas_coords[/code]. </description> </method> <method name="set_alternative_tile_id"> @@ -139,17 +129,23 @@ <argument index="1" name="alternative_tile" type="int" /> <argument index="2" name="new_id" type="int" /> <description> + Change a tile's alternative ID from [code]alternative_tile[/code] to [code]new_id[/code]. + Calling this function with [code]alternative_id[/code] equals to 0 will fail, as the base tile alternative cannot be moved. </description> </method> </methods> <members> <member name="margins" type="Vector2i" setter="set_margins" getter="get_margins" default="Vector2i(0, 0)"> + Margins, in pixels, to offset the origin of the grid in the texture. </member> <member name="separation" type="Vector2i" setter="set_separation" getter="get_separation" default="Vector2i(0, 0)"> + Separation, in pixels, between each tile texture region of the grid. </member> <member name="texture" type="Texture2D" setter="set_texture" getter="get_texture"> + The atlas texture. </member> - <member name="tile_size" type="Vector2i" setter="set_texture_region_size" getter="get_texture_region_size" default="Vector2i(16, 16)"> + <member name="texture_region_size" type="Vector2i" setter="set_texture_region_size" getter="get_texture_region_size" default="Vector2i(16, 16)"> + The base tile size in the texture (in pixel). This size must be bigger than the TileSet's [code]tile_size[/code] value. </member> </members> <constants> diff --git a/doc/classes/TileSetScenesCollectionSource.xml b/doc/classes/TileSetScenesCollectionSource.xml index a44f519a4c..119a04c25f 100644 --- a/doc/classes/TileSetScenesCollectionSource.xml +++ b/doc/classes/TileSetScenesCollectionSource.xml @@ -1,8 +1,11 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="TileSetScenesCollectionSource" inherits="TileSetSource" version="4.0"> <brief_description> + Exposes a set of scenes as tiles for a [TileSet] resource. </brief_description> <description> + When placed on a [TileMap], tiles from [TileSetScenesCollectionSource] will automatically instanciate an assiciated scene at the cell's position in the TileMap. + Scenes are instanciated as children of the [TileMap] when it enters the tree. If you add/remove a scene tile in the [TileMap] that is already inside the tree, the [TileMap] will automatically instanciate/free the scene accordingly. </description> <tutorials> </tutorials> @@ -12,83 +15,55 @@ <argument index="0" name="packed_scene" type="PackedScene" /> <argument index="1" name="id_override" type="int" default="-1" /> <description> - </description> - </method> - <method name="get_alternative_tile_id" qualifiers="const"> - <return type="int" /> - <argument index="0" name="atlas_coords" type="Vector2i" /> - <argument index="1" name="index" type="int" /> - <description> - </description> - </method> - <method name="get_alternative_tiles_count" qualifiers="const"> - <return type="int" /> - <argument index="0" name="atlas_coords" type="Vector2i" /> - <description> + Creates a scene-based tile out of the given scene. + Returns a newly generated unique ID. </description> </method> <method name="get_next_scene_tile_id" qualifiers="const"> <return type="int" /> <description> + Returns the scene ID a following call to [method create_scene_tile] would return. </description> </method> <method name="get_scene_tile_display_placeholder" qualifiers="const"> <return type="bool" /> <argument index="0" name="id" type="int" /> <description> + Returns whether the scene tile with id [code]id[/code] displays a placeholder in the editor. </description> </method> <method name="get_scene_tile_id"> <return type="int" /> <argument index="0" name="index" type="int" /> <description> + Returns the scene tile ID of the scene tile at index [code]index[/code]. </description> </method> <method name="get_scene_tile_scene" qualifiers="const"> <return type="PackedScene" /> <argument index="0" name="id" type="int" /> <description> + Returns the [PackedScene] resource of scene tile with id [code]id[/code]. </description> </method> <method name="get_scene_tiles_count"> <return type="int" /> <description> - </description> - </method> - <method name="get_tile_id" qualifiers="const"> - <return type="Vector2i" /> - <argument index="0" name="index" type="int" /> - <description> - </description> - </method> - <method name="get_tiles_count" qualifiers="const"> - <return type="int" /> - <description> - </description> - </method> - <method name="has_alternative_tile" qualifiers="const"> - <return type="bool" /> - <argument index="0" name="atlas_coords" type="Vector2i" /> - <argument index="1" name="alternative_tile" type="int" /> - <description> + Returns the number or scene tiles this TileSet source has. </description> </method> <method name="has_scene_tile_id"> <return type="bool" /> <argument index="0" name="id" type="int" /> <description> - </description> - </method> - <method name="has_tile" qualifiers="const"> - <return type="bool" /> - <argument index="0" name="atlas_coords" type="Vector2i" /> - <description> + Returns whether this TileSet source has a scene tile with id [code]id[/code]. </description> </method> <method name="remove_scene_tile"> <return type="void" /> <argument index="0" name="id" type="int" /> <description> + Remove the scene tile with id [code]id[/code]. </description> </method> <method name="set_scene_tile_display_placeholder"> @@ -96,6 +71,7 @@ <argument index="0" name="id" type="int" /> <argument index="1" name="display_placeholder" type="bool" /> <description> + Sets whether or not the scene tile with id [code]id[/code] should display a placeholder in the editor. This might be useful for scenes that are not visible. </description> </method> <method name="set_scene_tile_id"> @@ -103,6 +79,7 @@ <argument index="0" name="id" type="int" /> <argument index="1" name="new_id" type="int" /> <description> + Changes a scene tile's ID from [code]id[/code] to [code]new_id[/code]. This will fail if there is already a tile with a ID equal to [code]new_id[/code]. </description> </method> <method name="set_scene_tile_scene"> @@ -110,6 +87,7 @@ <argument index="0" name="id" type="int" /> <argument index="1" name="packed_scene" type="PackedScene" /> <description> + Assigns a [PackedScene] resource to the scene tile with id [code]id[/code]. This will fail if the scene does not extend CanvasItem, as positionning properties are needed to place the scene on the TileMap. </description> </method> </methods> diff --git a/doc/classes/TileSetSource.xml b/doc/classes/TileSetSource.xml index 6a3029bb3f..442d845f6c 100644 --- a/doc/classes/TileSetSource.xml +++ b/doc/classes/TileSetSource.xml @@ -1,12 +1,63 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="TileSetSource" inherits="Resource" version="4.0"> <brief_description> + Exposes a set of tiles for a [TileSet] resource. </brief_description> <description> + Exposes a set of tiles for a [TileSet] resource. + Tiles in a source are indexed with two IDs, coordinates ID (of type Vector2i) and an alternative ID (of type int), named according to their use in the [TileSetAtlasSource] class. + Depending on the TileSet source type, those IDs might have restrictions on their values, this is why the base [TileSetSource] class only exposes getters for them. + + You can iterate over all tiles exposed by a TileSetSource by first iterating over coordinates IDs using [method get_tiles_count] and [method get_tile_id], then over alternative IDs using [method get_alternative_tiles_count] and [method get_alternative_tile_id]. </description> <tutorials> </tutorials> <methods> + <method name="get_alternative_tile_id" qualifiers="const"> + <return type="int" /> + <argument index="0" name="atlas_coords" type="Vector2i" /> + <argument index="1" name="index" type="int" /> + <description> + Returns the alternative ID for the tile with coordinates ID [code]atlas_coords[/code] at index [code]index[/code]. + </description> + </method> + <method name="get_alternative_tiles_count" qualifiers="const"> + <return type="int" /> + <argument index="0" name="atlas_coords" type="Vector2i" /> + <description> + Returns the number of alternatives tiles for the coordinates ID [code]atlas_coords[/code]. + For [TileSetAtlasSource], this always return at least 1, as the base tile with ID 0 is always part of the alternatives list. + Returns -1 if there is not tile at the given coords. + </description> + </method> + <method name="get_tile_id" qualifiers="const"> + <return type="Vector2i" /> + <argument index="0" name="index" type="int" /> + <description> + Returns the tile coordinates ID of the tile with index [code]index[/code]. + </description> + </method> + <method name="get_tiles_count" qualifiers="const"> + <return type="int" /> + <description> + Returns how many tiles this atlas source defines (not including alternative tiles). + </description> + </method> + <method name="has_alternative_tile" qualifiers="const"> + <return type="bool" /> + <argument index="0" name="atlas_coords" type="Vector2i" /> + <argument index="1" name="alternative_tile" type="int" /> + <description> + Returns if the base tile at coordinates [code]atlas_coords[/code] has an alternative with ID [code]alternative_tile[/code]. + </description> + </method> + <method name="has_tile" qualifiers="const"> + <return type="bool" /> + <argument index="0" name="atlas_coords" type="Vector2i" /> + <description> + Returns if this atlas has a tile with coordinates ID [code]atlas_coordinates[/code]. + </description> + </method> </methods> <constants> </constants> diff --git a/doc/classes/VScrollBar.xml b/doc/classes/VScrollBar.xml index 98a0aea0c7..519cc9c137 100644 --- a/doc/classes/VScrollBar.xml +++ b/doc/classes/VScrollBar.xml @@ -23,6 +23,9 @@ <theme_item name="decrement_highlight" data_type="icon" type="Texture2D"> Displayed when the mouse cursor hovers over the decrement button. </theme_item> + <theme_item name="decrement_pressed" data_type="icon" type="Texture2D"> + Displayed when the decrement button is being pressed. + </theme_item> <theme_item name="grabber" data_type="style" type="StyleBox"> Used as texture for the grabber, the draggable element representing current scroll. </theme_item> @@ -38,6 +41,9 @@ <theme_item name="increment_highlight" data_type="icon" type="Texture2D"> Displayed when the mouse cursor hovers over the increment button. </theme_item> + <theme_item name="increment_pressed" data_type="icon" type="Texture2D"> + Displayed when the increment button is being pressed. + </theme_item> <theme_item name="scroll" data_type="style" type="StyleBox"> Used as background of this [ScrollBar]. </theme_item> diff --git a/doc/classes/Viewport.xml b/doc/classes/Viewport.xml index 46f38e91f7..06a7177bfc 100644 --- a/doc/classes/Viewport.xml +++ b/doc/classes/Viewport.xml @@ -134,6 +134,7 @@ <return type="void" /> <argument index="0" name="text" type="String" /> <description> + Returns [code]true[/code] if the viewport is currently embedding windows. </description> </method> <method name="push_unhandled_input"> @@ -320,10 +321,7 @@ <constant name="MSAA_8X" value="3" enum="MSAA"> Use 8× Multisample Antialiasing. This has a very high performance cost. The difference between 4× and 8× MSAA may not always be visible in real gameplay conditions. Likely unsupported on low-end and older hardware. </constant> - <constant name="MSAA_16X" value="4" enum="MSAA"> - Use 16× Multisample Antialiasing. This has a very high performance cost. The difference between 8× and 16× MSAA may not always be visible in real gameplay conditions. Likely unsupported on medium and low-end hardware. - </constant> - <constant name="MSAA_MAX" value="5" enum="MSAA"> + <constant name="MSAA_MAX" value="4" enum="MSAA"> Represents the size of the [enum MSAA] enum. </constant> <constant name="SCREEN_SPACE_AA_DISABLED" value="0" enum="ScreenSpaceAA"> diff --git a/doc/classes/XRInterfaceExtension.xml b/doc/classes/XRInterfaceExtension.xml index 46d8d7e4e8..fb79926043 100644 --- a/doc/classes/XRInterfaceExtension.xml +++ b/doc/classes/XRInterfaceExtension.xml @@ -11,8 +11,8 @@ <methods> <method name="_commit_views" qualifiers="virtual"> <return type="void" /> - <argument index="0" name="" type="RID" /> - <argument index="1" name="" type="Rect2" /> + <argument index="0" name="render_target" type="RID" /> + <argument index="1" name="screen_rect" type="Rect2" /> <description> </description> </method> @@ -107,19 +107,26 @@ <method name="add_blit"> <return type="void" /> <argument index="0" name="render_target" type="RID" /> - <argument index="1" name="rect" type="Rect2i" /> - <argument index="2" name="use_layer" type="bool" /> - <argument index="3" name="layer" type="int" /> - <argument index="4" name="apply_lens_distortion" type="bool" /> - <argument index="5" name="eye_center" type="Vector2" /> - <argument index="6" name="k1" type="float" /> - <argument index="7" name="k2" type="float" /> - <argument index="8" name="upscale" type="float" /> - <argument index="9" name="aspect_ratio" type="float" /> + <argument index="1" name="src_rect" type="Rect2" /> + <argument index="2" name="dst_rect" type="Rect2i" /> + <argument index="3" name="use_layer" type="bool" /> + <argument index="4" name="layer" type="int" /> + <argument index="5" name="apply_lens_distortion" type="bool" /> + <argument index="6" name="eye_center" type="Vector2" /> + <argument index="7" name="k1" type="float" /> + <argument index="8" name="k2" type="float" /> + <argument index="9" name="upscale" type="float" /> + <argument index="10" name="aspect_ratio" type="float" /> <description> Blits our render results to screen optionally applying lens distortion. This can only be called while processing [code]_commit_views[/code]. </description> </method> + <method name="get_render_target_texture"> + <return type="RID" /> + <argument index="0" name="render_target" type="RID" /> + <description> + </description> + </method> </methods> <constants> </constants> diff --git a/drivers/vulkan/rendering_device_vulkan.cpp b/drivers/vulkan/rendering_device_vulkan.cpp index 6ad24f255f..a4324f0a2c 100644 --- a/drivers/vulkan/rendering_device_vulkan.cpp +++ b/drivers/vulkan/rendering_device_vulkan.cpp @@ -7588,7 +7588,7 @@ Error RenderingDeviceVulkan::_draw_list_allocate(const Rect2i &p_viewport, uint3 VkCommandPoolCreateInfo cmd_pool_info; cmd_pool_info.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; cmd_pool_info.pNext = nullptr; - cmd_pool_info.queueFamilyIndex = context->get_graphics_queue(); + cmd_pool_info.queueFamilyIndex = context->get_graphics_queue_family_index(); cmd_pool_info.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; VkResult res = vkCreateCommandPool(device, &cmd_pool_info, nullptr, &split_draw_list_allocators.write[i].command_pool); @@ -8846,7 +8846,7 @@ void RenderingDeviceVulkan::initialize(VulkanContext *p_context, bool p_local_de VkCommandPoolCreateInfo cmd_pool_info; cmd_pool_info.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; cmd_pool_info.pNext = nullptr; - cmd_pool_info.queueFamilyIndex = p_context->get_graphics_queue(); + cmd_pool_info.queueFamilyIndex = p_context->get_graphics_queue_family_index(); cmd_pool_info.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; VkResult res = vkCreateCommandPool(device, &cmd_pool_info, nullptr, &frames[i].command_pool); @@ -9016,6 +9016,92 @@ void RenderingDeviceVulkan::capture_timestamp(const String &p_name) { frames[frame].timestamp_count++; } +uint64_t RenderingDeviceVulkan::get_driver_resource(DriverResource p_resource, RID p_rid, uint64_t p_index) { + _THREAD_SAFE_METHOD_ + + switch (p_resource) { + case DRIVER_RESOURCE_VULKAN_DEVICE: { + return (uint64_t)context->get_device(); + }; break; + case DRIVER_RESOURCE_VULKAN_PHYSICAL_DEVICE: { + return (uint64_t)context->get_physical_device(); + }; break; + case DRIVER_RESOURCE_VULKAN_INSTANCE: { + return (uint64_t)context->get_instance(); + }; break; + case DRIVER_RESOURCE_VULKAN_QUEUE: { + return (uint64_t)context->get_graphics_queue(); + }; break; + case DRIVER_RESOURCE_VULKAN_QUEUE_FAMILY_INDEX: { + return context->get_graphics_queue_family_index(); + }; break; + case DRIVER_RESOURCE_VULKAN_IMAGE: { + Texture *tex = texture_owner.getornull(p_rid); + ERR_FAIL_NULL_V(tex, 0); + + return (uint64_t)tex->image; + }; break; + case DRIVER_RESOURCE_VULKAN_IMAGE_VIEW: { + Texture *tex = texture_owner.getornull(p_rid); + ERR_FAIL_NULL_V(tex, 0); + + return (uint64_t)tex->view; + }; break; + case DRIVER_RESOURCE_VULKAN_IMAGE_NATIVE_TEXTURE_FORMAT: { + Texture *tex = texture_owner.getornull(p_rid); + ERR_FAIL_NULL_V(tex, 0); + + return vulkan_formats[tex->format]; + }; break; + case DRIVER_RESOURCE_VULKAN_SAMPLER: { + VkSampler *sampler = sampler_owner.getornull(p_rid); + ERR_FAIL_NULL_V(sampler, 0); + + return uint64_t(*sampler); + }; break; + case DRIVER_RESOURCE_VULKAN_DESCRIPTOR_SET: { + UniformSet *uniform_set = uniform_set_owner.getornull(p_rid); + ERR_FAIL_NULL_V(uniform_set, 0); + + return uint64_t(uniform_set->descriptor_set); + }; break; + case DRIVER_RESOURCE_VULKAN_BUFFER: { + Buffer *buffer = nullptr; + if (vertex_buffer_owner.owns(p_rid)) { + buffer = vertex_buffer_owner.getornull(p_rid); + } else if (index_buffer_owner.owns(p_rid)) { + buffer = index_buffer_owner.getornull(p_rid); + } else if (uniform_buffer_owner.owns(p_rid)) { + buffer = uniform_buffer_owner.getornull(p_rid); + } else if (texture_buffer_owner.owns(p_rid)) { + buffer = &texture_buffer_owner.getornull(p_rid)->buffer; + } else if (storage_buffer_owner.owns(p_rid)) { + buffer = storage_buffer_owner.getornull(p_rid); + } + + ERR_FAIL_NULL_V(buffer, 0); + + return uint64_t(buffer->buffer); + }; break; + case DRIVER_RESOURCE_VULKAN_COMPUTE_PIPELINE: { + ComputePipeline *compute_pipeline = compute_pipeline_owner.getornull(p_rid); + ERR_FAIL_NULL_V(compute_pipeline, 0); + + return uint64_t(compute_pipeline->pipeline); + }; break; + case DRIVER_RESOURCE_VULKAN_RENDER_PIPELINE: { + RenderPipeline *render_pipeline = render_pipeline_owner.getornull(p_rid); + ERR_FAIL_NULL_V(render_pipeline, 0); + + return uint64_t(render_pipeline->pipeline); + }; break; + default: { + // not supported for this driver + return 0; + }; break; + } +} + uint32_t RenderingDeviceVulkan::get_captured_timestamps_count() const { return frames[frame].timestamp_result_count; } diff --git a/drivers/vulkan/rendering_device_vulkan.h b/drivers/vulkan/rendering_device_vulkan.h index dc1b78c1d5..cf0b725cfc 100644 --- a/drivers/vulkan/rendering_device_vulkan.h +++ b/drivers/vulkan/rendering_device_vulkan.h @@ -813,7 +813,7 @@ class RenderingDeviceVulkan : public RenderingDevice { // When using split command lists, this is // implemented internally using secondary command // buffers. As they can be created in threads, - // each needs it's own command pool. + // each needs its own command pool. struct SplitDrawListAllocator { VkCommandPool command_pool = VK_NULL_HANDLE; @@ -1227,6 +1227,8 @@ public: virtual String get_device_name() const; virtual String get_device_pipeline_cache_uuid() const; + virtual uint64_t get_driver_resource(DriverResource p_resource, RID p_rid = RID(), uint64_t p_index = 0); + RenderingDeviceVulkan(); ~RenderingDeviceVulkan(); }; diff --git a/drivers/vulkan/vulkan_context.cpp b/drivers/vulkan/vulkan_context.cpp index 625c222b67..c14e3f0e93 100644 --- a/drivers/vulkan/vulkan_context.cpp +++ b/drivers/vulkan/vulkan_context.cpp @@ -2028,7 +2028,11 @@ int VulkanContext::get_swapchain_image_count() const { return swapchainImageCount; } -uint32_t VulkanContext::get_graphics_queue() const { +VkQueue VulkanContext::get_graphics_queue() const { + return graphics_queue; +} + +uint32_t VulkanContext::get_graphics_queue_family_index() const { return graphics_queue_family_index; } diff --git a/drivers/vulkan/vulkan_context.h b/drivers/vulkan/vulkan_context.h index fe09d4c497..19ea806616 100644 --- a/drivers/vulkan/vulkan_context.h +++ b/drivers/vulkan/vulkan_context.h @@ -243,7 +243,8 @@ public: VkPhysicalDevice get_physical_device(); VkInstance get_instance() { return inst; } int get_swapchain_image_count() const; - uint32_t get_graphics_queue() const; + VkQueue get_graphics_queue() const; + uint32_t get_graphics_queue_family_index() const; void window_resize(DisplayServer::WindowID p_window_id, int p_width, int p_height); int window_get_width(DisplayServer::WindowID p_window = 0); diff --git a/editor/doc_tools.cpp b/editor/doc_tools.cpp index fee2deddda..d04875f188 100644 --- a/editor/doc_tools.cpp +++ b/editor/doc_tools.cpp @@ -277,7 +277,7 @@ void DocTools::generate(bool p_basic_types) { EO = EO->next(); } - if (E.usage & PROPERTY_USAGE_GROUP || E.usage & PROPERTY_USAGE_SUBGROUP || E.usage & PROPERTY_USAGE_CATEGORY || E.usage & PROPERTY_USAGE_INTERNAL) { + if (E.usage & PROPERTY_USAGE_GROUP || E.usage & PROPERTY_USAGE_SUBGROUP || E.usage & PROPERTY_USAGE_CATEGORY || E.usage & PROPERTY_USAGE_INTERNAL || (E.type == Variant::NIL && E.usage & PROPERTY_USAGE_ARRAY)) { continue; } diff --git a/editor/editor_autoload_settings.cpp b/editor/editor_autoload_settings.cpp index fad76682b5..362c4c3457 100644 --- a/editor/editor_autoload_settings.cpp +++ b/editor/editor_autoload_settings.cpp @@ -78,6 +78,14 @@ bool EditorAutoloadSettings::_autoload_name_is_valid(const String &p_name, Strin return false; } + if (ScriptServer::is_global_class(p_name)) { + if (r_error) { + *r_error = TTR("Invalid name.") + "\n" + TTR("Must not collide with an existing global script class name."); + } + + return false; + } + for (int i = 0; i < Variant::VARIANT_MAX; i++) { if (Variant::get_type_name(Variant::Type(i)) == p_name) { if (r_error) { diff --git a/editor/editor_data.cpp b/editor/editor_data.cpp index c62e5b75b2..e40bbefef8 100644 --- a/editor/editor_data.cpp +++ b/editor/editor_data.cpp @@ -438,6 +438,21 @@ const Vector<Callable> EditorData::get_undo_redo_inspector_hook_callback() { return undo_redo_callbacks; } +void EditorData::add_move_array_element_function(const StringName &p_class, Callable p_callable) { + move_element_functions.insert(p_class, p_callable); +} + +void EditorData::remove_move_array_element_function(const StringName &p_class) { + move_element_functions.erase(p_class); +} + +Callable EditorData::get_move_array_element_function(const StringName &p_class) const { + if (move_element_functions.has(p_class)) { + return move_element_functions[p_class]; + } + return Callable(); +} + void EditorData::remove_editor_plugin(EditorPlugin *p_plugin) { p_plugin->undo_redo = nullptr; editor_plugins.erase(p_plugin); diff --git a/editor/editor_data.h b/editor/editor_data.h index df6ba9d0c9..9184ddcf39 100644 --- a/editor/editor_data.h +++ b/editor/editor_data.h @@ -133,6 +133,7 @@ private: List<PropertyData> clipboard; UndoRedo undo_redo; Vector<Callable> undo_redo_callbacks; + Map<StringName, Callable> move_element_functions; void _cleanup_history(); @@ -167,10 +168,14 @@ public: EditorPlugin *get_editor_plugin(int p_idx); UndoRedo &get_undo_redo(); - void add_undo_redo_inspector_hook_callback(Callable p_callable); // Callbacks should have 4 args: (Object* undo_redo, Object *modified_object, String property, Variant new_value) + void add_undo_redo_inspector_hook_callback(Callable p_callable); // Callbacks should have this signature: void (Object* undo_redo, Object *modified_object, String property, Variant new_value) void remove_undo_redo_inspector_hook_callback(Callable p_callable); const Vector<Callable> get_undo_redo_inspector_hook_callback(); + void add_move_array_element_function(const StringName &p_class, Callable p_callable); // Function should have this signature: void (Object* undo_redo, Object *modified_object, String array_prefix, int element_index, int new_position) + void remove_move_array_element_function(const StringName &p_class); + Callable get_move_array_element_function(const StringName &p_class) const; + void save_editor_global_states(); void restore_editor_global_states(); diff --git a/editor/editor_export.cpp b/editor/editor_export.cpp index 1240496028..10ed76673e 100644 --- a/editor/editor_export.cpp +++ b/editor/editor_export.cpp @@ -1948,7 +1948,7 @@ void EditorExportPlatformPC::set_debug_32(const String &p_file) { void EditorExportPlatformPC::get_platform_features(List<String> *r_features) { r_features->push_back("pc"); //all pcs support "pc" r_features->push_back("s3tc"); //all pcs support "s3tc" compression - r_features->push_back(get_os_name()); //OS name is a feature + r_features->push_back(get_os_name().to_lower()); //OS name is a feature } void EditorExportPlatformPC::resolve_platform_feature_priorities(const Ref<EditorExportPreset> &p_preset, Set<String> &p_features) { diff --git a/editor/editor_file_system.cpp b/editor/editor_file_system.cpp index aa89a14725..8523833d52 100644 --- a/editor/editor_file_system.cpp +++ b/editor/editor_file_system.cpp @@ -444,6 +444,10 @@ bool EditorFileSystem::_test_for_reimport(const String &p_path, bool p_only_impo Ref<ResourceImporter> importer = ResourceFormatImporter::get_singleton()->get_importer_by_name(importer_name); + if (importer.is_null()) { + return true; // the importer has possibly changed, try to reimport. + } + if (importer->get_format_version() > version) { return true; // version changed, reimport } diff --git a/editor/editor_help.cpp b/editor/editor_help.cpp index 24b6ba1a14..490c8f287f 100644 --- a/editor/editor_help.cpp +++ b/editor/editor_help.cpp @@ -1328,6 +1328,8 @@ void EditorHelp::_help_callback(const String &p_topic) { } else if (what == "class_global") { if (constant_line.has(name)) { line = constant_line[name]; + } else if (method_line.has(name)) { + line = method_line[name]; } else { Map<String, Map<String, int>>::Element *iter = enum_values_line.front(); while (true) { diff --git a/editor/editor_inspector.cpp b/editor/editor_inspector.cpp index fee27dae58..d3841ad6c0 100644 --- a/editor/editor_inspector.cpp +++ b/editor/editor_inspector.cpp @@ -246,9 +246,9 @@ void EditorProperty::_notification(int p_what) { Color color; if (draw_red) { - color = get_theme_color(SNAME("error_color")); + color = get_theme_color(is_read_only() ? SNAME("readonly_error_color") : SNAME("error_color")); } else { - color = get_theme_color(SNAME("property_color")); + color = get_theme_color(is_read_only() ? SNAME("readonly_color") : SNAME("property_color")); } if (label.find(".") != -1) { color.a = 0.5; //this should be un-hacked honestly, as it's used for editor overrides @@ -283,7 +283,7 @@ void EditorProperty::_notification(int p_what) { check_rect = Rect2(); } - if (can_revert) { + if (can_revert && !is_read_only()) { Ref<Texture2D> reload_icon = get_theme_icon(SNAME("ReloadSmall"), SNAME("EditorIcons")); text_limit -= reload_icon->get_width() + get_theme_constant(SNAME("hseparator"), SNAME("Tree")) * 2; revert_rect = Rect2(text_limit + get_theme_constant(SNAME("hseparator"), SNAME("Tree")), (size.height - reload_icon->get_height()) / 2, reload_icon->get_width(), reload_icon->get_height()); @@ -384,8 +384,12 @@ void EditorProperty::update_property() { GDVIRTUAL_CALL(_update_property); } +void EditorProperty::_set_read_only(bool p_read_only) { +} + void EditorProperty::set_read_only(bool p_read_only) { read_only = p_read_only; + _set_read_only(p_read_only); } bool EditorProperty::is_read_only() const { @@ -1180,147 +1184,144 @@ EditorInspectorCategory::EditorInspectorCategory() { void EditorInspectorSection::_test_unfold() { if (!vbox_added) { add_child(vbox); + move_child(vbox, 0); vbox_added = true; } } void EditorInspectorSection::_notification(int p_what) { - if (p_what == NOTIFICATION_SORT_CHILDREN) { - Ref<Font> font = get_theme_font(SNAME("bold"), SNAME("EditorFonts")); - int font_size = get_theme_font_size(SNAME("bold_size"), SNAME("EditorFonts")); - - Ref<Texture2D> arrow; - - if (foldable) { - if (object->editor_is_section_unfolded(section)) { - arrow = get_theme_icon(SNAME("arrow"), SNAME("Tree")); - } else { - if (is_layout_rtl()) { - arrow = get_theme_icon(SNAME("arrow_collapsed_mirrored"), SNAME("Tree")); + switch (p_what) { + case NOTIFICATION_THEME_CHANGED: { + minimum_size_changed(); + } break; + case NOTIFICATION_SORT_CHILDREN: { + if (!vbox_added) { + return; + } + // Get the section header font. + Ref<Font> font = get_theme_font(SNAME("bold"), SNAME("EditorFonts")); + int font_size = get_theme_font_size(SNAME("bold_size"), SNAME("EditorFonts")); + + // Get the right direction arrow texture, if the section is foldable. + Ref<Texture2D> arrow; + if (foldable) { + if (object->editor_is_section_unfolded(section)) { + arrow = get_theme_icon(SNAME("arrow"), SNAME("Tree")); } else { - arrow = get_theme_icon(SNAME("arrow_collapsed"), SNAME("Tree")); + if (is_layout_rtl()) { + arrow = get_theme_icon(SNAME("arrow_collapsed_mirrored"), SNAME("Tree")); + } else { + arrow = get_theme_icon(SNAME("arrow_collapsed"), SNAME("Tree")); + } } } - } - - Size2 size = get_size(); - Point2 offset; - Rect2 rect; - offset.y = font->get_height(font_size); - if (arrow.is_valid()) { - offset.y = MAX(offset.y, arrow->get_height()); - } - offset.y += get_theme_constant(SNAME("vseparation"), SNAME("Tree")); - if (is_layout_rtl()) { - rect = Rect2(offset, size - offset - Vector2(get_theme_constant(SNAME("inspector_margin"), SNAME("Editor")), 0)); - } else { - offset.x += get_theme_constant(SNAME("inspector_margin"), SNAME("Editor")); - rect = Rect2(offset, size - offset); - } - - //set children - for (int i = 0; i < get_child_count(); i++) { - Control *c = Object::cast_to<Control>(get_child(i)); - if (!c) { - continue; + // Compute the height of the section header. + int header_height = font->get_height(font_size); + if (arrow.is_valid()) { + header_height = MAX(header_height, arrow->get_height()); } - if (c->is_set_as_top_level()) { - continue; - } - if (!c->is_visible_in_tree()) { - continue; - } - - fit_child_in_rect(c, rect); - } - - update(); //need to redraw text - } + header_height += get_theme_constant(SNAME("vseparation"), SNAME("Tree")); - if (p_what == NOTIFICATION_DRAW) { - Ref<Texture2D> arrow; - bool rtl = is_layout_rtl(); + int inspector_margin = get_theme_constant(SNAME("inspector_margin"), SNAME("Editor")); + Size2 size = get_size() - Vector2(inspector_margin, 0); + Vector2 offset = Vector2(is_layout_rtl() ? 0 : inspector_margin, header_height); + for (int i = 0; i < get_child_count(); i++) { + Control *c = Object::cast_to<Control>(get_child(i)); + if (!c) { + continue; + } + if (c->is_set_as_top_level()) { + continue; + } - if (foldable) { - if (object->editor_is_section_unfolded(section)) { - arrow = get_theme_icon(SNAME("arrow"), SNAME("Tree")); - } else { - if (is_layout_rtl()) { - arrow = get_theme_icon(SNAME("arrow_collapsed_mirrored"), SNAME("Tree")); + fit_child_in_rect(c, Rect2(offset, size)); + } + } break; + case NOTIFICATION_DRAW: { + // Get the section header font. + Ref<Font> font = get_theme_font(SNAME("bold"), SNAME("EditorFonts")); + int font_size = get_theme_font_size(SNAME("bold_size"), SNAME("EditorFonts")); + + // Get the right direction arrow texture, if the section is foldable. + Ref<Texture2D> arrow; + if (foldable) { + if (object->editor_is_section_unfolded(section)) { + arrow = get_theme_icon(SNAME("arrow"), SNAME("Tree")); } else { - arrow = get_theme_icon(SNAME("arrow_collapsed"), SNAME("Tree")); + if (is_layout_rtl()) { + arrow = get_theme_icon(SNAME("arrow_collapsed_mirrored"), SNAME("Tree")); + } else { + arrow = get_theme_icon(SNAME("arrow_collapsed"), SNAME("Tree")); + } } } - } - - Ref<Font> font = get_theme_font(SNAME("bold"), SNAME("EditorFonts")); - int font_size = get_theme_font_size(SNAME("bold_size"), SNAME("EditorFonts")); - - int h = font->get_height(font_size); - if (arrow.is_valid()) { - h = MAX(h, arrow->get_height()); - } - h += get_theme_constant(SNAME("vseparation"), SNAME("Tree")); - - Color c = bg_color; - c.a *= 0.4; - draw_rect(Rect2(Vector2(), Vector2(get_size().width, h)), c); - const int arrow_margin = 2; - const int arrow_width = arrow.is_valid() ? arrow->get_width() : 0; - Color color = get_theme_color(SNAME("font_color")); - float text_width = get_size().width - Math::round(arrow_width + arrow_margin * EDSCALE); - draw_string(font, Point2(rtl ? 0 : Math::round(arrow_width + arrow_margin * EDSCALE), font->get_ascent(font_size) + (h - font->get_height(font_size)) / 2).floor(), label, rtl ? HALIGN_RIGHT : HALIGN_LEFT, text_width, font_size, color); + bool rtl = is_layout_rtl(); - if (arrow.is_valid()) { - if (rtl) { - draw_texture(arrow, Point2(get_size().width - arrow->get_width() - Math::round(arrow_margin * EDSCALE), (h - arrow->get_height()) / 2).floor()); - } else { - draw_texture(arrow, Point2(Math::round(arrow_margin * EDSCALE), (h - arrow->get_height()) / 2).floor()); + // Compute the height of the section header. + int header_height = font->get_height(font_size); + if (arrow.is_valid()) { + header_height = MAX(header_height, arrow->get_height()); } - } + header_height += get_theme_constant(SNAME("vseparation"), SNAME("Tree")); - if (dropping && !vbox->is_visible_in_tree()) { - Color accent_color = get_theme_color(SNAME("accent_color"), SNAME("Editor")); - draw_rect(Rect2(Point2(), get_size()), accent_color, false); - } - } + Color c = bg_color; + c.a *= 0.4; + draw_rect(Rect2(Vector2(), Vector2(get_size().width, header_height)), c); - if (p_what == NOTIFICATION_DRAG_BEGIN) { - Dictionary dd = get_viewport()->gui_get_drag_data(); + const int arrow_margin = 2; + const int arrow_width = arrow.is_valid() ? arrow->get_width() : 0; + Color color = get_theme_color(SNAME("font_color")); + float text_width = get_size().width - Math::round(arrow_width + arrow_margin * EDSCALE); + draw_string(font, Point2(rtl ? 0 : Math::round(arrow_width + arrow_margin * EDSCALE), font->get_ascent(font_size) + (header_height - font->get_height(font_size)) / 2).floor(), label, rtl ? HALIGN_RIGHT : HALIGN_LEFT, text_width, font_size, color); - // Only allow dropping if the section contains properties which can take the dragged data. - bool children_can_drop = false; - for (int child_idx = 0; child_idx < vbox->get_child_count(); child_idx++) { - Control *editor_property = Object::cast_to<Control>(vbox->get_child(child_idx)); + if (arrow.is_valid()) { + if (rtl) { + draw_texture(arrow, Point2(get_size().width - arrow->get_width() - Math::round(arrow_margin * EDSCALE), (header_height - arrow->get_height()) / 2).floor()); + } else { + draw_texture(arrow, Point2(Math::round(arrow_margin * EDSCALE), (header_height - arrow->get_height()) / 2).floor()); + } + } - // Test can_drop_data and can_drop_data_fw, since can_drop_data only works if set up with forwarding or if script attached. - if (editor_property && (editor_property->can_drop_data(Point2(), dd) || editor_property->call("_can_drop_data_fw", Point2(), dd, this))) { - children_can_drop = true; - break; + if (dropping && !vbox->is_visible_in_tree()) { + Color accent_color = get_theme_color(SNAME("accent_color"), SNAME("Editor")); + draw_rect(Rect2(Point2(), get_size()), accent_color, false); } - } + } break; + case NOTIFICATION_DRAG_BEGIN: { + Dictionary dd = get_viewport()->gui_get_drag_data(); - dropping = children_can_drop; - update(); - } + // Only allow dropping if the section contains properties which can take the dragged data. + bool children_can_drop = false; + for (int child_idx = 0; child_idx < vbox->get_child_count(); child_idx++) { + Control *editor_property = Object::cast_to<Control>(vbox->get_child(child_idx)); - if (p_what == NOTIFICATION_DRAG_END) { - dropping = false; - update(); - } + // Test can_drop_data and can_drop_data_fw, since can_drop_data only works if set up with forwarding or if script attached. + if (editor_property && (editor_property->can_drop_data(Point2(), dd) || editor_property->call("_can_drop_data_fw", Point2(), dd, this))) { + children_can_drop = true; + break; + } + } - if (p_what == NOTIFICATION_MOUSE_ENTER) { - if (dropping) { - dropping_unfold_timer->start(); - } - } + dropping = children_can_drop; + update(); + } break; + case NOTIFICATION_DRAG_END: { + dropping = false; + update(); + } break; + case NOTIFICATION_MOUSE_ENTER: { + if (dropping) { + dropping_unfold_timer->start(); + } + } break; - if (p_what == NOTIFICATION_MOUSE_EXIT) { - if (dropping) { - dropping_unfold_timer->stop(); - } + case NOTIFICATION_MOUSE_EXIT: { + if (dropping) { + dropping_unfold_timer->stop(); + } + } break; } } @@ -1359,6 +1360,7 @@ void EditorInspectorSection::setup(const String &p_section, const String &p_labe if (!foldable && !vbox_added) { add_child(vbox); + move_child(vbox, 0); vbox_added = true; } @@ -1418,7 +1420,7 @@ void EditorInspectorSection::fold() { } if (!vbox_added) { - return; //kinda pointless + return; } object->editor_set_section_unfold(section, false); @@ -1455,6 +1457,732 @@ EditorInspectorSection::~EditorInspectorSection() { //////////////////////////////////////////////// //////////////////////////////////////////////// +int EditorInspectorArray::_get_array_count() { + if (mode == MODE_USE_MOVE_ARRAY_ELEMENT_FUNCTION) { + List<PropertyInfo> object_property_list; + object->get_property_list(&object_property_list); + return _extract_properties_as_array(object_property_list).size(); + } else if (mode == MODE_USE_COUNT_PROPERTY) { + bool valid; + int count = object->get(count_property, &valid); + ERR_FAIL_COND_V_MSG(!valid, 0, vformat("%s is not a valid property to be used as array count.", count_property)); + return count; + } + return 0; +} + +void EditorInspectorArray::_add_button_pressed() { + _move_element(-1, -1); +} + +void EditorInspectorArray::_first_page_button_pressed() { + emit_signal("page_change_request", 0); +} + +void EditorInspectorArray::_prev_page_button_pressed() { + emit_signal("page_change_request", MAX(0, page - 1)); +} + +void EditorInspectorArray::_page_line_edit_text_submitted(String p_text) { + if (p_text.is_valid_int()) { + int new_page = p_text.to_int() - 1; + new_page = MIN(MAX(0, new_page), max_page); + page_line_edit->set_text(Variant(new_page)); + emit_signal("page_change_request", new_page); + } else { + page_line_edit->set_text(Variant(page)); + } +} + +void EditorInspectorArray::_next_page_button_pressed() { + emit_signal("page_change_request", MIN(max_page, page + 1)); +} + +void EditorInspectorArray::_last_page_button_pressed() { + emit_signal("page_change_request", max_page); +} + +void EditorInspectorArray::_rmb_popup_id_pressed(int p_id) { + switch (p_id) { + case OPTION_MOVE_UP: + if (popup_array_index_pressed > 0) { + _move_element(popup_array_index_pressed, popup_array_index_pressed - 1); + } + break; + case OPTION_MOVE_DOWN: + if (popup_array_index_pressed < count - 1) { + _move_element(popup_array_index_pressed, popup_array_index_pressed + 2); + } + break; + case OPTION_NEW_BEFORE: + _move_element(-1, popup_array_index_pressed); + break; + case OPTION_NEW_AFTER: + _move_element(-1, popup_array_index_pressed + 1); + break; + case OPTION_REMOVE: + _move_element(popup_array_index_pressed, -1); + break; + case OPTION_CLEAR_ARRAY: + _clear_array(); + break; + case OPTION_RESIZE_ARRAY: + new_size = count; + new_size_line_edit->set_text(Variant(new_size)); + resize_dialog->get_ok_button()->set_disabled(true); + resize_dialog->popup_centered(); + new_size_line_edit->grab_focus(); + new_size_line_edit->select_all(); + break; + default: + break; + } +} + +void EditorInspectorArray::_control_dropping_draw() { + int drop_position = _drop_position(); + + if (dropping && drop_position >= 0) { + Vector2 from; + Vector2 to; + if (drop_position < elements_vbox->get_child_count()) { + Transform2D xform = Object::cast_to<Control>(elements_vbox->get_child(drop_position))->get_transform(); + from = xform.xform(Vector2()); + to = xform.xform(Vector2(elements_vbox->get_size().x, 0)); + } else { + Control *child = Object::cast_to<Control>(elements_vbox->get_child(drop_position - 1)); + Transform2D xform = child->get_transform(); + from = xform.xform(Vector2(0, child->get_size().y)); + to = xform.xform(Vector2(elements_vbox->get_size().x, child->get_size().y)); + } + Color color = get_theme_color(SNAME("accent_color"), SNAME("Editor")); + control_dropping->draw_line(from, to, color, 2); + } +} + +void EditorInspectorArray::_vbox_visibility_changed() { + control_dropping->set_visible(vbox->is_visible_in_tree()); +} + +void EditorInspectorArray::_panel_draw(int p_index) { + ERR_FAIL_INDEX(p_index, (int)array_elements.size()); + + Ref<StyleBox> style = get_theme_stylebox("Focus", "EditorStyles"); + if (!style.is_valid()) { + return; + } + if (array_elements[p_index].panel->has_focus()) { + array_elements[p_index].panel->draw_style_box(style, Rect2(Vector2(), array_elements[p_index].panel->get_size())); + } +} + +void EditorInspectorArray::_panel_gui_input(Ref<InputEvent> p_event, int p_index) { + ERR_FAIL_INDEX(p_index, (int)array_elements.size()); + + Ref<InputEventKey> key_ref = p_event; + if (key_ref.is_valid()) { + const InputEventKey &key = **key_ref; + + if (array_elements[p_index].panel->has_focus() && key.is_pressed() && key.get_keycode() == KEY_DELETE) { + _move_element(begin_array_index + p_index, -1); + array_elements[p_index].panel->accept_event(); + } + } + + Ref<InputEventMouseButton> mb = p_event; + if (mb.is_valid()) { + if (mb->get_button_index() == MOUSE_BUTTON_RIGHT) { + popup_array_index_pressed = begin_array_index + p_index; + rmb_popup->set_item_disabled(OPTION_MOVE_UP, popup_array_index_pressed == 0); + rmb_popup->set_item_disabled(OPTION_MOVE_DOWN, popup_array_index_pressed == count - 1); + rmb_popup->set_position(mb->get_global_position()); + rmb_popup->set_size(Vector2()); + rmb_popup->popup(); + } + } +} + +void EditorInspectorArray::_move_element(int p_element_index, int p_to_pos) { + String action_name; + if (p_element_index < 0) { + action_name = vformat("Add element to property array with prefix %s.", array_element_prefix); + } else if (p_to_pos < 0) { + action_name = vformat("Remove element %d from property array with prefix %s.", p_element_index, array_element_prefix); + } else { + action_name = vformat("Move element %d to position %d in property array with prefix %s.", p_element_index, p_to_pos, array_element_prefix); + } + undo_redo->create_action(action_name); + if (mode == MODE_USE_MOVE_ARRAY_ELEMENT_FUNCTION) { + // Call the function. + Callable move_function = EditorNode::get_singleton()->get_editor_data().get_move_array_element_function(object->get_class_name()); + if (move_function.is_valid()) { + Variant args[] = { (Object *)undo_redo, object, array_element_prefix, p_element_index, p_to_pos }; + const Variant *args_p[] = { &args[0], &args[1], &args[2], &args[3], &args[4] }; + Variant return_value; + Callable::CallError call_error; + move_function.call(args_p, 5, return_value, call_error); + } else { + WARN_PRINT(vformat("Could not find a function to move arrays elements for class %s. Register a move element function using EditorData::add_move_array_element_function", object->get_class_name())); + } + } else if (mode == MODE_USE_COUNT_PROPERTY) { + ERR_FAIL_COND(p_to_pos < -1 || p_to_pos > count); + List<PropertyInfo> object_property_list; + object->get_property_list(&object_property_list); + + Array properties_as_array = _extract_properties_as_array(object_property_list); + properties_as_array.resize(count); + + // For undoing things + undo_redo->add_undo_property(object, count_property, properties_as_array.size()); + for (int i = 0; i < (int)properties_as_array.size(); i++) { + Dictionary d = Dictionary(properties_as_array[i]); + Array keys = d.keys(); + for (int j = 0; j < keys.size(); j++) { + String key = keys[j]; + undo_redo->add_undo_property(object, vformat(key, i), d[key]); + } + } + + if (p_element_index < 0) { + // Add an element. + properties_as_array.insert(p_to_pos < 0 ? properties_as_array.size() : p_to_pos, Dictionary()); + } else if (p_to_pos < 0) { + // Delete the element. + properties_as_array.remove(p_element_index); + } else { + // Move the element. + properties_as_array.insert(p_to_pos, properties_as_array[p_element_index].duplicate()); + properties_as_array.remove(p_to_pos < p_element_index ? p_element_index + 1 : p_element_index); + } + + // Change the array size then set the properties. + undo_redo->add_do_property(object, count_property, properties_as_array.size()); + for (int i = 0; i < (int)properties_as_array.size(); i++) { + Dictionary d = properties_as_array[i]; + Array keys = d.keys(); + for (int j = 0; j < keys.size(); j++) { + String key = keys[j]; + undo_redo->add_do_property(object, vformat(key, i), d[key]); + } + } + } + undo_redo->commit_action(); + + // Handle page change and update counts. + if (p_element_index < 0) { + int added_index = p_to_pos < 0 ? count : p_to_pos; + emit_signal("page_change_request", added_index / page_lenght); + count += 1; + } else if (p_to_pos < 0) { + count -= 1; + if (page == max_page && (MAX(0, count - 1) / page_lenght != max_page)) { + emit_signal("page_change_request", max_page - 1); + } + } + begin_array_index = page * page_lenght; + end_array_index = MIN(count, (page + 1) * page_lenght); + max_page = MAX(0, count - 1) / page_lenght; +} + +void EditorInspectorArray::_clear_array() { + undo_redo->create_action(vformat("Clear property array with prefix %s.", array_element_prefix)); + if (mode == MODE_USE_MOVE_ARRAY_ELEMENT_FUNCTION) { + for (int i = count - 1; i >= 0; i--) { + // Call the function. + Callable move_function = EditorNode::get_singleton()->get_editor_data().get_move_array_element_function(object->get_class_name()); + if (move_function.is_valid()) { + Variant args[] = { (Object *)undo_redo, object, array_element_prefix, i, -1 }; + const Variant *args_p[] = { &args[0], &args[1], &args[2], &args[3], &args[4] }; + Variant return_value; + Callable::CallError call_error; + move_function.call(args_p, 5, return_value, call_error); + } else { + WARN_PRINT(vformat("Could not find a function to move arrays elements for class %s. Register a move element function using EditorData::add_move_array_element_function", object->get_class_name())); + } + } + } else if (mode == MODE_USE_COUNT_PROPERTY) { + List<PropertyInfo> object_property_list; + object->get_property_list(&object_property_list); + + Array properties_as_array = _extract_properties_as_array(object_property_list); + properties_as_array.resize(count); + + // For undoing things + undo_redo->add_undo_property(object, count_property, count); + for (int i = 0; i < (int)properties_as_array.size(); i++) { + Dictionary d = Dictionary(properties_as_array[i]); + Array keys = d.keys(); + for (int j = 0; j < keys.size(); j++) { + String key = keys[j]; + undo_redo->add_undo_property(object, vformat(key, i), d[key]); + } + } + + // Change the array size then set the properties. + undo_redo->add_do_property(object, count_property, 0); + } + undo_redo->commit_action(); + + // Handle page change and update counts. + emit_signal("page_change_request", 0); + count = 0; + begin_array_index = 0; + end_array_index = 0; + max_page = 0; +} + +void EditorInspectorArray::_resize_array(int p_size) { + ERR_FAIL_COND(p_size < 0); + if (p_size == count) { + return; + } + + undo_redo->create_action(vformat("Resize property array with prefix %s.", array_element_prefix)); + if (p_size > count) { + if (mode == MODE_USE_MOVE_ARRAY_ELEMENT_FUNCTION) { + for (int i = count; i < p_size; i++) { + // Call the function. + Callable move_function = EditorNode::get_singleton()->get_editor_data().get_move_array_element_function(object->get_class_name()); + if (move_function.is_valid()) { + Variant args[] = { (Object *)undo_redo, object, array_element_prefix, -1, -1 }; + const Variant *args_p[] = { &args[0], &args[1], &args[2], &args[3], &args[4] }; + Variant return_value; + Callable::CallError call_error; + move_function.call(args_p, 5, return_value, call_error); + } else { + WARN_PRINT(vformat("Could not find a function to move arrays elements for class %s. Register a move element function using EditorData::add_move_array_element_function", object->get_class_name())); + } + } + } else if (mode == MODE_USE_COUNT_PROPERTY) { + undo_redo->add_undo_property(object, count_property, count); + undo_redo->add_do_property(object, count_property, p_size); + } + } else { + if (mode == MODE_USE_MOVE_ARRAY_ELEMENT_FUNCTION) { + for (int i = count - 1; i > p_size - 1; i--) { + // Call the function. + Callable move_function = EditorNode::get_singleton()->get_editor_data().get_move_array_element_function(object->get_class_name()); + if (move_function.is_valid()) { + Variant args[] = { (Object *)undo_redo, object, array_element_prefix, i, -1 }; + const Variant *args_p[] = { &args[0], &args[1], &args[2], &args[3], &args[4] }; + Variant return_value; + Callable::CallError call_error; + move_function.call(args_p, 5, return_value, call_error); + } else { + WARN_PRINT(vformat("Could not find a function to move arrays elements for class %s. Register a move element function using EditorData::add_move_array_element_function", object->get_class_name())); + } + } + } else if (mode == MODE_USE_COUNT_PROPERTY) { + List<PropertyInfo> object_property_list; + object->get_property_list(&object_property_list); + + Array properties_as_array = _extract_properties_as_array(object_property_list); + properties_as_array.resize(count); + + // For undoing things + undo_redo->add_undo_property(object, count_property, count); + for (int i = count - 1; i > p_size - 1; i--) { + Dictionary d = Dictionary(properties_as_array[i]); + Array keys = d.keys(); + for (int j = 0; j < keys.size(); j++) { + String key = keys[j]; + undo_redo->add_undo_property(object, vformat(key, i), d[key]); + } + } + + // Change the array size then set the properties. + undo_redo->add_do_property(object, count_property, p_size); + } + } + undo_redo->commit_action(); + + // Handle page change and update counts. + emit_signal("page_change_request", 0); + /* + count = 0; + begin_array_index = 0; + end_array_index = 0; + max_page = 0; + */ +} + +Array EditorInspectorArray::_extract_properties_as_array(const List<PropertyInfo> &p_list) { + Array output; + + for (const PropertyInfo &pi : p_list) { + if (pi.name.begins_with(array_element_prefix)) { + String str = pi.name.trim_prefix(array_element_prefix); + + int to_char_index = 0; + while (to_char_index < str.length()) { + if (str[to_char_index] < '0' || str[to_char_index] > '9') { + break; + } + to_char_index++; + } + if (to_char_index > 0) { + int array_index = str.left(to_char_index).to_int(); + Error error = OK; + if (array_index >= output.size()) { + error = output.resize(array_index + 1); + } + if (error == OK) { + String format_string = String(array_element_prefix) + "%d" + str.substr(to_char_index); + Dictionary dict = output[array_index]; + dict[format_string] = object->get(pi.name); + output[array_index] = dict; + } else { + WARN_PRINT(vformat("Array element %s has an index too high. Array allocaiton failed.", pi.name)); + } + } + } + } + return output; +} + +int EditorInspectorArray::_drop_position() const { + for (int i = 0; i < (int)array_elements.size(); i++) { + const ArrayElement &ae = array_elements[i]; + + Size2 size = ae.panel->get_size(); + Vector2 mp = ae.panel->get_local_mouse_position(); + + if (Rect2(Vector2(), size).has_point(mp)) { + if (mp.y < size.y / 2) { + return i; + } else { + return i + 1; + } + } + } + return -1; +} + +void EditorInspectorArray::_new_size_line_edit_text_changed(String p_text) { + bool valid = false; + ; + if (p_text.is_valid_int()) { + int val = p_text.to_int(); + if (val > 0 && val != count) { + valid = true; + } + } + resize_dialog->get_ok_button()->set_disabled(!valid); +} + +void EditorInspectorArray::_new_size_line_edit_text_submitted(String p_text) { + bool valid = false; + ; + if (p_text.is_valid_int()) { + int val = p_text.to_int(); + if (val > 0 && val != count) { + new_size = val; + valid = true; + } + } + if (valid) { + resize_dialog->hide(); + _resize_array(new_size); + } else { + new_size_line_edit->set_text(Variant(new_size)); + } +} + +void EditorInspectorArray::_resize_dialog_confirmed() { + _new_size_line_edit_text_submitted(new_size_line_edit->get_text()); +} + +void EditorInspectorArray::_setup() { + // Setup counts. + count = _get_array_count(); + begin_array_index = page * page_lenght; + end_array_index = MIN(count, (page + 1) * page_lenght); + max_page = MAX(0, count - 1) / page_lenght; + array_elements.resize(MAX(0, end_array_index - begin_array_index)); + if (page < 0 || page > max_page) { + WARN_PRINT(vformat("Invalid page number %d", page)); + page = CLAMP(page, 0, max_page); + } + + for (int i = 0; i < (int)array_elements.size(); i++) { + ArrayElement &ae = array_elements[i]; + + // Panel and its hbox. + ae.panel = memnew(PanelContainer); + ae.panel->set_focus_mode(FOCUS_ALL); + ae.panel->set_mouse_filter(MOUSE_FILTER_PASS); + ae.panel->set_drag_forwarding(this); + ae.panel->set_meta("index", begin_array_index + i); + ae.panel->set_tooltip(vformat(TTR("Element %d: %s%d*"), i, array_element_prefix, i)); + ae.panel->connect("focus_entered", callable_mp((CanvasItem *)ae.panel, &PanelContainer::update)); + ae.panel->connect("focus_exited", callable_mp((CanvasItem *)ae.panel, &PanelContainer::update)); + ae.panel->connect("draw", callable_bind(callable_mp(this, &EditorInspectorArray::_panel_draw), i)); + ae.panel->connect("gui_input", callable_bind(callable_mp(this, &EditorInspectorArray::_panel_gui_input), i)); + ae.panel->add_theme_style_override(SNAME("panel"), i % 2 ? odd_style : even_style); + elements_vbox->add_child(ae.panel); + + ae.margin = memnew(MarginContainer); + ae.margin->set_mouse_filter(MOUSE_FILTER_PASS); + if (is_inside_tree()) { + Size2 min_size = get_theme_stylebox("Focus", "EditorStyles")->get_minimum_size(); + ae.margin->add_theme_constant_override("margin_left", min_size.x / 2); + ae.margin->add_theme_constant_override("margin_top", min_size.y / 2); + ae.margin->add_theme_constant_override("margin_right", min_size.x / 2); + ae.margin->add_theme_constant_override("margin_bottom", min_size.y / 2); + } + ae.panel->add_child(ae.margin); + + ae.hbox = memnew(HBoxContainer); + ae.hbox->set_h_size_flags(SIZE_EXPAND_FILL); + ae.hbox->set_v_size_flags(SIZE_EXPAND_FILL); + ae.margin->add_child(ae.hbox); + + // Move button. + ae.move_texture_rect = memnew(TextureRect); + ae.move_texture_rect->set_stretch_mode(TextureRect::STRETCH_KEEP_CENTERED); + if (is_inside_tree()) { + ae.move_texture_rect->set_texture(get_theme_icon(SNAME("TripleBar"), SNAME("EditorIcons"))); + } + ae.hbox->add_child(ae.move_texture_rect); + + // Right vbox. + ae.vbox = memnew(VBoxContainer); + ae.vbox->set_h_size_flags(SIZE_EXPAND_FILL); + ae.vbox->set_v_size_flags(SIZE_EXPAND_FILL); + ae.hbox->add_child(ae.vbox); + } + + // Hide/show the add button. + add_button->set_visible(page == max_page); + + if (max_page == 0) { + hbox_pagination->hide(); + } else { + // Update buttons. + first_page_button->set_disabled(page == 0); + prev_page_button->set_disabled(page == 0); + next_page_button->set_disabled(page == max_page); + last_page_button->set_disabled(page == max_page); + + // Update page number and page count. + page_line_edit->set_text(vformat("%d", page + 1)); + page_count_label->set_text(vformat("/ %d", max_page + 1)); + } +} + +Variant EditorInspectorArray::get_drag_data_fw(const Point2 &p_point, Control *p_from) { + int index = p_from->get_meta("index"); + Dictionary dict; + dict["type"] = "property_array_element"; + dict["property_array_prefix"] = array_element_prefix; + dict["index"] = index; + + return dict; +} + +void EditorInspectorArray::drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) { + Dictionary dict = p_data; + + int to_drop = dict["index"]; + int drop_position = _drop_position(); + if (drop_position < 0) { + return; + } + _move_element(to_drop, begin_array_index + drop_position); +} + +bool EditorInspectorArray::can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const { + // First, update drawing. + control_dropping->update(); + + if (p_data.get_type() != Variant::DICTIONARY) { + return false; + } + Dictionary dict = p_data; + int drop_position = _drop_position(); + if (!dict.has("type") || dict["type"] != "property_array_element" || String(dict["property_array_prefix"]) != array_element_prefix || drop_position < 0) { + return false; + } + + // Check in dropping at the given index does indeed move the item. + int moved_array_index = (int)dict["index"]; + int drop_array_index = begin_array_index + drop_position; + + return drop_array_index != moved_array_index && drop_array_index - 1 != moved_array_index; +} + +void EditorInspectorArray::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_THEME_CHANGED: { + Color color = get_theme_color(SNAME("dark_color_1"), SNAME("Editor")); + odd_style->set_bg_color(color.lightened(0.15)); + even_style->set_bg_color(color.darkened(0.15)); + + for (int i = 0; i < (int)array_elements.size(); i++) { + ArrayElement &ae = array_elements[i]; + ae.move_texture_rect->set_texture(get_theme_icon(SNAME("TripleBar"), SNAME("EditorIcons"))); + + Size2 min_size = get_theme_stylebox("Focus", "EditorStyles")->get_minimum_size(); + ae.margin->add_theme_constant_override("margin_left", min_size.x / 2); + ae.margin->add_theme_constant_override("margin_top", min_size.y / 2); + ae.margin->add_theme_constant_override("margin_right", min_size.x / 2); + ae.margin->add_theme_constant_override("margin_bottom", min_size.y / 2); + } + + add_button->set_icon(get_theme_icon(SNAME("Add"), SNAME("EditorIcons"))); + first_page_button->set_icon(get_theme_icon(SNAME("PageFirst"), SNAME("EditorIcons"))); + prev_page_button->set_icon(get_theme_icon(SNAME("PagePrevious"), SNAME("EditorIcons"))); + next_page_button->set_icon(get_theme_icon(SNAME("PageNext"), SNAME("EditorIcons"))); + last_page_button->set_icon(get_theme_icon(SNAME("PageLast"), SNAME("EditorIcons"))); + minimum_size_changed(); + } break; + case NOTIFICATION_DRAG_BEGIN: { + Dictionary dict = get_viewport()->gui_get_drag_data(); + if (dict.has("type") && dict["type"] == "property_array_element" && String(dict["property_array_prefix"]) == array_element_prefix) { + dropping = true; + control_dropping->update(); + } + } break; + case NOTIFICATION_DRAG_END: { + if (dropping) { + dropping = false; + control_dropping->update(); + } + } break; + } +} + +void EditorInspectorArray::_bind_methods() { + ClassDB::bind_method(D_METHOD("_get_drag_data_fw"), &EditorInspectorArray::get_drag_data_fw); + ClassDB::bind_method(D_METHOD("_can_drop_data_fw"), &EditorInspectorArray::can_drop_data_fw); + ClassDB::bind_method(D_METHOD("_drop_data_fw"), &EditorInspectorArray::drop_data_fw); + + ADD_SIGNAL(MethodInfo("page_change_request")); +} + +void EditorInspectorArray::set_undo_redo(UndoRedo *p_undo_redo) { + undo_redo = p_undo_redo; +} + +void EditorInspectorArray::setup_with_move_element_function(Object *p_object, String p_label, const StringName &p_array_element_prefix, int p_page, const Color &p_bg_color, bool p_foldable) { + count_property = ""; + mode = MODE_USE_MOVE_ARRAY_ELEMENT_FUNCTION; + array_element_prefix = p_array_element_prefix; + page = p_page; + + EditorInspectorSection::setup(String(p_array_element_prefix) + "_array", p_label, p_object, p_bg_color, p_foldable); + + _setup(); +} + +void EditorInspectorArray::setup_with_count_property(Object *p_object, String p_label, const StringName &p_count_property, const StringName &p_array_element_prefix, int p_page, const Color &p_bg_color, bool p_foldable) { + count_property = p_count_property; + mode = MODE_USE_COUNT_PROPERTY; + array_element_prefix = p_array_element_prefix; + page = p_page; + + EditorInspectorSection::setup(String(count_property) + "_array", p_label, p_object, p_bg_color, p_foldable); + + _setup(); +} + +VBoxContainer *EditorInspectorArray::get_vbox(int p_index) { + if (p_index >= begin_array_index && p_index < end_array_index) { + return array_elements[p_index - begin_array_index].vbox; + } else if (p_index < 0) { + return vbox; + } else { + return nullptr; + } +} + +EditorInspectorArray::EditorInspectorArray() { + set_mouse_filter(Control::MOUSE_FILTER_STOP); + + odd_style.instantiate(); + even_style.instantiate(); + + rmb_popup = memnew(PopupMenu); + rmb_popup->add_item(TTR("Move Up"), OPTION_MOVE_UP); + rmb_popup->add_item(TTR("Move Down"), OPTION_MOVE_DOWN); + rmb_popup->add_separator(); + rmb_popup->add_item(TTR("Insert New Before"), OPTION_NEW_BEFORE); + rmb_popup->add_item(TTR("Insert New After"), OPTION_NEW_AFTER); + rmb_popup->add_separator(); + rmb_popup->add_item(TTR("Remove"), OPTION_REMOVE); + rmb_popup->add_separator(); + rmb_popup->add_item(TTR("Clear Array"), OPTION_CLEAR_ARRAY); + rmb_popup->add_item(TTR("Resize Array..."), OPTION_RESIZE_ARRAY); + rmb_popup->connect("id_pressed", callable_mp(this, &EditorInspectorArray::_rmb_popup_id_pressed)); + add_child(rmb_popup); + + elements_vbox = memnew(VBoxContainer); + elements_vbox->add_theme_constant_override("separation", 0); + vbox->add_child(elements_vbox); + + add_button = memnew(Button); + add_button->set_text(TTR("Add Element")); + add_button->set_text_align(Button::ALIGN_CENTER); + add_button->connect("pressed", callable_mp(this, &EditorInspectorArray::_add_button_pressed)); + vbox->add_child(add_button); + + hbox_pagination = memnew(HBoxContainer); + hbox_pagination->set_h_size_flags(SIZE_EXPAND_FILL); + hbox_pagination->set_alignment(HBoxContainer::ALIGN_CENTER); + vbox->add_child(hbox_pagination); + + first_page_button = memnew(Button); + first_page_button->set_flat(true); + first_page_button->connect("pressed", callable_mp(this, &EditorInspectorArray::_first_page_button_pressed)); + hbox_pagination->add_child(first_page_button); + + prev_page_button = memnew(Button); + prev_page_button->set_flat(true); + prev_page_button->connect("pressed", callable_mp(this, &EditorInspectorArray::_prev_page_button_pressed)); + hbox_pagination->add_child(prev_page_button); + + page_line_edit = memnew(LineEdit); + page_line_edit->connect("text_submitted", callable_mp(this, &EditorInspectorArray::_page_line_edit_text_submitted)); + page_line_edit->add_theme_constant_override("minimum_character_width", 2); + hbox_pagination->add_child(page_line_edit); + + page_count_label = memnew(Label); + hbox_pagination->add_child(page_count_label); + next_page_button = memnew(Button); + next_page_button->set_flat(true); + next_page_button->connect("pressed", callable_mp(this, &EditorInspectorArray::_next_page_button_pressed)); + hbox_pagination->add_child(next_page_button); + + last_page_button = memnew(Button); + last_page_button->set_flat(true); + last_page_button->connect("pressed", callable_mp(this, &EditorInspectorArray::_last_page_button_pressed)); + hbox_pagination->add_child(last_page_button); + + control_dropping = memnew(Control); + control_dropping->connect("draw", callable_mp(this, &EditorInspectorArray::_control_dropping_draw)); + control_dropping->set_mouse_filter(Control::MOUSE_FILTER_IGNORE); + add_child(control_dropping); + + resize_dialog = memnew(AcceptDialog); + resize_dialog->set_title(TTRC("Resize Array")); + resize_dialog->add_cancel_button(); + resize_dialog->connect("confirmed", callable_mp(this, &EditorInspectorArray::_resize_dialog_confirmed)); + add_child(resize_dialog); + + VBoxContainer *resize_dialog_vbox = memnew(VBoxContainer); + resize_dialog->add_child(resize_dialog_vbox); + + new_size_line_edit = memnew(LineEdit); + new_size_line_edit->connect("text_changed", callable_mp(this, &EditorInspectorArray::_new_size_line_edit_text_changed)); + new_size_line_edit->connect("text_submitted", callable_mp(this, &EditorInspectorArray::_new_size_line_edit_text_submitted)); + resize_dialog_vbox->add_margin_child(TTRC("New Size:"), new_size_line_edit); + + vbox->connect("visibility_changed", callable_mp(this, &EditorInspectorArray::_vbox_visibility_changed)); +} + +//////////////////////////////////////////////// +//////////////////////////////////////////////// Ref<EditorInspectorPlugin> EditorInspector::inspector_plugins[MAX_PLUGINS]; int EditorInspector::inspector_plugin_count = 0; @@ -1640,18 +2368,17 @@ void EditorInspector::update_tree() { valid_plugins.push_back(inspector_plugins[i]); } + // Decide if properties should be drawn in red. bool draw_red = false; - if (is_inside_tree()) { Node *nod = Object::cast_to<Node>(object); Node *es = EditorNode::get_singleton()->get_edited_scene(); if (nod && es != nod && nod->get_owner() != es) { + // Draw in red edited nodes that are not in the currently edited scene. draw_red = true; } } - // TreeItem *current_category = nullptr; - String filter = search_box ? search_box->get_text() : ""; String group; String group_base; @@ -1663,30 +2390,30 @@ void EditorInspector::update_tree() { object->get_property_list(&plist, true); _update_script_class_properties(*object, plist); - HashMap<String, VBoxContainer *> item_path; - Map<VBoxContainer *, EditorInspectorSection *> section_map; - - item_path[""] = main_vbox; + Map<VBoxContainer *, HashMap<String, VBoxContainer *>> vbox_per_path; + Map<String, EditorInspectorArray *> editor_inspector_array_per_prefix; Color sscolor = get_theme_color(SNAME("prop_subsection"), SNAME("Editor")); + // Get the lists of editors to add the beginning. for (Ref<EditorInspectorPlugin> &ped : valid_plugins) { ped->parse_begin(object); _parse_added_editors(main_vbox, ped); } - for (List<PropertyInfo>::Element *I = plist.front(); I; I = I->next()) { - PropertyInfo &p = I->get(); - - //make sure the property can be edited + // Get the lists of editors for properties. + for (List<PropertyInfo>::Element *E_property = plist.front(); E_property; E_property = E_property->next()) { + PropertyInfo &p = E_property->get(); if (p.usage & PROPERTY_USAGE_SUBGROUP) { + // Setup a property sub-group. subgroup = p.name; subgroup_base = p.hint_string; continue; } else if (p.usage & PROPERTY_USAGE_GROUP) { + // Setup a property group. group = p.name; group_base = p.hint_string; subgroup = ""; @@ -1695,6 +2422,7 @@ void EditorInspector::update_tree() { continue; } else if (p.usage & PROPERTY_USAGE_CATEGORY) { + // Setup a property category. group = ""; group_base = ""; subgroup = ""; @@ -1704,9 +2432,9 @@ void EditorInspector::update_tree() { continue; } - List<PropertyInfo>::Element *N = I->next(); + // Iterate over remaining properties. If no properties in category, skip the category. + List<PropertyInfo>::Element *N = E_property->next(); bool valid = true; - //if no properties in category, skip while (N) { if (N->get().usage & PROPERTY_USAGE_EDITOR && (!restrict_to_basic || (N->get().usage & PROPERTY_USAGE_EDITOR_BASIC_SETTING))) { break; @@ -1718,28 +2446,32 @@ void EditorInspector::update_tree() { N = N->next(); } if (!valid) { - continue; //empty, ignore + continue; // Empty, ignore it. } + // Create an EditorInspectorCategory and add it to the inspector. EditorInspectorCategory *category = memnew(EditorInspectorCategory); main_vbox->add_child(category); category_vbox = nullptr; //reset String type = p.name; + + // Set the category icon. if (!ClassDB::class_exists(type) && !ScriptServer::is_global_class(type) && p.hint_string.length() && FileAccess::exists(p.hint_string)) { - Ref<Script> s = ResourceLoader::load(p.hint_string, "Script"); + // If we have a category inside a script, search for the first script with a valid icon. + Ref<Script> script = ResourceLoader::load(p.hint_string, "Script"); String base_type; - if (s.is_valid()) { - base_type = s->get_instance_base_type(); + if (script.is_valid()) { + base_type = script->get_instance_base_type(); } - while (s.is_valid()) { - StringName name = EditorNode::get_editor_data().script_class_get_name(s->get_path()); + while (script.is_valid()) { + StringName name = EditorNode::get_editor_data().script_class_get_name(script->get_path()); String icon_path = EditorNode::get_editor_data().script_class_get_icon_path(name); if (name != StringName() && icon_path.length()) { category->icon = ResourceLoader::load(icon_path, "Texture"); break; } - s = s->get_base_script(); + script = script->get_base_script(); } if (category->icon.is_null() && has_theme_icon(base_type, SNAME("EditorIcons"))) { category->icon = get_theme_icon(base_type, SNAME("EditorIcons")); @@ -1750,9 +2482,12 @@ void EditorInspector::update_tree() { category->icon = EditorNode::get_singleton()->get_class_icon(type, "Object"); } } + + // Set the category label. category->label = type; if (use_doc_hints) { + // Sets the category tooltip to show documentation. StringName type2 = p.name; if (!class_descr_cache.has(type2)) { String descr; @@ -1767,6 +2502,7 @@ void EditorInspector::update_tree() { category->set_tooltip(p.name + "::" + (class_descr_cache[type2] == "" ? "" : class_descr_cache[type2])); } + // Add editors at the start of a category. for (Ref<EditorInspectorPlugin> &ped : valid_plugins) { ped->parse_category(object, p.name); _parse_added_editors(main_vbox, ped); @@ -1775,134 +2511,215 @@ void EditorInspector::update_tree() { continue; } else if (!(p.usage & PROPERTY_USAGE_EDITOR) || _is_property_disabled_by_feature_profile(p.name) || (restrict_to_basic && !(p.usage & PROPERTY_USAGE_EDITOR_BASIC_SETTING))) { + // Ignore properties that are not supposed to be in the inspector. continue; } if (p.name == "script") { - category_vbox = nullptr; // script should go into its own category + // Script should go into its own category. + category_vbox = nullptr; } if (p.usage & PROPERTY_USAGE_HIGH_END_GFX && RS::get_singleton()->is_low_end()) { - continue; //do not show this property in low end gfx + // Do not show this property in low end gfx. + continue; } if (p.name == "script" && (hide_script || bool(object->call("_hide_script_from_inspector")))) { + // Hide script variables from inspector if required. continue; } - String basename = p.name; + // Get the path for property. + String path = p.name; - if (subgroup != "") { - if (subgroup_base != "") { - if (basename.begins_with(subgroup_base)) { - basename = basename.replace_first(subgroup_base, ""); - } else if (subgroup_base.begins_with(basename)) { - //keep it, this is used pretty often - } else { - subgroup = ""; //no longer using subgroup base, clear + // First check if we have an array that fits the prefix. + String array_prefix = ""; + int array_index = -1; + for (Map<String, EditorInspectorArray *>::Element *E = editor_inspector_array_per_prefix.front(); E; E = E->next()) { + if (p.name.begins_with(E->key()) && E->key().length() > array_prefix.length()) { + array_prefix = E->key(); + } + } + + if (!array_prefix.is_empty()) { + // If we have an array element, find the according index in array. + String str = p.name.trim_prefix(array_prefix); + int to_char_index = 0; + while (to_char_index < str.length()) { + if (str[to_char_index] < '0' || str[to_char_index] > '9') { + break; } + to_char_index++; + } + if (to_char_index > 0) { + array_index = str.left(to_char_index).to_int(); + } else { + array_prefix = ""; } } - if (group != "") { - if (group_base != "" && subgroup == "") { - if (basename.begins_with(group_base)) { - basename = basename.replace_first(group_base, ""); - } else if (group_base.begins_with(basename)) { - //keep it, this is used pretty often + + if (!array_prefix.is_empty()) { + path = path.trim_prefix(array_prefix); + int char_index = path.find("/"); + if (char_index >= 0) { + path = path.right(-char_index - 1); + } else { + path = vformat(TTR("Element %s"), array_index); + } + } else { + // Check if we exit or not a subgroup. If there is a prefix, remove it from the property label string. + if (subgroup != "" && subgroup_base != "") { + if (path.begins_with(subgroup_base)) { + path = path.trim_prefix(subgroup_base); + } else if (subgroup_base.begins_with(path)) { + // Keep it, this is used pretty often. } else { - group = ""; //no longer using group base, clear + subgroup = ""; // The prefix changed, we are no longer in the subgroup. + } + } + + // Check if we exit or not a group. If there is a prefix, remove it from the property label string. + if (group != "" && group_base != "" && subgroup == "") { + if (path.begins_with(group_base)) { + path = path.trim_prefix(group_base); + } else if (group_base.begins_with(path)) { + // Keep it, this is used pretty often. + } else { + group = ""; // The prefix changed, we are no longer in the group. subgroup = ""; } } - } - if (subgroup != "") { - basename = subgroup + "/" + basename; - } - if (group != "") { - basename = group + "/" + basename; - } - String name = (basename.find("/") != -1) ? basename.substr(basename.rfind("/") + 1) : basename; + // Add the group and subgroup to the path. + if (subgroup != "") { + path = subgroup + "/" + path; + } + if (group != "") { + path = group + "/" + path; + } + } + // Get the property label's string. + String property_label_string = (path.find("/") != -1) ? path.substr(path.rfind("/") + 1) : path; if (capitalize_paths) { - int dot = name.find("."); + // Capitalize paths. + int dot = property_label_string.find("."); if (dot != -1) { - String ov = name.substr(dot); - name = name.substr(0, dot); - name = name.capitalize(); - name += ov; - + String ov = property_label_string.substr(dot); + property_label_string = property_label_string.substr(0, dot); + property_label_string = property_label_string.capitalize(); + property_label_string += ov; } else { - name = name.capitalize(); + property_label_string = property_label_string.capitalize(); } } - String path; - { - int idx = basename.rfind("/"); - if (idx > -1) { - path = basename.left(idx); - } + // Remove the property from the path. + int idx = path.rfind("/"); + if (idx > -1) { + path = path.left(idx); + } else { + path = ""; } + // Ignore properties that do not fit the filter. if (use_filter && filter != "") { - String cat = path; - - if (capitalize_paths) { - cat = cat.capitalize(); - } - - if (!filter.is_subsequence_ofi(cat) && !filter.is_subsequence_ofi(name) && property_prefix.to_lower().find(filter.to_lower()) == -1) { + if (!filter.is_subsequence_ofi(path) && !filter.is_subsequence_ofi(property_label_string) && property_prefix.to_lower().find(filter.to_lower()) == -1) { continue; } } + // Recreate the category vbox if it was reset. if (category_vbox == nullptr) { category_vbox = memnew(VBoxContainer); main_vbox->add_child(category_vbox); } - VBoxContainer *current_vbox = main_vbox; + // Find the correct section/vbox to add the property editor to. + VBoxContainer *root_vbox = array_prefix.is_empty() ? main_vbox : editor_inspector_array_per_prefix[array_prefix]->get_vbox(array_index); + if (!root_vbox) { + continue; + } - { - String acc_path = ""; - int level = 1; - for (int i = 0; i < path.get_slice_count("/"); i++) { - String path_name = path.get_slice("/", i); - if (i > 0) { - acc_path += "/"; - } - acc_path += path_name; - if (!item_path.has(acc_path)) { - EditorInspectorSection *section = memnew(EditorInspectorSection); - current_vbox->add_child(section); - sections.push_back(section); - - if (capitalize_paths) { - path_name = path_name.capitalize(); - } + if (!vbox_per_path.has(root_vbox)) { + vbox_per_path[root_vbox] = HashMap<String, VBoxContainer *>(); + vbox_per_path[root_vbox][""] = root_vbox; + } + + VBoxContainer *current_vbox = root_vbox; + String acc_path = ""; + int level = 1; + + Vector<String> components = path.split("/"); + for (int i = 0; i < components.size(); i++) { + String component = components[i]; + acc_path += (i > 0) ? "/" + component : component; - Color c = sscolor; - c.a /= level; - section->setup(acc_path, path_name, object, c, use_folding); + if (!vbox_per_path[root_vbox].has(acc_path)) { + // If the section does not exists, create it. + EditorInspectorSection *section = memnew(EditorInspectorSection); + current_vbox->add_child(section); + sections.push_back(section); - VBoxContainer *vb = section->get_vbox(); - item_path[acc_path] = vb; - section_map[vb] = section; + if (capitalize_paths) { + component = component.capitalize(); } - current_vbox = item_path[acc_path]; - level = (MIN(level + 1, 4)); - } - if (current_vbox == main_vbox) { - //do not add directly to the main vbox, given it has no spacing - if (category_vbox == nullptr) { - category_vbox = memnew(VBoxContainer); + Color c = sscolor; + c.a /= level; + section->setup(acc_path, component, object, c, use_folding); + + vbox_per_path[root_vbox][acc_path] = section->get_vbox(); + } + + current_vbox = vbox_per_path[root_vbox][acc_path]; + level = (MIN(level + 1, 4)); + } + + // If we did not find a section to add the property to, add it to the category vbox instead (the category vbox handles margins correctly). + if (current_vbox == main_vbox) { + current_vbox = category_vbox; + } + + // Check if the property is an array counter, if so create a dedicated array editor for the array. + if (p.usage & PROPERTY_USAGE_ARRAY) { + EditorInspectorArray *editor_inspector_array = nullptr; + StringName array_element_prefix; + Color c = sscolor; + c.a /= level; + if (p.type == Variant::NIL) { + // Setup the array to use a method to create/move/delete elements. + array_element_prefix = p.class_name; + editor_inspector_array = memnew(EditorInspectorArray); + + String array_label = (path.find("/") != -1) ? path.substr(path.rfind("/") + 1) : path; + array_label = property_label_string.capitalize(); + int page = per_array_page.has(array_element_prefix) ? per_array_page[array_element_prefix] : 0; + editor_inspector_array->setup_with_move_element_function(object, array_label, array_element_prefix, page, c, use_folding); + editor_inspector_array->connect("page_change_request", callable_mp(this, &EditorInspector::_page_change_request), varray(array_element_prefix)); + editor_inspector_array->set_undo_redo(undo_redo); + } else if (p.type == Variant::INT) { + // Setup the array to use the count property and built-in functions to create/move/delete elements. + Vector<String> class_name_components = String(p.class_name).split(","); + if (class_name_components.size() == 2) { + array_element_prefix = class_name_components[1]; + editor_inspector_array = memnew(EditorInspectorArray); + int page = per_array_page.has(array_element_prefix) ? per_array_page[array_element_prefix] : 0; + editor_inspector_array->setup_with_count_property(object, class_name_components[0], p.name, array_element_prefix, page, c, use_folding); + editor_inspector_array->connect("page_change_request", callable_mp(this, &EditorInspector::_page_change_request), varray(array_element_prefix)); + editor_inspector_array->set_undo_redo(undo_redo); } - current_vbox = category_vbox; } + + if (editor_inspector_array) { + current_vbox->add_child(editor_inspector_array); + editor_inspector_array_per_prefix[array_element_prefix] = editor_inspector_array; + } + continue; } + // Checkable and checked properties. bool checkable = false; bool checked = false; if (p.usage & PROPERTY_USAGE_CHECKABLE) { @@ -1910,6 +2727,9 @@ void EditorInspector::update_tree() { checked = p.usage & PROPERTY_USAGE_CHECKED; } + bool property_read_only = (p.usage & PROPERTY_USAGE_READ_ONLY) || read_only; + + // Mark properties that would require an editor restart (mostly when editing editor settings). if (p.usage & PROPERTY_USAGE_RESTART_IF_CHANGED) { restart_request_props.insert(p.name); } @@ -1917,14 +2737,19 @@ void EditorInspector::update_tree() { String doc_hint; if (use_doc_hints) { + // Build the doc hint, to use as tooltip. + + // Get the class name. StringName classname = object->get_class_name(); if (object_class != String()) { classname = object_class; } + StringName propname = property_prefix + p.name; String descr; bool found = false; + // Search for the property description in the cache. Map<StringName, Map<StringName, String>>::Element *E = descr_cache.find(classname); if (E) { Map<StringName, String>::Element *F = E->get().find(propname); @@ -1935,6 +2760,7 @@ void EditorInspector::update_tree() { } if (!found) { + // Build the property description String and add it to the cache. DocTools *dd = EditorHelp::get_doc_data(); Map<String, DocData::ClassDoc>::Element *F = dd->class_list.find(classname); while (F && descr == String()) { @@ -1967,17 +2793,18 @@ void EditorInspector::update_tree() { doc_hint = descr; } + // Seach for the inspector plugin that will handle the properties. Then add the correct property editor to it. for (Ref<EditorInspectorPlugin> &ped : valid_plugins) { bool exclusive = ped->parse_property(object, p.type, p.name, p.hint, p.hint_string, p.usage, wide_editors); - List<EditorInspectorPlugin::AddedEditor> editors = ped->added_editors; //make a copy, since plugins may be used again in a sub-inspector + List<EditorInspectorPlugin::AddedEditor> editors = ped->added_editors; // Make a copy, since plugins may be used again in a sub-inspector. ped->added_editors.clear(); for (const EditorInspectorPlugin::AddedEditor &F : editors) { EditorProperty *ep = Object::cast_to<EditorProperty>(F.property_editor); if (ep) { - //set all this before the control gets the ENTER_TREE notification + // Set all this before the control gets the ENTER_TREE notification. ep->object = object; if (F.properties.size()) { @@ -1992,7 +2819,7 @@ void EditorInspector::update_tree() { ep->set_label(F.label); } else { // Use the existing one. - ep->set_label(name); + ep->set_label(property_label_string); } for (int i = 0; i < F.properties.size(); i++) { String prop = F.properties[i]; @@ -2008,18 +2835,16 @@ void EditorInspector::update_tree() { ep->set_checkable(checkable); ep->set_checked(checked); ep->set_keying(keying); - - ep->set_read_only(read_only); + ep->set_read_only(property_read_only); ep->set_deletable(deletable_properties); } current_vbox->add_child(F.property_editor); if (ep) { - ep->connect("property_changed", callable_mp(this, &EditorInspector::_property_changed)); - if (p.usage & PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED) { - ep->connect("property_changed", callable_mp(this, &EditorInspector::_property_changed_update_all), varray(), CONNECT_DEFERRED); - } + // Eventually, set other properties/signals after the property editor got added to the tree. + bool update_all = (p.usage & PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED); + ep->connect("property_changed", callable_mp(this, &EditorInspector::_property_changed), varray(update_all)); ep->connect("property_keyed", callable_mp(this, &EditorInspector::_property_keyed)); ep->connect("property_deleted", callable_mp(this, &EditorInspector::_property_deleted), varray(), CONNECT_DEFERRED); ep->connect("property_keyed_with_value", callable_mp(this, &EditorInspector::_property_keyed_with_value)); @@ -2044,17 +2869,17 @@ void EditorInspector::update_tree() { } if (exclusive) { + // If we know the plugin is exclusive, we don't need to go through other plugins. break; } } } + // Get the lists of to add at the end. for (Ref<EditorInspectorPlugin> &ped : valid_plugins) { ped->parse_end(); _parse_added_editors(main_vbox, ped); } - - //see if this property exists and should be kept } void EditorInspector::update_property(const String &p_prop) { @@ -2093,6 +2918,7 @@ void EditorInspector::edit(Object *p_object) { _clear(); object->disconnect("property_list_changed", callable_mp(this, &EditorInspector::_changed_callback)); } + per_array_page.clear(); object = p_object; @@ -2238,6 +3064,15 @@ void EditorInspector::set_use_deletable_properties(bool p_enabled) { deletable_properties = p_enabled; } +void EditorInspector::_page_change_request(int p_new_page, const StringName &p_array_prefix) { + int prev_page = per_array_page.has(p_array_prefix) ? per_array_page[p_array_prefix] : 0; + int new_page = MAX(0, p_new_page); + if (new_page != prev_page) { + per_array_page[p_array_prefix] = new_page; + update_tree_pending = true; + } +} + void EditorInspector::_edit_request_change(Object *p_object, const String &p_property) { if (object != p_object) { //may be undoing/redoing for a non edited object, so ignore return; @@ -2338,14 +3173,14 @@ void EditorInspector::_edit_set(const String &p_name, const Variant &p_value, bo } } -void EditorInspector::_property_changed(const String &p_path, const Variant &p_value, const String &p_name, bool p_changing) { +void EditorInspector::_property_changed(const String &p_path, const Variant &p_value, const String &p_name, bool p_changing, bool p_update_all) { // The "changing" variable must be true for properties that trigger events as typing occurs, // like "text_changed" signal. E.g. text property of Label, Button, RichTextLabel, etc. if (p_changing) { this->changing++; } - _edit_set(p_path, p_value, false, p_name); + _edit_set(p_path, p_value, p_update_all, p_name); if (p_changing) { this->changing--; @@ -2356,11 +3191,7 @@ void EditorInspector::_property_changed(const String &p_path, const Variant &p_v } } -void EditorInspector::_property_changed_update_all(const String &p_path, const Variant &p_value, const String &p_name, bool p_changing) { - update_tree(); -} - -void EditorInspector::_multiple_properties_changed(Vector<String> p_paths, Array p_values) { +void EditorInspector::_multiple_properties_changed(Vector<String> p_paths, Array p_values, bool p_changing) { ERR_FAIL_COND(p_paths.size() == 0 || p_values.size() == 0); ERR_FAIL_COND(p_paths.size() != p_values.size()); String names; @@ -2377,9 +3208,13 @@ void EditorInspector::_multiple_properties_changed(Vector<String> p_paths, Array emit_signal(SNAME("restart_requested")); } } - changing++; + if (p_changing) { + changing++; + } undo_redo->commit_action(); - changing--; + if (p_changing) { + changing--; + } } void EditorInspector::_property_keyed(const String &p_path, bool p_advance) { diff --git a/editor/editor_inspector.h b/editor/editor_inspector.h index 8c522f00ef..5992c23f8c 100644 --- a/editor/editor_inspector.h +++ b/editor/editor_inspector.h @@ -32,8 +32,12 @@ #define EDITOR_INSPECTOR_H #include "scene/gui/box_container.h" +#include "scene/gui/button.h" +#include "scene/gui/dialogs.h" #include "scene/gui/line_edit.h" +#include "scene/gui/panel_container.h" #include "scene/gui/scroll_container.h" +#include "scene/gui/texture_rect.h" class UndoRedo; @@ -115,6 +119,7 @@ private: protected: void _notification(int p_what); static void _bind_methods(); + virtual void _set_read_only(bool p_read_only); virtual void gui_input(const Ref<InputEvent> &p_event) override; virtual void unhandled_key_input(const Ref<InputEvent> &p_event) override; @@ -250,9 +255,7 @@ class EditorInspectorSection : public Container { String label; String section; - Object *object; - VBoxContainer *vbox; - bool vbox_added; //optimization + bool vbox_added; // Optimization. Color bg_color; bool foldable; @@ -262,6 +265,9 @@ class EditorInspectorSection : public Container { void _test_unfold(); protected: + Object *object; + VBoxContainer *vbox; + void _notification(int p_what); static void _bind_methods(); virtual void gui_input(const Ref<InputEvent> &p_event) override; @@ -280,6 +286,118 @@ public: ~EditorInspectorSection(); }; +class EditorInspectorArray : public EditorInspectorSection { + GDCLASS(EditorInspectorArray, EditorInspectorSection); + + UndoRedo *undo_redo; + + enum Mode { + MODE_NONE, + MODE_USE_COUNT_PROPERTY, + MODE_USE_MOVE_ARRAY_ELEMENT_FUNCTION, + } mode; + StringName count_property; + StringName array_element_prefix; + + int count = 0; + + VBoxContainer *elements_vbox; + + Control *control_dropping; + bool dropping = false; + + Button *add_button; + + AcceptDialog *resize_dialog; + int new_size = 0; + LineEdit *new_size_line_edit; + + // Pagination + int page_lenght = 5; + int page = 0; + int max_page = 0; + int begin_array_index = 0; + int end_array_index = 0; + HBoxContainer *hbox_pagination; + Button *first_page_button; + Button *prev_page_button; + LineEdit *page_line_edit; + Label *page_count_label; + Button *next_page_button; + Button *last_page_button; + + enum MenuOptions { + OPTION_MOVE_UP = 0, + OPTION_MOVE_DOWN, + OPTION_NEW_BEFORE, + OPTION_NEW_AFTER, + OPTION_REMOVE, + OPTION_CLEAR_ARRAY, + OPTION_RESIZE_ARRAY, + }; + int popup_array_index_pressed = -1; + PopupMenu *rmb_popup; + + struct ArrayElement { + PanelContainer *panel; + MarginContainer *margin; + HBoxContainer *hbox; + TextureRect *move_texture_rect; + VBoxContainer *vbox; + }; + LocalVector<ArrayElement> array_elements; + + Ref<StyleBoxFlat> odd_style; + Ref<StyleBoxFlat> even_style; + + int _get_array_count(); + void _add_button_pressed(); + + void _first_page_button_pressed(); + void _prev_page_button_pressed(); + void _page_line_edit_text_submitted(String p_text); + void _next_page_button_pressed(); + void _last_page_button_pressed(); + + void _rmb_popup_id_pressed(int p_id); + + void _control_dropping_draw(); + + void _vbox_visibility_changed(); + + void _panel_draw(int p_index); + void _panel_gui_input(Ref<InputEvent> p_event, int p_index); + void _move_element(int p_element_index, int p_to_pos); + void _clear_array(); + void _resize_array(int p_size); + Array _extract_properties_as_array(const List<PropertyInfo> &p_list); + int _drop_position() const; + + void _new_size_line_edit_text_changed(String p_text); + void _new_size_line_edit_text_submitted(String p_text); + void _resize_dialog_confirmed(); + + void _update_elements_visibility(); + void _setup(); + + Variant get_drag_data_fw(const Point2 &p_point, Control *p_from); + void drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from); + bool can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const; + +protected: + void _notification(int p_what); + static void _bind_methods(); + +public: + void set_undo_redo(UndoRedo *p_undo_redo); + + void setup_with_move_element_function(Object *p_object, String p_label, const StringName &p_array_element_prefix, int p_page, const Color &p_bg_color, bool p_foldable); + void setup_with_count_property(Object *p_object, String p_label, const StringName &p_count_property, const StringName &p_array_element_prefix, int p_page, const Color &p_bg_color, bool p_foldable); + VBoxContainer *get_vbox(int p_index); + + EditorInspectorArray(); +}; + class EditorInspector : public ScrollContainer { GDCLASS(EditorInspector, ScrollContainer); @@ -339,9 +457,8 @@ class EditorInspector : public ScrollContainer { void _edit_set(const String &p_name, const Variant &p_value, bool p_refresh_all, const String &p_changed_field); - void _property_changed(const String &p_path, const Variant &p_value, const String &p_name = "", bool p_changing = false); - void _property_changed_update_all(const String &p_path, const Variant &p_value, const String &p_name = "", bool p_changing = false); - void _multiple_properties_changed(Vector<String> p_paths, Array p_values); + void _property_changed(const String &p_path, const Variant &p_value, const String &p_name = "", bool p_changing = false, bool p_update_all = false); + void _multiple_properties_changed(Vector<String> p_paths, Array p_values, bool p_changing = false); void _property_keyed(const String &p_path, bool p_advance); void _property_keyed_with_value(const String &p_path, const Variant &p_value, bool p_advance); void _property_deleted(const String &p_path); @@ -354,6 +471,9 @@ class EditorInspector : public ScrollContainer { void _node_removed(Node *p_node); + Map<StringName, int> per_array_page; + void _page_change_request(int p_new_page, const StringName &p_array_prefix); + void _changed_callback(); void _edit_request_change(Object *p_object, const String &p_prop); diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 9c7bddf037..cf4f8c0b7d 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -1856,7 +1856,7 @@ void EditorNode::_dialog_action(String p_file) { ml = Ref<MeshLibrary>(memnew(MeshLibrary)); } - MeshLibraryEditor::update_library_file(editor_data.get_edited_scene_root(), ml, true); + MeshLibraryEditor::update_library_file(editor_data.get_edited_scene_root(), ml, true, file_export_lib_apply_xforms->is_pressed()); Error err = ResourceSaver::save(p_file, ml); if (err) { @@ -2092,7 +2092,6 @@ void EditorNode::_edit_current() { Object *prev_inspected_object = get_inspector()->get_edited_object(); - bool capitalize = bool(EDITOR_GET("interface/inspector/capitalize_properties")); bool disable_folding = bool(EDITOR_GET("interface/inspector/disable_folding")); bool is_resource = current_obj->is_class("Resource"); bool is_node = current_obj->is_class("Node"); @@ -2150,7 +2149,6 @@ void EditorNode::_edit_current() { if (current_obj->is_class("EditorDebuggerRemoteObject")) { editable_warning = TTR("This is a remote object, so changes to it won't be kept.\nPlease read the documentation relevant to debugging to better understand this workflow."); - capitalize = false; disable_folding = true; } else if (current_obj->is_class("MultiNodeEdit")) { Node *scene = get_edited_scene(); @@ -2187,10 +2185,6 @@ void EditorNode::_edit_current() { inspector_dock->set_warning(editable_warning); - if (get_inspector()->is_capitalize_paths_enabled() != capitalize) { - get_inspector()->set_enable_capitalize_paths(capitalize); - } - if (get_inspector()->is_using_folding() == disable_folding) { get_inspector()->set_use_folding(!disable_folding); } @@ -6817,6 +6811,10 @@ EditorNode::EditorNode() { file_export_lib_merge->set_text(TTR("Merge With Existing")); file_export_lib_merge->set_pressed(true); file_export_lib->get_vbox()->add_child(file_export_lib_merge); + file_export_lib_apply_xforms = memnew(CheckBox); + file_export_lib_apply_xforms->set_text(TTR("Apply MeshInstance Transforms")); + file_export_lib_apply_xforms->set_pressed(false); + file_export_lib->get_vbox()->add_child(file_export_lib_apply_xforms); gui_base->add_child(file_export_lib); file_script = memnew(EditorFileDialog); diff --git a/editor/editor_node.h b/editor/editor_node.h index 03c18a8972..488957b1df 100644 --- a/editor/editor_node.h +++ b/editor/editor_node.h @@ -335,6 +335,7 @@ private: EditorFileDialog *file_script; EditorFileDialog *file_android_build_source; CheckBox *file_export_lib_merge; + CheckBox *file_export_lib_apply_xforms; String current_path; MenuButton *update_spinner; diff --git a/editor/editor_properties.cpp b/editor/editor_properties.cpp index 9507833746..1729705be5 100644 --- a/editor/editor_properties.cpp +++ b/editor/editor_properties.cpp @@ -51,6 +51,10 @@ EditorPropertyNil::EditorPropertyNil() { ///////////////////// TEXT ///////////////////////// +void EditorPropertyText::_set_read_only(bool p_read_only) { + text->set_editable(!p_read_only); +}; + void EditorPropertyText::_text_submitted(const String &p_string) { if (updating) { return; @@ -108,6 +112,11 @@ EditorPropertyText::EditorPropertyText() { ///////////////////// MULTILINE TEXT ///////////////////////// +void EditorPropertyMultilineText::_set_read_only(bool p_read_only) { + text->set_editable(!p_read_only); + open_big_text->set_disabled(p_read_only); +}; + void EditorPropertyMultilineText::_big_text_changed() { text->set_text(big_text->get_text()); emit_changed(get_edited_property(), big_text->get_text(), "", true); @@ -180,6 +189,11 @@ EditorPropertyMultilineText::EditorPropertyMultilineText() { ///////////////////// TEXT ENUM ///////////////////////// +void EditorPropertyTextEnum::_set_read_only(bool p_read_only) { + option_button->set_disabled(p_read_only); + edit_button->set_disabled(p_read_only); +}; + void EditorPropertyTextEnum::_emit_changed_value(String p_string) { if (string_name) { emit_changed(get_edited_property(), StringName(p_string)); @@ -328,6 +342,11 @@ EditorPropertyTextEnum::EditorPropertyTextEnum() { ///////////////////// PATH ///////////////////////// +void EditorPropertyPath::_set_read_only(bool p_read_only) { + path->set_editable(!p_read_only); + path_edit->set_disabled(p_read_only); +}; + void EditorPropertyPath::_path_selected(const String &p_path) { emit_changed(get_edited_property(), p_path); update_property(); @@ -420,6 +439,10 @@ EditorPropertyPath::EditorPropertyPath() { ///////////////////// CLASS NAME ///////////////////////// +void EditorPropertyClassName::_set_read_only(bool p_read_only) { + property->set_disabled(p_read_only); +}; + void EditorPropertyClassName::setup(const String &p_base_type, const String &p_selected_type) { base_type = p_base_type; dialog->set_base_type(base_type); @@ -461,6 +484,10 @@ EditorPropertyClassName::EditorPropertyClassName() { ///////////////////// MEMBER ///////////////////////// +void EditorPropertyMember::_set_read_only(bool p_read_only) { + property->set_disabled(p_read_only); +}; + void EditorPropertyMember::_property_selected(const String &p_selected) { emit_changed(get_edited_property(), p_selected); update_property(); @@ -557,6 +584,11 @@ EditorPropertyMember::EditorPropertyMember() { } ///////////////////// CHECK ///////////////////////// + +void EditorPropertyCheck::_set_read_only(bool p_read_only) { + checkbox->set_disabled(p_read_only); +}; + void EditorPropertyCheck::_checkbox_pressed() { emit_changed(get_edited_property(), checkbox->is_pressed()); } @@ -580,6 +612,10 @@ EditorPropertyCheck::EditorPropertyCheck() { ///////////////////// ENUM ///////////////////////// +void EditorPropertyEnum::_set_read_only(bool p_read_only) { + options->set_disabled(p_read_only); +}; + void EditorPropertyEnum::_option_selected(int p_which) { int64_t val = options->get_item_metadata(p_which); emit_changed(get_edited_property(), val); @@ -628,6 +664,12 @@ EditorPropertyEnum::EditorPropertyEnum() { ///////////////////// FLAGS ///////////////////////// +void EditorPropertyFlags::_set_read_only(bool p_read_only) { + for (CheckBox *check : flags) { + check->set_disabled(p_read_only); + } +}; + void EditorPropertyFlags::_flag_toggled() { uint32_t value = 0; for (int i = 0; i < flags.size(); i++) { @@ -698,6 +740,7 @@ private: bool expanded = false; int expansion_rows = 0; int hovered_index = -1; + bool read_only = false; Size2 get_grid_size() const { Ref<Font> font = get_theme_font(SNAME("font"), SNAME("Label")); @@ -712,6 +755,10 @@ public: Vector<String> names; Vector<String> tooltips; + void set_read_only(bool p_read_only) { + read_only = p_read_only; + } + virtual Size2 get_minimum_size() const override { Size2 min_size = get_grid_size(); @@ -736,6 +783,9 @@ public: } void gui_input(const Ref<InputEvent> &p_ev) override { + if (read_only) { + return; + } const Ref<InputEventMouseMotion> mm = p_ev; if (mm.is_valid()) { bool expand_was_hovered = expand_hovered; @@ -799,12 +849,12 @@ public: const int bsize = (grid_size.height * 80 / 100) / 2; const int h = bsize * 2 + 1; - Color color = get_theme_color(SNAME("highlight_color"), SNAME("Editor")); + Color color = get_theme_color(read_only ? SNAME("disabled_highlight_color") : SNAME("highlight_color"), SNAME("Editor")); - Color text_color = get_theme_color(SNAME("font_color"), SNAME("Editor")); + Color text_color = get_theme_color(read_only ? SNAME("disabled_font_color") : SNAME("font_color"), SNAME("Editor")); text_color.a *= 0.5; - Color text_color_on = get_theme_color(SNAME("font_hover_color"), SNAME("Editor")); + Color text_color_on = get_theme_color(read_only ? SNAME("disabled_font_color") : SNAME("font_hover_color"), SNAME("Editor")); text_color_on.a *= 0.7; const int vofs = (grid_size.height - h) / 2; @@ -935,6 +985,11 @@ public: } }; +void EditorPropertyLayers::_set_read_only(bool p_read_only) { + button->set_disabled(p_read_only); + grid->set_read_only(p_read_only); +}; + void EditorPropertyLayers::_grid_changed(uint32_t p_grid) { emit_changed(get_edited_property(), p_grid); } @@ -1071,6 +1126,10 @@ EditorPropertyLayers::EditorPropertyLayers() { ///////////////////// INT ///////////////////////// +void EditorPropertyInteger::_set_read_only(bool p_read_only) { + spin->set_read_only(p_read_only); +}; + void EditorPropertyInteger::_value_changed(int64_t val) { if (setting) { return; @@ -1113,6 +1172,10 @@ EditorPropertyInteger::EditorPropertyInteger() { ///////////////////// OBJECT ID ///////////////////////// +void EditorPropertyObjectID::_set_read_only(bool p_read_only) { + edit->set_disabled(p_read_only); +}; + void EditorPropertyObjectID::_edit_pressed() { emit_signal(SNAME("object_id_selected"), get_edited_property(), get_edited_object()->get(get_edited_property())); } @@ -1151,6 +1214,10 @@ EditorPropertyObjectID::EditorPropertyObjectID() { ///////////////////// FLOAT ///////////////////////// +void EditorPropertyFloat::_set_read_only(bool p_read_only) { + spin->set_read_only(p_read_only); +}; + void EditorPropertyFloat::_value_changed(double val) { if (setting) { return; @@ -1197,7 +1264,14 @@ EditorPropertyFloat::EditorPropertyFloat() { ///////////////////// EASING ///////////////////////// +void EditorPropertyEasing::_set_read_only(bool p_read_only) { + spin->set_read_only(p_read_only); +}; + void EditorPropertyEasing::_drag_easing(const Ref<InputEvent> &p_ev) { + if (is_read_only()) { + return; + } const Ref<InputEventMouseButton> mb = p_ev; if (mb.is_valid()) { if (mb->is_double_click() && mb->get_button_index() == MOUSE_BUTTON_LEFT) { @@ -1271,12 +1345,12 @@ void EditorPropertyEasing::_draw_easing() { const Ref<Font> f = get_theme_font(SNAME("font"), SNAME("Label")); int font_size = get_theme_font_size(SNAME("font_size"), SNAME("Label")); - const Color font_color = get_theme_color(SNAME("font_color"), SNAME("Label")); + const Color font_color = get_theme_color(is_read_only() ? SNAME("font_uneditable_color") : SNAME("font_color"), SNAME("LineEdit")); Color line_color; if (dragging) { line_color = get_theme_color(SNAME("accent_color"), SNAME("Editor")); } else { - line_color = get_theme_color(SNAME("font_color"), SNAME("Label")) * Color(1, 1, 1, 0.9); + line_color = get_theme_color(is_read_only() ? SNAME("font_uneditable_color") : SNAME("font_color"), SNAME("LineEdit")) * Color(1, 1, 1, 0.9); } Vector<Point2> points; @@ -1409,6 +1483,12 @@ EditorPropertyEasing::EditorPropertyEasing() { ///////////////////// VECTOR2 ///////////////////////// +void EditorPropertyVector2::_set_read_only(bool p_read_only) { + for (int i = 0; i < 2; i++) { + spin[i]->set_read_only(p_read_only); + } +}; + void EditorPropertyVector2::_value_changed(double val, const String &p_name) { if (setting) { return; @@ -1492,6 +1572,12 @@ EditorPropertyVector2::EditorPropertyVector2(bool p_force_wide) { ///////////////////// RECT2 ///////////////////////// +void EditorPropertyRect2::_set_read_only(bool p_read_only) { + for (int i = 0; i < 4; i++) { + spin[i]->set_read_only(p_read_only); + } +}; + void EditorPropertyRect2::_value_changed(double val, const String &p_name) { if (setting) { return; @@ -1589,6 +1675,12 @@ EditorPropertyRect2::EditorPropertyRect2(bool p_force_wide) { ///////////////////// VECTOR3 ///////////////////////// +void EditorPropertyVector3::_set_read_only(bool p_read_only) { + for (int i = 0; i < 3; i++) { + spin[i]->set_read_only(p_read_only); + } +}; + void EditorPropertyVector3::_value_changed(double val, const String &p_name) { if (setting) { return; @@ -1701,6 +1793,12 @@ EditorPropertyVector3::EditorPropertyVector3(bool p_force_wide) { ///////////////////// VECTOR2i ///////////////////////// +void EditorPropertyVector2i::_set_read_only(bool p_read_only) { + for (int i = 0; i < 2; i++) { + spin[i]->set_read_only(p_read_only); + } +}; + void EditorPropertyVector2i::_value_changed(double val, const String &p_name) { if (setting) { return; @@ -1784,6 +1882,12 @@ EditorPropertyVector2i::EditorPropertyVector2i(bool p_force_wide) { ///////////////////// RECT2i ///////////////////////// +void EditorPropertyRect2i::_set_read_only(bool p_read_only) { + for (int i = 0; i < 4; i++) { + spin[i]->set_read_only(p_read_only); + } +}; + void EditorPropertyRect2i::_value_changed(double val, const String &p_name) { if (setting) { return; @@ -1881,6 +1985,12 @@ EditorPropertyRect2i::EditorPropertyRect2i(bool p_force_wide) { ///////////////////// VECTOR3i ///////////////////////// +void EditorPropertyVector3i::_set_read_only(bool p_read_only) { + for (int i = 0; i < 3; i++) { + spin[i]->set_read_only(p_read_only); + } +}; + void EditorPropertyVector3i::_value_changed(double val, const String &p_name) { if (setting) { return; @@ -1965,6 +2075,12 @@ EditorPropertyVector3i::EditorPropertyVector3i(bool p_force_wide) { ///////////////////// PLANE ///////////////////////// +void EditorPropertyPlane::_set_read_only(bool p_read_only) { + for (int i = 0; i < 4; i++) { + spin[i]->set_read_only(p_read_only); + } +}; + void EditorPropertyPlane::_value_changed(double val, const String &p_name) { if (setting) { return; @@ -2052,6 +2168,12 @@ EditorPropertyPlane::EditorPropertyPlane(bool p_force_wide) { ///////////////////// QUATERNION ///////////////////////// +void EditorPropertyQuaternion::_set_read_only(bool p_read_only) { + for (int i = 0; i < 4; i++) { + spin[i]->set_read_only(p_read_only); + } +}; + void EditorPropertyQuaternion::_value_changed(double val, const String &p_name) { if (setting) { return; @@ -2136,6 +2258,12 @@ EditorPropertyQuaternion::EditorPropertyQuaternion() { ///////////////////// AABB ///////////////////////// +void EditorPropertyAABB::_set_read_only(bool p_read_only) { + for (int i = 0; i < 6; i++) { + spin[i]->set_read_only(p_read_only); + } +}; + void EditorPropertyAABB::_value_changed(double val, const String &p_name) { if (setting) { return; @@ -2213,6 +2341,12 @@ EditorPropertyAABB::EditorPropertyAABB() { ///////////////////// TRANSFORM2D ///////////////////////// +void EditorPropertyTransform2D::_set_read_only(bool p_read_only) { + for (int i = 0; i < 6; i++) { + spin[i]->set_read_only(p_read_only); + } +}; + void EditorPropertyTransform2D::_value_changed(double val, const String &p_name) { if (setting) { return; @@ -2289,6 +2423,12 @@ EditorPropertyTransform2D::EditorPropertyTransform2D() { ///////////////////// BASIS ///////////////////////// +void EditorPropertyBasis::_set_read_only(bool p_read_only) { + for (int i = 0; i < 9; i++) { + spin[i]->set_read_only(p_read_only); + } +}; + void EditorPropertyBasis::_value_changed(double val, const String &p_name) { if (setting) { return; @@ -2371,6 +2511,12 @@ EditorPropertyBasis::EditorPropertyBasis() { ///////////////////// TRANSFORM ///////////////////////// +void EditorPropertyTransform3D::_set_read_only(bool p_read_only) { + for (int i = 0; i < 12; i++) { + spin[i]->set_read_only(p_read_only); + } +}; + void EditorPropertyTransform3D::_value_changed(double val, const String &p_name) { if (setting) { return; @@ -2461,6 +2607,10 @@ EditorPropertyTransform3D::EditorPropertyTransform3D() { ////////////// COLOR PICKER ////////////////////// +void EditorPropertyColor::_set_read_only(bool p_read_only) { + picker->set_disabled(p_read_only); +}; + void EditorPropertyColor::_color_changed(const Color &p_color) { // Cancel the color change if the current color is identical to the new one. if (get_edited_object()->get(get_edited_property()) == p_color) { @@ -2533,6 +2683,11 @@ EditorPropertyColor::EditorPropertyColor() { ////////////// NODE PATH ////////////////////// +void EditorPropertyNodePath::_set_read_only(bool p_read_only) { + assign->set_disabled(p_read_only); + clear->set_disabled(p_read_only); +}; + void EditorPropertyNodePath::_node_selected(const NodePath &p_path) { NodePath path = p_path; Node *base_node = nullptr; @@ -2678,6 +2833,10 @@ EditorPropertyRID::EditorPropertyRID() { ////////////// RESOURCE ////////////////////// +void EditorPropertyResource::_set_read_only(bool p_read_only) { + resource_picker->set_editable(!p_read_only); +}; + void EditorPropertyResource::_resource_selected(const RES &p_resource) { if (use_sub_inspector) { bool unfold = !get_edited_object()->editor_is_section_unfolded(get_edited_property()); diff --git a/editor/editor_properties.h b/editor/editor_properties.h index 0cb21bb391..cee5ab96a7 100644 --- a/editor/editor_properties.h +++ b/editor/editor_properties.h @@ -59,6 +59,7 @@ class EditorPropertyText : public EditorProperty { void _text_submitted(const String &p_string); protected: + virtual void _set_read_only(bool p_read_only) override; static void _bind_methods(); public: @@ -81,6 +82,7 @@ class EditorPropertyMultilineText : public EditorProperty { void _open_big_text(); protected: + virtual void _set_read_only(bool p_read_only) override; void _notification(int p_what); static void _bind_methods(); @@ -115,6 +117,7 @@ class EditorPropertyTextEnum : public EditorProperty { void _custom_value_cancelled(); protected: + virtual void _set_read_only(bool p_read_only) override; static void _bind_methods(); void _notification(int p_what); @@ -139,6 +142,7 @@ class EditorPropertyPath : public EditorProperty { void _path_focus_exited(); protected: + virtual void _set_read_only(bool p_read_only) override; static void _bind_methods(); void _notification(int p_what); @@ -161,6 +165,7 @@ private: void _dialog_created(); protected: + virtual void _set_read_only(bool p_read_only) override; static void _bind_methods(); public: @@ -182,7 +187,6 @@ public: MEMBER_PROPERTY_OF_BASE_TYPE, ///< a property of a base type MEMBER_PROPERTY_OF_INSTANCE, ///< a property of an instance MEMBER_PROPERTY_OF_SCRIPT, ///< a property of a script & base - }; private: @@ -195,6 +199,7 @@ private: void _property_select(); protected: + virtual void _set_read_only(bool p_read_only) override; static void _bind_methods(); public: @@ -210,6 +215,7 @@ class EditorPropertyCheck : public EditorProperty { void _checkbox_pressed(); protected: + virtual void _set_read_only(bool p_read_only) override; static void _bind_methods(); public: @@ -224,6 +230,7 @@ class EditorPropertyEnum : public EditorProperty { void _option_selected(int p_which); protected: + virtual void _set_read_only(bool p_read_only) override; static void _bind_methods(); public: @@ -242,6 +249,7 @@ class EditorPropertyFlags : public EditorProperty { void _flag_toggled(); protected: + virtual void _set_read_only(bool p_read_only) override; static void _bind_methods(); public: @@ -276,6 +284,7 @@ private: void _menu_pressed(int p_menu); protected: + virtual void _set_read_only(bool p_read_only) override; static void _bind_methods(); public: @@ -291,6 +300,7 @@ class EditorPropertyInteger : public EditorProperty { void _value_changed(int64_t p_val); protected: + virtual void _set_read_only(bool p_read_only) override; static void _bind_methods(); public: @@ -306,6 +316,7 @@ class EditorPropertyObjectID : public EditorProperty { void _edit_pressed(); protected: + virtual void _set_read_only(bool p_read_only) override; static void _bind_methods(); public: @@ -322,6 +333,7 @@ class EditorPropertyFloat : public EditorProperty { void _value_changed(double p_val); protected: + virtual void _set_read_only(bool p_read_only) override; static void _bind_methods(); public: @@ -363,6 +375,7 @@ class EditorPropertyEasing : public EditorProperty { void _notification(int p_what); protected: + virtual void _set_read_only(bool p_read_only) override; static void _bind_methods(); public: @@ -378,6 +391,7 @@ class EditorPropertyVector2 : public EditorProperty { void _value_changed(double p_val, const String &p_name); protected: + virtual void _set_read_only(bool p_read_only) override; void _notification(int p_what); static void _bind_methods(); @@ -394,6 +408,7 @@ class EditorPropertyRect2 : public EditorProperty { void _value_changed(double p_val, const String &p_name); protected: + virtual void _set_read_only(bool p_read_only) override; void _notification(int p_what); static void _bind_methods(); @@ -411,6 +426,7 @@ class EditorPropertyVector3 : public EditorProperty { void _value_changed(double p_val, const String &p_name); protected: + virtual void _set_read_only(bool p_read_only) override; void _notification(int p_what); static void _bind_methods(); @@ -429,6 +445,7 @@ class EditorPropertyVector2i : public EditorProperty { void _value_changed(double p_val, const String &p_name); protected: + virtual void _set_read_only(bool p_read_only) override; void _notification(int p_what); static void _bind_methods(); @@ -445,6 +462,7 @@ class EditorPropertyRect2i : public EditorProperty { void _value_changed(double p_val, const String &p_name); protected: + virtual void _set_read_only(bool p_read_only) override; void _notification(int p_what); static void _bind_methods(); @@ -461,6 +479,7 @@ class EditorPropertyVector3i : public EditorProperty { void _value_changed(double p_val, const String &p_name); protected: + virtual void _set_read_only(bool p_read_only) override; void _notification(int p_what); static void _bind_methods(); @@ -477,6 +496,7 @@ class EditorPropertyPlane : public EditorProperty { void _value_changed(double p_val, const String &p_name); protected: + virtual void _set_read_only(bool p_read_only) override; void _notification(int p_what); static void _bind_methods(); @@ -493,6 +513,7 @@ class EditorPropertyQuaternion : public EditorProperty { void _value_changed(double p_val, const String &p_name); protected: + virtual void _set_read_only(bool p_read_only) override; void _notification(int p_what); static void _bind_methods(); @@ -509,6 +530,7 @@ class EditorPropertyAABB : public EditorProperty { void _value_changed(double p_val, const String &p_name); protected: + virtual void _set_read_only(bool p_read_only) override; void _notification(int p_what); static void _bind_methods(); @@ -525,6 +547,7 @@ class EditorPropertyTransform2D : public EditorProperty { void _value_changed(double p_val, const String &p_name); protected: + virtual void _set_read_only(bool p_read_only) override; void _notification(int p_what); static void _bind_methods(); @@ -541,6 +564,7 @@ class EditorPropertyBasis : public EditorProperty { void _value_changed(double p_val, const String &p_name); protected: + virtual void _set_read_only(bool p_read_only) override; void _notification(int p_what); static void _bind_methods(); @@ -557,6 +581,7 @@ class EditorPropertyTransform3D : public EditorProperty { void _value_changed(double p_val, const String &p_name); protected: + virtual void _set_read_only(bool p_read_only) override; void _notification(int p_what); static void _bind_methods(); @@ -578,6 +603,7 @@ class EditorPropertyColor : public EditorProperty { Color last_color; protected: + virtual void _set_read_only(bool p_read_only) override; static void _bind_methods(); public: @@ -600,6 +626,7 @@ class EditorPropertyNodePath : public EditorProperty { void _node_clear(); protected: + virtual void _set_read_only(bool p_read_only) override; static void _bind_methods(); void _notification(int p_what); @@ -644,6 +671,7 @@ class EditorPropertyResource : public EditorProperty { void _update_property_bg(); protected: + virtual void _set_read_only(bool p_read_only) override; static void _bind_methods(); void _notification(int p_what); diff --git a/editor/editor_spin_slider.cpp b/editor/editor_spin_slider.cpp index 3f65b101f7..8cd636ddf3 100644 --- a/editor/editor_spin_slider.cpp +++ b/editor/editor_spin_slider.cpp @@ -221,7 +221,7 @@ void EditorSpinSlider::_draw_spin_slider() { bool rtl = is_layout_rtl(); Vector2 size = get_size(); - Ref<StyleBox> sb = get_theme_stylebox(SNAME("normal"), SNAME("LineEdit")); + Ref<StyleBox> sb = get_theme_stylebox(is_read_only() ? SNAME("read_only") : SNAME("normal"), SNAME("LineEdit")); if (!flat) { draw_style_box(sb, Rect2(Vector2(), size)); } @@ -233,7 +233,7 @@ void EditorSpinSlider::_draw_spin_slider() { int label_width = font->get_string_size(label, font_size).width; int number_width = size.width - sb->get_minimum_size().width - label_width - sep; - Ref<Texture2D> updown = get_theme_icon(SNAME("updown"), SNAME("SpinBox")); + Ref<Texture2D> updown = get_theme_icon(is_read_only() ? SNAME("updown_disabled") : SNAME("updown"), SNAME("SpinBox")); if (get_step() == 1) { number_width -= updown->get_width(); @@ -243,7 +243,7 @@ void EditorSpinSlider::_draw_spin_slider() { int vofs = (size.height - font->get_height(font_size)) / 2 + font->get_ascent(font_size); - Color fc = get_theme_color(SNAME("font_color"), SNAME("LineEdit")); + Color fc = get_theme_color(is_read_only() ? SNAME("font_uneditable_color") : SNAME("font_color"), SNAME("LineEdit")); Color lc; if (use_custom_label_color) { lc = custom_label_color; @@ -299,7 +299,7 @@ void EditorSpinSlider::_draw_spin_slider() { TS->free(num_rid); if (get_step() == 1) { - Ref<Texture2D> updown2 = get_theme_icon(SNAME("updown"), SNAME("SpinBox")); + Ref<Texture2D> updown2 = get_theme_icon(is_read_only() ? SNAME("updown_disabled") : SNAME("updown"), SNAME("SpinBox")); int updown_vofs = (size.height - updown2->get_height()) / 2; if (rtl) { updown_offset = sb->get_margin(SIDE_LEFT); diff --git a/editor/editor_themes.cpp b/editor/editor_themes.cpp index 32ec49e11e..6e5b94dc07 100644 --- a/editor/editor_themes.cpp +++ b/editor/editor_themes.cpp @@ -395,12 +395,14 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { const Color separator_color = Color(mono_color.r, mono_color.g, mono_color.b, 0.1); const Color highlight_color = Color(accent_color.r, accent_color.g, accent_color.b, 0.275); + const Color disabled_highlight_color = highlight_color.lerp(dark_theme ? Color(0, 0, 0) : Color(1, 1, 1), 0.5); float prev_icon_saturation = theme->has_color("icon_saturation", "Editor") ? theme->get_color("icon_saturation", "Editor").r : 1.0; theme->set_color("icon_saturation", "Editor", Color(icon_saturation, icon_saturation, icon_saturation)); //can't save single float in theme, so using color theme->set_color("accent_color", "Editor", accent_color); theme->set_color("highlight_color", "Editor", highlight_color); + theme->set_color("disabled_highlight_color", "Editor", disabled_highlight_color); theme->set_color("base_color", "Editor", base_color); theme->set_color("dark_color_1", "Editor", dark_color_1); theme->set_color("dark_color_2", "Editor", dark_color_2); @@ -424,6 +426,8 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { Color warning_color = Color(1, 0.87, 0.4); Color error_color = Color(1, 0.47, 0.42); Color property_color = font_color.lerp(Color(0.5, 0.5, 0.5), 0.5); + Color readonly_color = property_color.lerp(dark_theme ? Color(0, 0, 0) : Color(1, 1, 1), 0.5); + Color readonly_error_color = error_color.lerp(dark_theme ? Color(0, 0, 0) : Color(1, 1, 1), 0.5); if (!dark_theme) { // Darken some colors to be readable on a light background @@ -436,6 +440,8 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { theme->set_color("warning_color", "Editor", warning_color); theme->set_color("error_color", "Editor", error_color); theme->set_color("property_color", "Editor", property_color); + theme->set_color("readonly_color", "Editor", readonly_color); + theme->set_color("readonly_error_color", "EditorProperty", readonly_error_color); if (!dark_theme) { theme->set_color("vulkan_color", "Editor", Color::hex(0xad1128ff)); @@ -696,6 +702,10 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { theme->set_icon("unchecked", "CheckBox", theme->get_icon("GuiUnchecked", "EditorIcons")); theme->set_icon("radio_checked", "CheckBox", theme->get_icon("GuiRadioChecked", "EditorIcons")); theme->set_icon("radio_unchecked", "CheckBox", theme->get_icon("GuiRadioUnchecked", "EditorIcons")); + theme->set_icon("checked_disabled", "CheckBox", theme->get_icon("GuiCheckedDisabled", "EditorIcons")); + theme->set_icon("unchecked_disabled", "CheckBox", theme->get_icon("GuiUncheckedDisabled", "EditorIcons")); + theme->set_icon("radio_checked_disabled", "CheckBox", theme->get_icon("GuiRadioCheckedDisabled", "EditorIcons")); + theme->set_icon("radio_unchecked_disabled", "CheckBox", theme->get_icon("GuiRadioUncheckedDisabled", "EditorIcons")); theme->set_color("font_color", "CheckBox", font_color); theme->set_color("font_hover_color", "CheckBox", font_hover_color); @@ -742,6 +752,10 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { theme->set_icon("unchecked", "PopupMenu", theme->get_icon("GuiUnchecked", "EditorIcons")); theme->set_icon("radio_checked", "PopupMenu", theme->get_icon("GuiRadioChecked", "EditorIcons")); theme->set_icon("radio_unchecked", "PopupMenu", theme->get_icon("GuiRadioUnchecked", "EditorIcons")); + theme->set_icon("checked_disabled", "PopupMenu", theme->get_icon("GuiCheckedDisabled", "EditorIcons")); + theme->set_icon("unchecked_disabled", "PopupMenu", theme->get_icon("GuiUncheckedDisabled", "EditorIcons")); + theme->set_icon("radio_checked_disabled", "PopupMenu", theme->get_icon("GuiRadioCheckedDisabled", "EditorIcons")); + theme->set_icon("radio_unchecked_disabled", "PopupMenu", theme->get_icon("GuiRadioUncheckedDisabled", "EditorIcons")); theme->set_icon("submenu", "PopupMenu", theme->get_icon("ArrowRight", "EditorIcons")); theme->set_icon("submenu_mirrored", "PopupMenu", theme->get_icon("ArrowLeft", "EditorIcons")); theme->set_icon("visibility_hidden", "PopupMenu", theme->get_icon("GuiVisibilityHidden", "EditorIcons")); @@ -803,6 +817,8 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { theme->set_constant("vseparation", "EditorProperty", (extra_spacing + default_margin_size) * EDSCALE); theme->set_color("error_color", "EditorProperty", error_color); theme->set_color("property_color", "EditorProperty", property_color); + theme->set_color("readonly_color", "EditorProperty", readonly_color); + theme->set_color("readonly_error_color", "EditorProperty", readonly_error_color); Color inspector_section_color = font_color.lerp(Color(0.5, 0.5, 0.5), 0.35); theme->set_color("font_color", "EditorInspectorSection", inspector_section_color); @@ -1052,7 +1068,7 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { theme->set_icon("tab", "TextEdit", theme->get_icon("GuiTab", "EditorIcons")); theme->set_icon("space", "TextEdit", theme->get_icon("GuiSpace", "EditorIcons")); theme->set_color("font_color", "TextEdit", font_color); - theme->set_color("font_readonly_color", "LineEdit", font_readonly_color); + theme->set_color("font_readonly_color", "TextEdit", font_readonly_color); theme->set_color("caret_color", "TextEdit", font_color); theme->set_color("selection_color", "TextEdit", selection_color); theme->set_constant("line_spacing", "TextEdit", 4 * EDSCALE); @@ -1138,8 +1154,10 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { theme->set_icon("increment", "HScrollBar", empty_icon); theme->set_icon("increment_highlight", "HScrollBar", empty_icon); + theme->set_icon("increment_pressed", "HScrollBar", empty_icon); theme->set_icon("decrement", "HScrollBar", empty_icon); theme->set_icon("decrement_highlight", "HScrollBar", empty_icon); + theme->set_icon("decrement_pressed", "HScrollBar", empty_icon); // VScrollBar theme->set_stylebox("scroll", "VScrollBar", make_stylebox(theme->get_icon("GuiScrollBg", "EditorIcons"), 5, 5, 5, 5, 0, 0, 0, 0)); @@ -1150,8 +1168,10 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { theme->set_icon("increment", "VScrollBar", empty_icon); theme->set_icon("increment_highlight", "VScrollBar", empty_icon); + theme->set_icon("increment_pressed", "VScrollBar", empty_icon); theme->set_icon("decrement", "VScrollBar", empty_icon); theme->set_icon("decrement_highlight", "VScrollBar", empty_icon); + theme->set_icon("decrement_pressed", "VScrollBar", empty_icon); // HSlider theme->set_icon("grabber_highlight", "HSlider", theme->get_icon("GuiSliderGrabberHl", "EditorIcons")); @@ -1216,6 +1236,7 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { // SpinBox theme->set_icon("updown", "SpinBox", theme->get_icon("GuiSpinboxUpdown", "EditorIcons")); + theme->set_icon("updown_disabled", "SpinBox", theme->get_icon("GuiSpinboxUpdownDisabled", "EditorIcons")); // ProgressBar theme->set_stylebox("bg", "ProgressBar", make_stylebox(theme->get_icon("GuiProgressBar", "EditorIcons"), 4, 4, 4, 4, 0, 0, 0, 0)); diff --git a/editor/filesystem_dock.cpp b/editor/filesystem_dock.cpp index daee61c2dd..5dd5c050e0 100644 --- a/editor/filesystem_dock.cpp +++ b/editor/filesystem_dock.cpp @@ -2158,6 +2158,14 @@ bool FileSystemDock::can_drop_data_fw(const Point2 &p_point, const Variant &p_da return true; } + if (drag_data.has("type") && String(drag_data["type"]) == "nodes") { + // Save branch as scene. + String to_dir; + bool favorite; + _get_drag_target_folder(to_dir, favorite, p_point, p_from); + return !favorite && Array(drag_data["nodes"]).size() == 1; + } + return false; } @@ -2296,6 +2304,13 @@ void FileSystemDock::drop_data_fw(const Point2 &p_point, const Variant &p_data, _update_tree(_compute_uncollapsed_paths()); } } + + if (drag_data.has("type") && String(drag_data["type"]) == "nodes") { + String to_dir; + bool favorite; + _get_drag_target_folder(to_dir, favorite, p_point, p_from); + EditorNode::get_singleton()->get_scene_tree_dock()->save_branch_to_file(to_dir); + } } void FileSystemDock::_get_drag_target_folder(String &target, bool &target_favorites, const Point2 &p_point, Control *p_from) const { diff --git a/editor/icons/AnimatableBody2D.svg b/editor/icons/AnimatableBody2D.svg new file mode 100644 index 0000000000..f4fed813c9 --- /dev/null +++ b/editor/icons/AnimatableBody2D.svg @@ -0,0 +1 @@ +<svg clip-rule="evenodd" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="1.5" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><g fill="#8da5f3" fill-opacity=".99" stroke-width="1.08904"><path d="m10.86822576 4.28299076h1.99947744v1.99947744h-1.99947744z"/><path d="m10.86822576 10.84273776h1.99947744v1.99947744h-1.99947744z"/><path d="m4.71256576 10.84273776h1.99947744v1.99947744h-1.99947744z"/></g><g fill="none" stroke="#8da5f3"><path d="m1.635 8.161v4.848c0 .713.579 1.293 1.292 1.293h9.857c.713 0 1.291-.58 1.291-1.293v-9.854c0-.714-.578-1.293-1.291-1.293h-5.526" stroke-width="1.07" transform="matrix(.939225 0 0 .938055 1.27996 1.07595)"/><path d="m1.339 1.364 2.539 2.539" stroke-width=".74" transform="matrix(2.04823 .655864 .655864 2.04823 -1.51683 -1.5267)"/><path d="m1.436 1.461 1.168 1.168" stroke-width="1.18" transform="matrix(1.69185 0 0 1.69185 4.50755 -.792876)"/><path d="m1.385 1.41 1.219 1.219" stroke-width="1.22" transform="matrix(1.63859 0 0 1.63859 -.688679 4.82985)"/></g></svg> diff --git a/editor/icons/AnimatableBody3D.svg b/editor/icons/AnimatableBody3D.svg new file mode 100644 index 0000000000..2e472f0625 --- /dev/null +++ b/editor/icons/AnimatableBody3D.svg @@ -0,0 +1 @@ +<svg clip-rule="evenodd" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="1.5" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><g fill="#fc7f7f" fill-opacity=".99" stroke-width="1.08904"><path d="m10.86822576 4.28299076h1.99947744v1.99947744h-1.99947744z"/><path d="m10.86822576 10.84273776h1.99947744v1.99947744h-1.99947744z"/><path d="m4.71256576 10.84273776h1.99947744v1.99947744h-1.99947744z"/></g><g fill="none" stroke="#fc7f7f"><path d="m1.635 8.161v4.848c0 .713.579 1.293 1.292 1.293h9.857c.713 0 1.291-.58 1.291-1.293v-9.854c0-.714-.578-1.293-1.291-1.293h-5.526" stroke-width="1.07" transform="matrix(.939225 0 0 .938055 1.27996 1.07595)"/><path d="m1.339 1.364 2.539 2.539" stroke-width=".74" transform="matrix(2.04823 .655864 .655864 2.04823 -1.51683 -1.5267)"/><path d="m1.436 1.461 1.168 1.168" stroke-width="1.18" transform="matrix(1.69185 0 0 1.69185 4.50755 -.792876)"/><path d="m1.385 1.41 1.219 1.219" stroke-width="1.22" transform="matrix(1.63859 0 0 1.63859 -.688679 4.82985)"/></g></svg> diff --git a/editor/icons/GuiCheckedDisabled.svg b/editor/icons/GuiCheckedDisabled.svg new file mode 100644 index 0000000000..6252176241 --- /dev/null +++ b/editor/icons/GuiCheckedDisabled.svg @@ -0,0 +1 @@ +<svg enable-background="new 0 0 16 16" height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m3.333 1c-1.289 0-2.333 1.045-2.333 2.333v9.333c0 1.289 1.044 2.334 2.333 2.334h9.333c1.289 0 2.334-1.045 2.334-2.334v-9.333c0-1.289-1.045-2.333-2.334-2.333z" fill="#808080"/><path d="m11.501 3.734-5.612 5.612-1.704-1.681-1.5 1.5 3.204 3.181 7.111-7.113z" fill="#b3b3b3"/></svg> diff --git a/editor/icons/GuiRadioCheckedDisabled.svg b/editor/icons/GuiRadioCheckedDisabled.svg new file mode 100644 index 0000000000..94a1fffa0b --- /dev/null +++ b/editor/icons/GuiRadioCheckedDisabled.svg @@ -0,0 +1 @@ +<svg enable-background="new 0 0 16 16" height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m15 8c0 3.866-3.134 7-7 7s-7-3.134-7-7 3.134-7 7-7 7 3.134 7 7" fill="#808080"/><path d="m12 8c0 2.209-1.791 4-4 4s-4-1.791-4-4 1.791-4 4-4 4 1.791 4 4" fill="#b3b3b3"/></svg> diff --git a/editor/icons/GuiRadioUncheckedDisabled.svg b/editor/icons/GuiRadioUncheckedDisabled.svg new file mode 100644 index 0000000000..3a75797c4d --- /dev/null +++ b/editor/icons/GuiRadioUncheckedDisabled.svg @@ -0,0 +1 @@ +<svg enable-background="new 0 0 16 16" height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m15 8c0 3.866-3.134 7-7 7s-7-3.134-7-7 3.134-7 7-7 7 3.134 7 7" fill="#808080" fill-opacity=".1882"/></svg> diff --git a/editor/icons/GuiSpinboxUpdownDisabled.svg b/editor/icons/GuiSpinboxUpdownDisabled.svg new file mode 100644 index 0000000000..332c5e7bf8 --- /dev/null +++ b/editor/icons/GuiSpinboxUpdownDisabled.svg @@ -0,0 +1 @@ +<svg enable-background="new 0 0 16 16" height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m7.984 1.002c-.259.004-.507.108-.691.291l-4 4c-.398.383-.41 1.016-.027 1.414s1.016.41 1.414.027l.027-.027 3.293-3.293 3.293 3.293c.383.398 1.016.41 1.414.027s.41-1.016.027-1.414c-.01-.009-.018-.018-.027-.027l-4-4c-.191-.19-.452-.296-.723-.291zm4.006 7.984c-.264.006-.514.117-.697.307l-3.293 3.293-3.293-3.293c-.188-.193-.447-.303-.717-.303-.552 0-1 .448-1 1 0 .271.109.529.303.717l4 4c.391.391 1.023.391 1.414 0l4-4c.398-.383.41-1.016.027-1.414-.193-.202-.463-.313-.744-.307z" fill="#b3b3b3" fill-opacity=".7843"/></svg> diff --git a/editor/icons/GuiUncheckedDisabled.svg b/editor/icons/GuiUncheckedDisabled.svg new file mode 100644 index 0000000000..2b6515f9d7 --- /dev/null +++ b/editor/icons/GuiUncheckedDisabled.svg @@ -0,0 +1 @@ +<svg enable-background="new 0 0 16 16" height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m3.333 1c-1.289 0-2.333 1.045-2.333 2.333v9.333c0 1.289 1.044 2.334 2.333 2.334h9.333c1.289 0 2.334-1.045 2.334-2.334v-9.333c0-1.289-1.045-2.333-2.334-2.333z" fill="#808080" fill-opacity=".1882"/></svg> diff --git a/editor/icons/Listener2D.svg b/editor/icons/Listener2D.svg new file mode 100644 index 0000000000..db84dcfed7 --- /dev/null +++ b/editor/icons/Listener2D.svg @@ -0,0 +1 @@ +<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m6 1a5 5 0 0 0 -5 5h2a3 3 0 0 1 3-3 3 3 0 0 1 3 3c0 1.75-.54175 2.3583-1.1406 2.8574-.29944.2495-.62954.44071-.97656.69141-.17351.1253-.35729.26529-.53711.49219-.17982.227-.3457.58398-.3457.95898 0 1.2778-.31632 1.5742-.63867 1.7676-.32236.1934-.86133.23242-1.3613.23242h-1v2h1c.5 0 1.461.038922 2.3887-.51758.87316-.5239 1.4826-1.6633 1.5566-3.2266.011365-.0098.027247-.024684.10938-.083984.21547-.1556.63537-.40194 1.0859-.77734.90112-.751 1.8594-2.1445 1.8594-4.3945a5 5 0 0 0 -5-5zm7.9277 1-1.7383 1.0039a6 6 0 0 1 .81055 2.9961 6 6 0 0 1 -.80859 2.998l1.7363 1.002a8 8 0 0 0 0-8z" fill="#a5b7f3"/></svg> diff --git a/editor/icons/PageFirst.svg b/editor/icons/PageFirst.svg new file mode 100644 index 0000000000..76078691ef --- /dev/null +++ b/editor/icons/PageFirst.svg @@ -0,0 +1,47 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + height="12" + viewBox="0 0 12 12" + width="12" + version="1.1" + id="svg4" + sodipodi:docname="PageFirst.svg" + inkscape:version="1.1 (c4e8f9ed74, 2021-05-24)" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <defs + id="defs8" /> + <sodipodi:namedview + id="namedview6" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageshadow="2" + inkscape:pageopacity="0.0" + inkscape:pagecheckerboard="0" + showgrid="true" + inkscape:zoom="74.25" + inkscape:cx="18.053872" + inkscape:cy="6.5252525" + inkscape:window-width="3838" + inkscape:window-height="1582" + inkscape:window-x="0" + inkscape:window-y="16" + inkscape:window-maximized="1" + inkscape:current-layer="svg4"> + <inkscape:grid + type="xygrid" + id="grid989" /> + </sodipodi:namedview> + <path + d="M 6,9 3,6 6,3" + style="fill:none;stroke:#e0e0e0;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" + id="path2" /> + <path + d="M 9,9 V 3" + style="fill:none;stroke:#e0e0e0;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" + id="path2211" + sodipodi:nodetypes="cc" /> +</svg> diff --git a/editor/icons/PageLast.svg b/editor/icons/PageLast.svg new file mode 100644 index 0000000000..17c874e8c9 --- /dev/null +++ b/editor/icons/PageLast.svg @@ -0,0 +1,47 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + height="12" + viewBox="0 0 12 12" + width="12" + version="1.1" + id="svg4" + sodipodi:docname="PageLast.svg" + inkscape:version="1.1 (c4e8f9ed74, 2021-05-24)" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <defs + id="defs8" /> + <sodipodi:namedview + id="namedview6" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageshadow="2" + inkscape:pageopacity="0.0" + inkscape:pagecheckerboard="0" + showgrid="true" + inkscape:zoom="74.25" + inkscape:cx="18.053872" + inkscape:cy="6.5252525" + inkscape:window-width="3838" + inkscape:window-height="1582" + inkscape:window-x="0" + inkscape:window-y="16" + inkscape:window-maximized="1" + inkscape:current-layer="svg4"> + <inkscape:grid + type="xygrid" + id="grid989" /> + </sodipodi:namedview> + <path + d="m 6.0000414,9 3,-3 -3,-3" + style="fill:none;stroke:#e0e0e0;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" + id="path2" /> + <path + d="M 3.0000414,9 V 3" + style="fill:none;stroke:#e0e0e0;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" + id="path2211" + sodipodi:nodetypes="cc" /> +</svg> diff --git a/editor/icons/PageNext.svg b/editor/icons/PageNext.svg new file mode 100644 index 0000000000..89ff6219bb --- /dev/null +++ b/editor/icons/PageNext.svg @@ -0,0 +1,42 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + height="12" + viewBox="0 0 12 12" + width="12" + version="1.1" + id="svg4" + sodipodi:docname="PageNext.svg" + inkscape:version="1.1 (c4e8f9ed74, 2021-05-24)" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <defs + id="defs8" /> + <sodipodi:namedview + id="namedview6" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageshadow="2" + inkscape:pageopacity="0.0" + inkscape:pagecheckerboard="0" + showgrid="true" + inkscape:zoom="105.00536" + inkscape:cx="4.5854803" + inkscape:cy="5.9377923" + inkscape:window-width="3838" + inkscape:window-height="1582" + inkscape:window-x="0" + inkscape:window-y="16" + inkscape:window-maximized="1" + inkscape:current-layer="svg4"> + <inkscape:grid + type="xygrid" + id="grid989" /> + </sodipodi:namedview> + <path + d="m 4.5000207,9 3,-3 -3,-3" + style="fill:none;stroke:#e0e0e0;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" + id="path2" /> +</svg> diff --git a/editor/icons/PagePrevious.svg b/editor/icons/PagePrevious.svg new file mode 100644 index 0000000000..a2fa84da0c --- /dev/null +++ b/editor/icons/PagePrevious.svg @@ -0,0 +1,42 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + height="12" + viewBox="0 0 12 12" + width="12" + version="1.1" + id="svg4" + sodipodi:docname="PagePrevious.svg" + inkscape:version="1.1 (c4e8f9ed74, 2021-05-24)" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <defs + id="defs8" /> + <sodipodi:namedview + id="namedview6" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageshadow="2" + inkscape:pageopacity="0.0" + inkscape:pagecheckerboard="0" + showgrid="true" + inkscape:zoom="105.00536" + inkscape:cx="4.5854803" + inkscape:cy="5.9377923" + inkscape:window-width="3838" + inkscape:window-height="1582" + inkscape:window-x="0" + inkscape:window-y="16" + inkscape:window-maximized="1" + inkscape:current-layer="svg4"> + <inkscape:grid + type="xygrid" + id="grid989" /> + </sodipodi:namedview> + <path + d="m 7.4999793,9 -3,-3 3,-3" + style="fill:none;stroke:#e0e0e0;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" + id="path2" /> +</svg> diff --git a/editor/import/resource_importer_texture.cpp b/editor/import/resource_importer_texture.cpp index daf7b15794..61745cb6ee 100644 --- a/editor/import/resource_importer_texture.cpp +++ b/editor/import/resource_importer_texture.cpp @@ -393,7 +393,7 @@ Error ResourceImporterTexture::import(const String &p_source_file, const String float lossy = p_options["compress/lossy_quality"]; int pack_channels = p_options["compress/channel_pack"]; bool mipmaps = p_options["mipmaps/generate"]; - uint32_t mipmap_limit = int(mipmaps ? int(p_options["mipmaps/limit"]) : int(-1)); + uint32_t mipmap_limit = mipmaps ? uint32_t(p_options["mipmaps/limit"]) : uint32_t(-1); bool fix_alpha_border = p_options["process/fix_alpha_border"]; bool premult_alpha = p_options["process/premult_alpha"]; bool normal_map_invert_y = p_options["process/normal_map_invert_y"]; diff --git a/editor/import/scene_importer_mesh.cpp b/editor/import/scene_importer_mesh.cpp index 06f373c54f..d8248e2670 100644 --- a/editor/import/scene_importer_mesh.cpp +++ b/editor/import/scene_importer_mesh.cpp @@ -33,6 +33,8 @@ #include "core/math/math_defs.h" #include "scene/resources/surface_tool.h" +#include <cstdint> + void EditorSceneImporterMesh::add_blend_shape(const String &p_name) { ERR_FAIL_COND(surfaces.size() > 0); blend_shapes.push_back(p_name); @@ -55,13 +57,14 @@ Mesh::BlendShapeMode EditorSceneImporterMesh::get_blend_shape_mode() const { return blend_shape_mode; } -void EditorSceneImporterMesh::add_surface(Mesh::PrimitiveType p_primitive, const Array &p_arrays, const Array &p_blend_shapes, const Dictionary &p_lods, const Ref<Material> &p_material, const String &p_name) { +void EditorSceneImporterMesh::add_surface(Mesh::PrimitiveType p_primitive, const Array &p_arrays, const Array &p_blend_shapes, const Dictionary &p_lods, const Ref<Material> &p_material, const String &p_name, const uint32_t p_flags) { ERR_FAIL_COND(p_blend_shapes.size() != blend_shapes.size()); ERR_FAIL_COND(p_arrays.size() != Mesh::ARRAY_MAX); Surface s; s.primitive = p_primitive; s.arrays = p_arrays; s.name = p_name; + s.flags = p_flags; Vector<Vector3> vertex_array = p_arrays[Mesh::ARRAY_VERTEX]; int vertex_count = vertex_array.size(); @@ -138,6 +141,11 @@ float EditorSceneImporterMesh::get_surface_lod_size(int p_surface, int p_lod) co return surfaces[p_surface].lods[p_lod].distance; } +uint32_t EditorSceneImporterMesh::get_surface_format(int p_surface) const { + ERR_FAIL_INDEX_V(p_surface, surfaces.size(), 0); + return surfaces[p_surface].flags; +} + Ref<Material> EditorSceneImporterMesh::get_surface_material(int p_surface) const { ERR_FAIL_INDEX_V(p_surface, surfaces.size(), Ref<Material>()); return surfaces[p_surface].material; @@ -283,7 +291,7 @@ Ref<ArrayMesh> EditorSceneImporterMesh::get_mesh(const Ref<ArrayMesh> &p_base) { } } - mesh->add_surface_from_arrays(surfaces[i].primitive, surfaces[i].arrays, bs_data, lods); + mesh->add_surface_from_arrays(surfaces[i].primitive, surfaces[i].arrays, bs_data, lods, surfaces[i].flags); if (surfaces[i].material.is_valid()) { mesh->surface_set_material(mesh->get_surface_count() - 1, surfaces[i].material); } @@ -398,7 +406,7 @@ void EditorSceneImporterMesh::create_shadow_mesh() { } } - shadow_mesh->add_surface(surfaces[i].primitive, new_surface, Array(), lods, Ref<Material>(), surfaces[i].name); + shadow_mesh->add_surface(surfaces[i].primitive, new_surface, Array(), lods, Ref<Material>(), surfaces[i].name, surfaces[i].flags); } } @@ -436,7 +444,11 @@ void EditorSceneImporterMesh::_set_data(const Dictionary &p_data) { if (s.has("material")) { material = s["material"]; } - add_surface(prim, arr, blend_shapes, lods, material, name); + uint32_t flags = 0; + if (s.has("flags")) { + flags = s["flags"]; + } + add_surface(prim, arr, blend_shapes, lods, material, name, flags); } } } @@ -473,6 +485,10 @@ Dictionary EditorSceneImporterMesh::_get_data() const { d["name"] = surfaces[i].name; } + if (surfaces[i].flags != 0) { + d["flags"] = surfaces[i].flags; + } + surface_arr.push_back(d); } data["surfaces"] = surface_arr; @@ -833,7 +849,7 @@ void EditorSceneImporterMesh::_bind_methods() { ClassDB::bind_method(D_METHOD("set_blend_shape_mode", "mode"), &EditorSceneImporterMesh::set_blend_shape_mode); ClassDB::bind_method(D_METHOD("get_blend_shape_mode"), &EditorSceneImporterMesh::get_blend_shape_mode); - ClassDB::bind_method(D_METHOD("add_surface", "primitive", "arrays", "blend_shapes", "lods", "material", "name"), &EditorSceneImporterMesh::add_surface, DEFVAL(Array()), DEFVAL(Dictionary()), DEFVAL(Ref<Material>()), DEFVAL(String())); + ClassDB::bind_method(D_METHOD("add_surface", "primitive", "arrays", "blend_shapes", "lods", "material", "name", "flags"), &EditorSceneImporterMesh::add_surface, DEFVAL(Array()), DEFVAL(Dictionary()), DEFVAL(Ref<Material>()), DEFVAL(String()), DEFVAL(0)); ClassDB::bind_method(D_METHOD("get_surface_count"), &EditorSceneImporterMesh::get_surface_count); ClassDB::bind_method(D_METHOD("get_surface_primitive_type", "surface_idx"), &EditorSceneImporterMesh::get_surface_primitive_type); @@ -844,6 +860,7 @@ void EditorSceneImporterMesh::_bind_methods() { ClassDB::bind_method(D_METHOD("get_surface_lod_size", "surface_idx", "lod_idx"), &EditorSceneImporterMesh::get_surface_lod_size); ClassDB::bind_method(D_METHOD("get_surface_lod_indices", "surface_idx", "lod_idx"), &EditorSceneImporterMesh::get_surface_lod_indices); ClassDB::bind_method(D_METHOD("get_surface_material", "surface_idx"), &EditorSceneImporterMesh::get_surface_material); + ClassDB::bind_method(D_METHOD("get_surface_format", "surface_idx"), &EditorSceneImporterMesh::get_surface_format); ClassDB::bind_method(D_METHOD("set_surface_name", "surface_idx", "name"), &EditorSceneImporterMesh::set_surface_name); ClassDB::bind_method(D_METHOD("set_surface_material", "surface_idx", "material"), &EditorSceneImporterMesh::set_surface_material); diff --git a/editor/import/scene_importer_mesh.h b/editor/import/scene_importer_mesh.h index e57e479d8e..c8e25244fa 100644 --- a/editor/import/scene_importer_mesh.h +++ b/editor/import/scene_importer_mesh.h @@ -36,6 +36,9 @@ #include "scene/resources/convex_polygon_shape_3d.h" #include "scene/resources/mesh.h" #include "scene/resources/navigation_mesh.h" + +#include <cstdint> + // The following classes are used by importers instead of ArrayMesh and MeshInstance3D // so the data is not registered (hence, quality loss), importing happens faster and // its easier to modify before saving @@ -57,6 +60,7 @@ class EditorSceneImporterMesh : public Resource { Vector<LOD> lods; Ref<Material> material; String name; + uint32_t flags = 0; }; Vector<Surface> surfaces; Vector<String> blend_shapes; @@ -80,7 +84,7 @@ public: int get_blend_shape_count() const; String get_blend_shape_name(int p_blend_shape) const; - void add_surface(Mesh::PrimitiveType p_primitive, const Array &p_arrays, const Array &p_blend_shapes = Array(), const Dictionary &p_lods = Dictionary(), const Ref<Material> &p_material = Ref<Material>(), const String &p_name = String()); + void add_surface(Mesh::PrimitiveType p_primitive, const Array &p_arrays, const Array &p_blend_shapes = Array(), const Dictionary &p_lods = Dictionary(), const Ref<Material> &p_material = Ref<Material>(), const String &p_name = String(), const uint32_t p_flags = 0); int get_surface_count() const; void set_blend_shape_mode(Mesh::BlendShapeMode p_blend_shape_mode); @@ -95,6 +99,7 @@ public: Vector<int> get_surface_lod_indices(int p_surface, int p_lod) const; float get_surface_lod_size(int p_surface, int p_lod) const; Ref<Material> get_surface_material(int p_surface) const; + uint32_t get_surface_format(int p_surface) const; void set_surface_material(int p_surface, const Ref<Material> &p_material); diff --git a/editor/multi_node_edit.cpp b/editor/multi_node_edit.cpp index fd4a4334fc..1e707c1a60 100644 --- a/editor/multi_node_edit.cpp +++ b/editor/multi_node_edit.cpp @@ -49,6 +49,11 @@ bool MultiNodeEdit::_set_impl(const StringName &p_name, const Variant &p_value, name = "script"; } + Node *node_path_target = nullptr; + if (p_value.get_type() == Variant::NODE_PATH && p_value != NodePath()) { + node_path_target = es->get_node(p_value); + } + UndoRedo *ur = EditorNode::get_undo_redo(); ur->create_action(TTR("MultiNode Set") + " " + String(name), UndoRedo::MERGE_ENDS); @@ -63,9 +68,11 @@ bool MultiNodeEdit::_set_impl(const StringName &p_name, const Variant &p_value, } if (p_value.get_type() == Variant::NODE_PATH) { - Node *tonode = n->get_node(p_value); - NodePath p_path = n->get_path_to(tonode); - ur->add_do_property(n, name, p_path); + NodePath path; + if (node_path_target) { + path = n->get_path_to(node_path_target); + } + ur->add_do_property(n, name, path); } else { Variant new_value; if (p_field == "") { diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp index d96cc1cd18..f11e51960c 100644 --- a/editor/plugins/canvas_item_editor_plugin.cpp +++ b/editor/plugins/canvas_item_editor_plugin.cpp @@ -5204,7 +5204,9 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) { snap_rotation = false; snap_scale = false; snap_relative = false; - snap_pixel = false; + // Enable pixel snapping even if pixel snap rendering is disabled in the Project Settings. + // This results in crisper visuals by preventing 2D nodes from being placed at subpixel coordinates. + snap_pixel = true; snap_target[0] = SNAP_TARGET_NONE; snap_target[1] = SNAP_TARGET_NONE; diff --git a/editor/plugins/mesh_library_editor_plugin.cpp b/editor/plugins/mesh_library_editor_plugin.cpp index b3f92c9d95..18e7480287 100644 --- a/editor/plugins/mesh_library_editor_plugin.cpp +++ b/editor/plugins/mesh_library_editor_plugin.cpp @@ -47,23 +47,25 @@ void MeshLibraryEditor::edit(const Ref<MeshLibrary> &p_mesh_library) { } } -void MeshLibraryEditor::_menu_confirm() { +void MeshLibraryEditor::_menu_remove_confirm() { switch (option) { case MENU_OPTION_REMOVE_ITEM: { mesh_library->remove_item(to_erase); } break; - case MENU_OPTION_UPDATE_FROM_SCENE: { - String existing = mesh_library->get_meta("_editor_source_scene"); - ERR_FAIL_COND(existing == ""); - _import_scene_cbk(existing); - - } break; default: { }; } } -void MeshLibraryEditor::_import_scene(Node *p_scene, Ref<MeshLibrary> p_library, bool p_merge) { +void MeshLibraryEditor::_menu_update_confirm(bool p_apply_xforms) { + cd_update->hide(); + apply_xforms = p_apply_xforms; + String existing = mesh_library->get_meta("_editor_source_scene"); + ERR_FAIL_COND(existing == ""); + _import_scene_cbk(existing); +} + +void MeshLibraryEditor::_import_scene(Node *p_scene, Ref<MeshLibrary> p_library, bool p_merge, bool p_apply_xforms) { if (!p_merge) { p_library->clear(); } @@ -108,6 +110,13 @@ void MeshLibraryEditor::_import_scene(Node *p_scene, Ref<MeshLibrary> p_library, } p_library->set_item_mesh(id, mesh); + + if (p_apply_xforms) { + p_library->set_item_mesh_transform(id, mi->get_transform()); + } else { + p_library->set_item_mesh_transform(id, Transform3D()); + } + mesh_instances[id] = mi; Vector<MeshLibrary::ShapeData> collisions; @@ -197,15 +206,16 @@ void MeshLibraryEditor::_import_scene_cbk(const String &p_str) { ERR_FAIL_COND_MSG(!scene, "Cannot create an instance from PackedScene '" + p_str + "'."); - _import_scene(scene, mesh_library, option == MENU_OPTION_UPDATE_FROM_SCENE); + _import_scene(scene, mesh_library, option == MENU_OPTION_UPDATE_FROM_SCENE, apply_xforms); memdelete(scene); mesh_library->set_meta("_editor_source_scene", p_str); + menu->get_popup()->set_item_disabled(menu->get_popup()->get_item_index(MENU_OPTION_UPDATE_FROM_SCENE), false); } -Error MeshLibraryEditor::update_library_file(Node *p_base_scene, Ref<MeshLibrary> ml, bool p_merge) { - _import_scene(p_base_scene, ml, p_merge); +Error MeshLibraryEditor::update_library_file(Node *p_base_scene, Ref<MeshLibrary> ml, bool p_merge, bool p_apply_xforms) { + _import_scene(p_base_scene, ml, p_merge, p_apply_xforms); return OK; } @@ -219,16 +229,21 @@ void MeshLibraryEditor::_menu_cbk(int p_option) { String p = editor->get_inspector()->get_selected_path(); if (p.begins_with("/MeshLibrary/item") && p.get_slice_count("/") >= 3) { to_erase = p.get_slice("/", 3).to_int(); - cd->set_text(vformat(TTR("Remove item %d?"), to_erase)); - cd->popup_centered(Size2(300, 60)); + cd_remove->set_text(vformat(TTR("Remove item %d?"), to_erase)); + cd_remove->popup_centered(Size2(300, 60)); } } break; case MENU_OPTION_IMPORT_FROM_SCENE: { + apply_xforms = false; + file->popup_file_dialog(); + } break; + case MENU_OPTION_IMPORT_FROM_SCENE_APPLY_XFORMS: { + apply_xforms = true; file->popup_file_dialog(); } break; case MENU_OPTION_UPDATE_FROM_SCENE: { - cd->set_text(vformat(TTR("Update from existing scene?:\n%s"), String(mesh_library->get_meta("_editor_source_scene")))); - cd->popup_centered(Size2(500, 60)); + cd_update->set_text(vformat(TTR("Update from existing scene?:\n%s"), String(mesh_library->get_meta("_editor_source_scene")))); + cd_update->popup_centered(Size2(500, 60)); } break; } } @@ -258,16 +273,22 @@ MeshLibraryEditor::MeshLibraryEditor(EditorNode *p_editor) { menu->get_popup()->add_item(TTR("Add Item"), MENU_OPTION_ADD_ITEM); menu->get_popup()->add_item(TTR("Remove Selected Item"), MENU_OPTION_REMOVE_ITEM); menu->get_popup()->add_separator(); - menu->get_popup()->add_item(TTR("Import from Scene"), MENU_OPTION_IMPORT_FROM_SCENE); + menu->get_popup()->add_item(TTR("Import from Scene (Ignore Transforms)"), MENU_OPTION_IMPORT_FROM_SCENE); + menu->get_popup()->add_item(TTR("Import from Scene (Apply Transforms)"), MENU_OPTION_IMPORT_FROM_SCENE_APPLY_XFORMS); menu->get_popup()->add_item(TTR("Update from Scene"), MENU_OPTION_UPDATE_FROM_SCENE); menu->get_popup()->set_item_disabled(menu->get_popup()->get_item_index(MENU_OPTION_UPDATE_FROM_SCENE), true); menu->get_popup()->connect("id_pressed", callable_mp(this, &MeshLibraryEditor::_menu_cbk)); menu->hide(); editor = p_editor; - cd = memnew(ConfirmationDialog); - add_child(cd); - cd->get_ok_button()->connect("pressed", callable_mp(this, &MeshLibraryEditor::_menu_confirm)); + cd_remove = memnew(ConfirmationDialog); + add_child(cd_remove); + cd_remove->get_ok_button()->connect("pressed", callable_mp(this, &MeshLibraryEditor::_menu_remove_confirm)); + cd_update = memnew(ConfirmationDialog); + add_child(cd_update); + cd_update->get_ok_button()->set_text("Apply without Transforms"); + cd_update->get_ok_button()->connect("pressed", callable_mp(this, &MeshLibraryEditor::_menu_update_confirm), varray(false)); + cd_update->add_button("Apply with Transforms")->connect("pressed", callable_mp(this, &MeshLibraryEditor::_menu_update_confirm), varray(true)); } void MeshLibraryEditorPlugin::edit(Object *p_node) { diff --git a/editor/plugins/mesh_library_editor_plugin.h b/editor/plugins/mesh_library_editor_plugin.h index 6c33c8bb9e..9e225ffb9b 100644 --- a/editor/plugins/mesh_library_editor_plugin.h +++ b/editor/plugins/mesh_library_editor_plugin.h @@ -41,23 +41,27 @@ class MeshLibraryEditor : public Control { EditorNode *editor; MenuButton *menu; - ConfirmationDialog *cd; + ConfirmationDialog *cd_remove; + ConfirmationDialog *cd_update; EditorFileDialog *file; + bool apply_xforms; int to_erase; enum { MENU_OPTION_ADD_ITEM, MENU_OPTION_REMOVE_ITEM, MENU_OPTION_UPDATE_FROM_SCENE, - MENU_OPTION_IMPORT_FROM_SCENE + MENU_OPTION_IMPORT_FROM_SCENE, + MENU_OPTION_IMPORT_FROM_SCENE_APPLY_XFORMS }; int option; void _import_scene_cbk(const String &p_str); void _menu_cbk(int p_option); - void _menu_confirm(); + void _menu_remove_confirm(); + void _menu_update_confirm(bool p_apply_xforms); - static void _import_scene(Node *p_scene, Ref<MeshLibrary> p_library, bool p_merge); + static void _import_scene(Node *p_scene, Ref<MeshLibrary> p_library, bool p_merge, bool p_apply_xforms); protected: static void _bind_methods(); @@ -66,7 +70,7 @@ public: MenuButton *get_menu_button() const { return menu; } void edit(const Ref<MeshLibrary> &p_mesh_library); - static Error update_library_file(Node *p_base_scene, Ref<MeshLibrary> ml, bool p_merge = true); + static Error update_library_file(Node *p_base_scene, Ref<MeshLibrary> ml, bool p_merge = true, bool p_apply_xforms = false); MeshLibraryEditor(EditorNode *p_editor); }; diff --git a/editor/plugins/node_3d_editor_gizmos.cpp b/editor/plugins/node_3d_editor_gizmos.cpp index d04e88e915..d20f3d105b 100644 --- a/editor/plugins/node_3d_editor_gizmos.cpp +++ b/editor/plugins/node_3d_editor_gizmos.cpp @@ -2097,7 +2097,6 @@ void Skeleton3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { Color bonecolor = Color(1.0, 0.4, 0.4, 0.3); Color rootcolor = Color(0.4, 1.0, 0.4, 0.1); - //LocalVector<int> bones_to_process = skel->get_parentless_bones(); LocalVector<int> bones_to_process; bones_to_process = skel->get_parentless_bones(); @@ -2109,19 +2108,18 @@ void Skeleton3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { child_bones_vector = skel->get_bone_children(current_bone_idx); int child_bones_size = child_bones_vector.size(); - // You have children but no parent, then you must be a root/parentless bone. - if (child_bones_size >= 0 && skel->get_bone_parent(current_bone_idx) <= 0) { - grests[current_bone_idx] = skel->global_pose_to_local_pose(current_bone_idx, skel->get_bone_global_pose(current_bone_idx)); + if (skel->get_bone_parent(current_bone_idx) < 0) { + grests[current_bone_idx] = skel->get_bone_rest(current_bone_idx); } for (int i = 0; i < child_bones_size; i++) { int child_bone_idx = child_bones_vector[i]; - grests[child_bone_idx] = skel->global_pose_to_local_pose(child_bone_idx, skel->get_bone_global_pose(child_bone_idx)); + grests[child_bone_idx] = grests[current_bone_idx] * skel->get_bone_rest(child_bone_idx); Vector3 v0 = grests[current_bone_idx].origin; Vector3 v1 = grests[child_bone_idx].origin; - Vector3 d = skel->get_bone_rest(child_bone_idx).origin.normalized(); - real_t dist = skel->get_bone_rest(child_bone_idx).origin.length(); + Vector3 d = (v1 - v0).normalized(); + real_t dist = v0.distance_to(v1); // Find closest axis. int closest = -1; diff --git a/editor/plugins/packed_scene_translation_parser_plugin.cpp b/editor/plugins/packed_scene_translation_parser_plugin.cpp index 0a949c8610..53c5b8dd70 100644 --- a/editor/plugins/packed_scene_translation_parser_plugin.cpp +++ b/editor/plugins/packed_scene_translation_parser_plugin.cpp @@ -31,6 +31,7 @@ #include "packed_scene_translation_parser_plugin.h" #include "core/io/resource_loader.h" +#include "scene/gui/option_button.h" #include "scene/resources/packed_scene.h" void PackedSceneEditorTranslationParserPlugin::get_recognized_extensions(List<String> *r_extensions) const { @@ -50,21 +51,31 @@ Error PackedSceneEditorTranslationParserPlugin::parse_file(const String &p_path, Ref<SceneState> state = Ref<PackedScene>(loaded_res)->get_state(); Vector<String> parsed_strings; - String property_name; - Variant property_value; for (int i = 0; i < state->get_node_count(); i++) { - if (!ClassDB::is_parent_class(state->get_node_type(i), "Control") && !ClassDB::is_parent_class(state->get_node_type(i), "Viewport")) { + String node_type = state->get_node_type(i); + if (!ClassDB::is_parent_class(node_type, "Control") && !ClassDB::is_parent_class(node_type, "Window")) { continue; } + // Find the `auto_translate` property, and abort the string parsing of the node if disabled. + bool auto_translating = true; for (int j = 0; j < state->get_node_property_count(i); j++) { - property_name = state->get_node_property_name(i, j); - if (!lookup_properties.has(property_name)) { - continue; + if (state->get_node_property_name(i, j) == "auto_translate" && (bool)state->get_node_property_value(i, j) == false) { + auto_translating = false; + break; } + } + if (!auto_translating) { + continue; + } - property_value = state->get_node_property_value(i, j); + for (int j = 0; j < state->get_node_property_count(i); j++) { + String property_name = state->get_node_property_name(i, j); + if (!lookup_properties.has(property_name) || (exception_list.has(node_type) && exception_list[node_type].has(property_name))) { + continue; + } + Variant property_value = state->get_node_property_value(i, j); if (property_name == "script" && property_value.get_type() == Variant::OBJECT && !property_value.is_null()) { // Parse built-in script. Ref<Script> s = Object::cast_to<Script>(property_value); @@ -76,7 +87,16 @@ Error PackedSceneEditorTranslationParserPlugin::parse_file(const String &p_path, parsed_strings.append_array(temp); r_ids_ctx_plural->append_array(ids_context_plural); } - } else if (property_name == "filters") { + } else if ((node_type == "MenuButton" || node_type == "OptionButton") && property_name == "items") { + Vector<String> str_values = property_value; + int incr_value = node_type == "MenuButton" ? PopupMenu::ITEM_PROPERTY_SIZE : OptionButton::ITEM_PROPERTY_SIZE; + for (int k = 0; k < str_values.size(); k += incr_value) { + String desc = str_values[k].get_slice(";", 1).strip_edges(); + if (!desc.is_empty()) { + parsed_strings.push_back(desc); + } + } + } else if (node_type == "FileDialog" && property_name == "filters") { // Extract FileDialog's filters property with values in format "*.png ; PNG Images","*.gd ; GDScript Files". Vector<String> str_values = property_value; for (int k = 0; k < str_values.size(); k++) { @@ -105,12 +125,17 @@ PackedSceneEditorTranslationParserPlugin::PackedSceneEditorTranslationParserPlug lookup_properties.insert("text"); lookup_properties.insert("hint_tooltip"); lookup_properties.insert("placeholder_text"); + lookup_properties.insert("items"); + lookup_properties.insert("title"); lookup_properties.insert("dialog_text"); lookup_properties.insert("filters"); lookup_properties.insert("script"); - //Add exception list (to prevent false positives) - //line edit, text edit, richtextlabel - //Set<String> exception_list; - //exception_list.insert("RichTextLabel"); + // Exception list (to prevent false positives). + exception_list.insert("LineEdit", Vector<StringName>()); + exception_list["LineEdit"].append("text"); + exception_list.insert("TextEdit", Vector<StringName>()); + exception_list["TextEdit"].append("text"); + exception_list.insert("CodeEdit", Vector<StringName>()); + exception_list["CodeEdit"].append("text"); } diff --git a/editor/plugins/packed_scene_translation_parser_plugin.h b/editor/plugins/packed_scene_translation_parser_plugin.h index e51d65414e..af0291b69c 100644 --- a/editor/plugins/packed_scene_translation_parser_plugin.h +++ b/editor/plugins/packed_scene_translation_parser_plugin.h @@ -37,7 +37,9 @@ class PackedSceneEditorTranslationParserPlugin : public EditorTranslationParserP GDCLASS(PackedSceneEditorTranslationParserPlugin, EditorTranslationParserPlugin); // Scene Node's properties that contain translation strings. - Set<String> lookup_properties; + Set<StringName> lookup_properties; + // Properties from specific Nodes that should be ignored. + Map<StringName, Vector<StringName>> exception_list; public: virtual Error parse_file(const String &p_path, Vector<String> *r_ids, Vector<Vector<String>> *r_ids_ctx_plural) override; diff --git a/editor/plugins/script_text_editor.cpp b/editor/plugins/script_text_editor.cpp index c44760807f..48239a5d99 100644 --- a/editor/plugins/script_text_editor.cpp +++ b/editor/plugins/script_text_editor.cpp @@ -158,7 +158,6 @@ void ScriptTextEditor::enable_editor() { editor_enabled = true; _enable_code_editor(); - _set_theme_for_script(); _validate_script(); } diff --git a/editor/plugins/tiles/tile_data_editors.cpp b/editor/plugins/tiles/tile_data_editors.cpp index d406c2514c..fd5c59af34 100644 --- a/editor/plugins/tiles/tile_data_editors.cpp +++ b/editor/plugins/tiles/tile_data_editors.cpp @@ -1465,12 +1465,13 @@ void TileDataTerrainsEditor::_tile_set_changed() { ERR_FAIL_COND(!tile_set.is_valid()); // Fix if wrong values are selected. - if (int(dummy_object->get("terrain_set")) > tile_set->get_terrain_sets_count()) { + int terrain_set = int(dummy_object->get("terrain_set")); + if (terrain_set >= tile_set->get_terrain_sets_count()) { + terrain_set = -1; dummy_object->set("terrain_set", -1); } - int terrain_set = int(dummy_object->get("terrain")); if (terrain_set >= 0) { - if (int(dummy_object->get("terrain")) > tile_set->get_terrains_count(terrain_set)) { + if (int(dummy_object->get("terrain")) >= tile_set->get_terrains_count(terrain_set)) { dummy_object->set("terrain", -1); } } diff --git a/editor/plugins/tiles/tile_map_editor.cpp b/editor/plugins/tiles/tile_map_editor.cpp index 77084f551a..b5e070b4d6 100644 --- a/editor/plugins/tiles/tile_map_editor.cpp +++ b/editor/plugins/tiles/tile_map_editor.cpp @@ -3549,30 +3549,76 @@ void TileMapEditor::_update_layers_selection() { tile_map_editor_plugins[tabs->get_current_tab()]->edit(tile_map_id, tile_map_layer); } -void TileMapEditor::_undo_redo_inspector_callback(Object *p_undo_redo, Object *p_edited, String p_property, Variant p_new_value) { +void TileMapEditor::_move_tile_map_array_element(Object *p_undo_redo, Object *p_edited, String p_array_prefix, int p_from_index, int p_to_pos) { UndoRedo *undo_redo = Object::cast_to<UndoRedo>(p_undo_redo); ERR_FAIL_COND(!undo_redo); TileMap *tile_map = Object::cast_to<TileMap>(p_edited); - if (tile_map) { - if (p_property == "layers_count") { - int new_layers_count = (int)p_new_value; - if (new_layers_count < tile_map->get_layers_count()) { - List<PropertyInfo> property_list; - tile_map->get_property_list(&property_list); - - for (PropertyInfo property_info : property_list) { - Vector<String> components = String(property_info.name).split("/", true, 2); - if (components.size() == 2 && components[0].begins_with("layer_") && components[0].trim_prefix("layer_").is_valid_int()) { - int index = components[0].trim_prefix("layer_").to_int(); - if (index >= new_layers_count) { - undo_redo->add_undo_property(tile_map, property_info.name, tile_map->get(property_info.name)); - } - } + if (!tile_map) { + return; + } + + // Compute the array indices to save. + int begin = 0; + int end; + if (p_array_prefix == "layer_") { + end = tile_map->get_layers_count(); + } else { + ERR_FAIL_MSG("Invalid array prefix for TileSet."); + } + if (p_from_index < 0) { + // Adding new. + if (p_to_pos >= 0) { + begin = p_to_pos; + } else { + end = 0; // Nothing to save when adding at the end. + } + } else if (p_to_pos < 0) { + // Removing. + begin = p_from_index; + } else { + // Moving. + begin = MIN(p_from_index, p_to_pos); + end = MIN(MAX(p_from_index, p_to_pos) + 1, end); + } + +#define ADD_UNDO(obj, property) undo_redo->add_undo_property(obj, property, obj->get(property)); + // Save layers' properties. + if (p_from_index < 0) { + undo_redo->add_undo_method(tile_map, "remove_layer", p_to_pos < 0 ? tile_map->get_layers_count() : p_to_pos); + } else if (p_to_pos < 0) { + undo_redo->add_undo_method(tile_map, "add_layer", p_from_index); + } + + List<PropertyInfo> properties; + tile_map->get_property_list(&properties); + for (PropertyInfo pi : properties) { + if (pi.name.begins_with(p_array_prefix)) { + String str = pi.name.trim_prefix(p_array_prefix); + int to_char_index = 0; + while (to_char_index < str.length()) { + if (str[to_char_index] < '0' || str[to_char_index] > '9') { + break; + } + to_char_index++; + } + if (to_char_index > 0) { + int array_index = str.left(to_char_index).to_int(); + if (array_index >= begin && array_index < end) { + ADD_UNDO(tile_map, pi.name); } } } } +#undef ADD_UNDO + + if (p_from_index < 0) { + undo_redo->add_do_method(tile_map, "add_layer", p_to_pos); + } else if (p_to_pos < 0) { + undo_redo->add_do_method(tile_map, "remove_layer", p_from_index); + } else { + undo_redo->add_do_method(tile_map, "move_layer", p_from_index, p_to_pos); + } } bool TileMapEditor::forward_canvas_gui_input(const Ref<InputEvent> &p_event) { @@ -3851,7 +3897,7 @@ TileMapEditor::TileMapEditor() { _tab_changed(0); // Registers UndoRedo inspector callback. - EditorNode::get_singleton()->get_editor_data().add_undo_redo_inspector_hook_callback(callable_mp(this, &TileMapEditor::_undo_redo_inspector_callback)); + EditorNode::get_singleton()->get_editor_data().add_move_array_element_function(SNAME("TileMap"), callable_mp(this, &TileMapEditor::_move_tile_map_array_element)); } TileMapEditor::~TileMapEditor() { diff --git a/editor/plugins/tiles/tile_map_editor.h b/editor/plugins/tiles/tile_map_editor.h index 6e2f2ce2ba..6126db59e9 100644 --- a/editor/plugins/tiles/tile_map_editor.h +++ b/editor/plugins/tiles/tile_map_editor.h @@ -341,7 +341,7 @@ private: void _update_layers_selection(); // Inspector undo/redo callback. - void _undo_redo_inspector_callback(Object *p_undo_redo, Object *p_edited, String p_property, Variant p_new_value); + void _move_tile_map_array_element(Object *p_undo_redo, Object *p_edited, String p_array_prefix, int p_from_index, int p_to_pos); protected: void _notification(int p_what); diff --git a/editor/plugins/tiles/tile_set_atlas_source_editor.cpp b/editor/plugins/tiles/tile_set_atlas_source_editor.cpp index 432f48fa85..c3a3f40e00 100644 --- a/editor/plugins/tiles/tile_set_atlas_source_editor.cpp +++ b/editor/plugins/tiles/tile_set_atlas_source_editor.cpp @@ -1866,7 +1866,7 @@ void TileSetAtlasSourceEditor::_undo_redo_inspector_callback(Object *p_undo_redo UndoRedo *undo_redo = Object::cast_to<UndoRedo>(p_undo_redo); ERR_FAIL_COND(!undo_redo); -#define ADD_UNDO(obj, property) undo_redo->add_undo_property(obj, property, tile_data->get(property)); +#define ADD_UNDO(obj, property) undo_redo->add_undo_property(obj, property, obj->get(property)); AtlasTileProxyObject *tile_data = Object::cast_to<AtlasTileProxyObject>(p_edited); if (tile_data) { diff --git a/editor/plugins/tiles/tile_set_editor.cpp b/editor/plugins/tiles/tile_set_editor.cpp index ba98a7d6b3..48d0d9b333 100644 --- a/editor/plugins/tiles/tile_set_editor.cpp +++ b/editor/plugins/tiles/tile_set_editor.cpp @@ -330,11 +330,192 @@ void TileSetEditor::_tile_set_changed() { tile_set_changed_needs_update = true; } +void TileSetEditor::_move_tile_set_array_element(Object *p_undo_redo, Object *p_edited, String p_array_prefix, int p_from_index, int p_to_pos) { + UndoRedo *undo_redo = Object::cast_to<UndoRedo>(p_undo_redo); + ERR_FAIL_COND(!undo_redo); + + TileSet *tile_set = Object::cast_to<TileSet>(p_edited); + if (!tile_set) { + return; + } + + Vector<String> components = String(p_array_prefix).split("/", true, 2); + + // Compute the array indices to save. + int begin = 0; + int end; + if (p_array_prefix == "occlusion_layer_") { + end = tile_set->get_occlusion_layers_count(); + } else if (p_array_prefix == "physics_layer_") { + end = tile_set->get_physics_layers_count(); + } else if (p_array_prefix == "terrain_set_") { + end = tile_set->get_terrain_sets_count(); + } else if (components.size() >= 2 && components[0].begins_with("terrain_set_") && components[0].trim_prefix("terrain_set_").is_valid_int() && components[1] == "terrain_") { + int terrain_set = components[0].trim_prefix("terrain_set_").to_int(); + end = tile_set->get_terrains_count(terrain_set); + } else if (p_array_prefix == "navigation_layer_") { + end = tile_set->get_navigation_layers_count(); + } else if (p_array_prefix == "custom_data_layer_") { + end = tile_set->get_custom_data_layers_count(); + } else { + ERR_FAIL_MSG("Invalid array prefix for TileSet."); + } + if (p_from_index < 0) { + // Adding new. + if (p_to_pos >= 0) { + begin = p_to_pos; + } else { + end = 0; // Nothing to save when adding at the end. + } + } else if (p_to_pos < 0) { + // Removing. + begin = p_from_index; + } else { + // Moving. + begin = MIN(p_from_index, p_to_pos); + end = MIN(MAX(p_from_index, p_to_pos) + 1, end); + } + +#define ADD_UNDO(obj, property) undo_redo->add_undo_property(obj, property, obj->get(property)); + // Save layers' properties. + List<PropertyInfo> properties; + tile_set->get_property_list(&properties); + for (PropertyInfo pi : properties) { + if (pi.name.begins_with(p_array_prefix)) { + String str = pi.name.trim_prefix(p_array_prefix); + int to_char_index = 0; + while (to_char_index < str.length()) { + if (str[to_char_index] < '0' || str[to_char_index] > '9') { + break; + } + to_char_index++; + } + if (to_char_index > 0) { + int array_index = str.left(to_char_index).to_int(); + if (array_index >= begin && array_index < end) { + ADD_UNDO(tile_set, pi.name); + } + } + } + } + + // Save properties for TileSetAtlasSources tile data + for (int i = 0; i < tile_set->get_source_count(); i++) { + int source_id = tile_set->get_source_id(i); + + Ref<TileSetAtlasSource> tas = tile_set->get_source(source_id); + if (tas.is_valid()) { + for (int j = 0; j < tas->get_tiles_count(); j++) { + Vector2i tile_id = tas->get_tile_id(j); + for (int k = 0; k < tas->get_alternative_tiles_count(tile_id); k++) { + int alternative_id = tas->get_alternative_tile_id(tile_id, k); + TileData *tile_data = Object::cast_to<TileData>(tas->get_tile_data(tile_id, alternative_id)); + ERR_FAIL_COND(!tile_data); + + // Actually saving stuff. + if (p_array_prefix == "occlusion_layer_") { + for (int layer_index = begin; layer_index < end; layer_index++) { + ADD_UNDO(tile_data, vformat("occlusion_layer_%d/polygon", layer_index)); + } + } else if (p_array_prefix == "physics_layer_") { + for (int layer_index = begin; layer_index < end; layer_index++) { + ADD_UNDO(tile_data, vformat("physics_layer_%d/polygons_count", layer_index)); + for (int polygon_index = 0; polygon_index < tile_data->get_collision_polygons_count(layer_index); polygon_index++) { + ADD_UNDO(tile_data, vformat("physics_layer_%d/polygon_%d/points", layer_index, polygon_index)); + ADD_UNDO(tile_data, vformat("physics_layer_%d/polygon_%d/one_way", layer_index, polygon_index)); + ADD_UNDO(tile_data, vformat("physics_layer_%d/polygon_%d/one_way_margin", layer_index, polygon_index)); + } + } + } else if (p_array_prefix == "terrain_set_") { + ADD_UNDO(tile_data, "terrain_set"); + for (int terrain_set_index = begin; terrain_set_index < end; terrain_set_index++) { + for (int l = 0; l < TileSet::CELL_NEIGHBOR_MAX; l++) { + TileSet::CellNeighbor bit = TileSet::CellNeighbor(l); + if (tile_data->is_valid_peering_bit_terrain(bit)) { + ADD_UNDO(tile_data, "terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[l])); + } + } + } + } else if (components.size() >= 2 && components[0].begins_with("terrain_set_") && components[0].trim_prefix("terrain_set_").is_valid_int() && components[1] == "terrain_") { + for (int terrain_index = 0; terrain_index < TileSet::CELL_NEIGHBOR_MAX; terrain_index++) { + TileSet::CellNeighbor bit = TileSet::CellNeighbor(terrain_index); + if (tile_data->is_valid_peering_bit_terrain(bit)) { + ADD_UNDO(tile_data, "terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[terrain_index])); + } + } + } else if (p_array_prefix == "navigation_layer_") { + for (int layer_index = begin; layer_index < end; layer_index++) { + ADD_UNDO(tile_data, vformat("navigation_layer_%d/polygon", layer_index)); + } + } else if (p_array_prefix == "custom_data_layer_") { + for (int layer_index = begin; layer_index < end; layer_index++) { + ADD_UNDO(tile_data, vformat("custom_data_%d", layer_index)); + } + } + } + } + } + } +#undef ADD_UNDO + + // Add do method. + if (p_array_prefix == "occlusion_layer_") { + if (p_from_index < 0) { + undo_redo->add_do_method(tile_set, "add_occlusion_layer", p_to_pos); + } else if (p_to_pos < 0) { + undo_redo->add_do_method(tile_set, "remove_occlusion_layer", p_from_index); + } else { + undo_redo->add_do_method(tile_set, "move_occlusion_layer", p_from_index, p_to_pos); + } + } else if (p_array_prefix == "physics_layer_") { + if (p_from_index < 0) { + undo_redo->add_do_method(tile_set, "add_physics_layer", p_to_pos); + } else if (p_to_pos < 0) { + undo_redo->add_do_method(tile_set, "remove_physics_layer", p_from_index); + } else { + undo_redo->add_do_method(tile_set, "move_physics_layer", p_from_index, p_to_pos); + } + } else if (p_array_prefix == "terrain_set_") { + if (p_from_index < 0) { + undo_redo->add_do_method(tile_set, "add_terrain_set", p_to_pos); + } else if (p_to_pos < 0) { + undo_redo->add_do_method(tile_set, "remove_terrain_set", p_from_index); + } else { + undo_redo->add_do_method(tile_set, "move_terrain_set", p_from_index, p_to_pos); + } + } else if (components.size() >= 2 && components[0].begins_with("terrain_set_") && components[0].trim_prefix("terrain_set_").is_valid_int() && components[1] == "terrain_") { + int terrain_set = components[0].trim_prefix("terrain_set_").to_int(); + if (p_from_index < 0) { + undo_redo->add_do_method(tile_set, "add_terrain", terrain_set, p_to_pos); + } else if (p_to_pos < 0) { + undo_redo->add_do_method(tile_set, "remove_terrain", terrain_set, p_from_index); + } else { + undo_redo->add_do_method(tile_set, "move_terrain", terrain_set, p_from_index, p_to_pos); + } + } else if (p_array_prefix == "navigation_layer_") { + if (p_from_index < 0) { + undo_redo->add_do_method(tile_set, "add_navigation_layer", p_to_pos); + } else if (p_to_pos < 0) { + undo_redo->add_do_method(tile_set, "remove_navigation_layer", p_from_index); + } else { + undo_redo->add_do_method(tile_set, "move_navigation_layer", p_from_index, p_to_pos); + } + } else if (p_array_prefix == "custom_data_layer_") { + if (p_from_index < 0) { + undo_redo->add_do_method(tile_set, "add_custom_data_layer", p_to_pos); + } else if (p_to_pos < 0) { + undo_redo->add_do_method(tile_set, "remove_custom_data_layer", p_from_index); + } else { + undo_redo->add_do_method(tile_set, "move_custom_data_layer", p_from_index, p_to_pos); + } + } +} + void TileSetEditor::_undo_redo_inspector_callback(Object *p_undo_redo, Object *p_edited, String p_property, Variant p_new_value) { UndoRedo *undo_redo = Object::cast_to<UndoRedo>(p_undo_redo); ERR_FAIL_COND(!undo_redo); -#define ADD_UNDO(obj, property) undo_redo->add_undo_property(obj, property, tile_data->get(property)); +#define ADD_UNDO(obj, property) undo_redo->add_undo_property(obj, property, obj->get(property)); TileSet *tile_set = Object::cast_to<TileSet>(p_edited); if (tile_set) { Vector<String> components = p_property.split("/", true, 3); @@ -350,30 +531,7 @@ void TileSetEditor::_undo_redo_inspector_callback(Object *p_undo_redo, Object *p TileData *tile_data = Object::cast_to<TileData>(tas->get_tile_data(tile_id, alternative_id)); ERR_FAIL_COND(!tile_data); - if (p_property == "occlusion_layers_count") { - int new_layer_count = p_new_value; - int old_layer_count = tile_set->get_occlusion_layers_count(); - if (new_layer_count < old_layer_count) { - for (int occclusion_layer_index = new_layer_count - 1; occclusion_layer_index < old_layer_count; occclusion_layer_index++) { - ADD_UNDO(tile_data, vformat("occlusion_layer_%d/polygon", occclusion_layer_index)); - } - } - } else if (p_property == "physics_layers_count") { - int new_layer_count = p_new_value; - int old_layer_count = tile_set->get_physics_layers_count(); - if (new_layer_count < old_layer_count) { - for (int physics_layer_index = new_layer_count - 1; physics_layer_index < old_layer_count; physics_layer_index++) { - ADD_UNDO(tile_data, vformat("physics_layer_%d/polygons_count", physics_layer_index)); - for (int polygon_index = 0; polygon_index < tile_data->get_collision_polygons_count(physics_layer_index); polygon_index++) { - ADD_UNDO(tile_data, vformat("physics_layer_%d/polygon_%d/points", physics_layer_index, polygon_index)); - ADD_UNDO(tile_data, vformat("physics_layer_%d/polygon_%d/one_way", physics_layer_index, polygon_index)); - ADD_UNDO(tile_data, vformat("physics_layer_%d/polygon_%d/one_way_margin", physics_layer_index, polygon_index)); - } - } - } - } else if ((p_property == "terrains_sets_count" && tile_data->get_terrain_set() >= (int)p_new_value) || - (components.size() == 2 && components[0].begins_with("terrain_set_") && components[0].trim_prefix("terrain_set_").is_valid_int() && components[1] == "mode") || - (components.size() == 2 && components[0].begins_with("terrain_set_") && components[0].trim_prefix("terrain_set_").is_valid_int() && components[1] == "terrains_count" && tile_data->get_terrain_set() == components[0].trim_prefix("terrain_set_").to_int() && (int)p_new_value < tile_set->get_terrains_count(tile_data->get_terrain_set()))) { + if (components.size() == 2 && components[0].begins_with("terrain_set_") && components[0].trim_prefix("terrain_set_").is_valid_int() && components[1] == "mode") { ADD_UNDO(tile_data, "terrain_set"); for (int l = 0; l < TileSet::CELL_NEIGHBOR_MAX; l++) { TileSet::CellNeighbor bit = TileSet::CellNeighbor(l); @@ -381,22 +539,6 @@ void TileSetEditor::_undo_redo_inspector_callback(Object *p_undo_redo, Object *p ADD_UNDO(tile_data, "terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[l])); } } - } else if (p_property == "navigation_layers_count") { - int new_layer_count = p_new_value; - int old_layer_count = tile_set->get_navigation_layers_count(); - if (new_layer_count < old_layer_count) { - for (int navigation_layer_index = new_layer_count - 1; navigation_layer_index < old_layer_count; navigation_layer_index++) { - ADD_UNDO(tile_data, vformat("navigation_layer_%d/polygon", navigation_layer_index)); - } - } - } else if (p_property == "custom_data_layers_count") { - int new_layer_count = p_new_value; - int old_layer_count = tile_set->get_custom_data_layers_count(); - if (new_layer_count < old_layer_count) { - for (int custom_data_layer_index = new_layer_count - 1; custom_data_layer_index < old_layer_count; custom_data_layer_index++) { - ADD_UNDO(tile_data, vformat("custom_data_%d", custom_data_layer_index)); - } - } } else if (components.size() == 2 && components[0].begins_with("custom_data_layer_") && components[0].trim_prefix("custom_data_layer_").is_valid_int() && components[1] == "type") { int custom_data_layer = components[0].trim_prefix("custom_data_layer_").is_valid_int(); ADD_UNDO(tile_data, vformat("custom_data_%d", custom_data_layer)); @@ -531,6 +673,7 @@ TileSetEditor::TileSetEditor() { tile_set_scenes_collection_source_editor->hide(); // Registers UndoRedo inspector callback. + EditorNode::get_singleton()->get_editor_data().add_move_array_element_function(SNAME("TileSet"), callable_mp(this, &TileSetEditor::_move_tile_set_array_element)); EditorNode::get_singleton()->get_editor_data().add_undo_redo_inspector_hook_callback(callable_mp(this, &TileSetEditor::_undo_redo_inspector_callback)); } diff --git a/editor/plugins/tiles/tile_set_editor.h b/editor/plugins/tiles/tile_set_editor.h index 970e3fabb6..fe854b2281 100644 --- a/editor/plugins/tiles/tile_set_editor.h +++ b/editor/plugins/tiles/tile_set_editor.h @@ -71,6 +71,7 @@ private: void _tile_set_changed(); + void _move_tile_set_array_element(Object *p_undo_redo, Object *p_edited, String p_array_prefix, int p_from_index, int p_to_pos); void _undo_redo_inspector_callback(Object *p_undo_redo, Object *p_edited, String p_property, Variant p_new_value); protected: diff --git a/editor/pot_generator.cpp b/editor/pot_generator.cpp index b58b7e4cac..d57345cac1 100644 --- a/editor/pot_generator.cpp +++ b/editor/pot_generator.cpp @@ -108,7 +108,6 @@ void POTGenerator::_write_to_pot(const String &p_file) { const String header = "# LANGUAGE translation for " + project_name + " for the following files:\n" + extracted_files + "#\n" - "#\n" "# FIRST AUTHOR < EMAIL @ADDRESS>, YEAR.\n" "#\n" "#, fuzzy\n" @@ -116,8 +115,9 @@ void POTGenerator::_write_to_pot(const String &p_file) { "msgstr \"\"\n" "\"Project-Id-Version: " + project_name + "\\n\"\n" + "\"MIME-Version: 1.0\\n\"\n" "\"Content-Type: text/plain; charset=UTF-8\\n\"\n" - "\"Content-Transfer-Encoding: 8-bit\\n\"\n\n"; + "\"Content-Transfer-Encoding: 8-bit\\n\"\n"; file->store_string(header); @@ -129,6 +129,9 @@ void POTGenerator::_write_to_pot(const String &p_file) { String plural = v_msgid_data[i].plural; const Set<String> &locations = v_msgid_data[i].locations; + // Put the blank line at the start, to avoid a double at the end when closing the file. + file->store_line(""); + // Write file locations. for (Set<String>::Element *E = locations.front(); E; E = E->next()) { file->store_line("#: " + E->get().trim_prefix("res://")); @@ -142,13 +145,13 @@ void POTGenerator::_write_to_pot(const String &p_file) { // Write msgid. _write_msgid(file, msgid, false); - // Write msgid_plural + // Write msgid_plural. if (!plural.is_empty()) { _write_msgid(file, plural, true); file->store_line("msgstr[0] \"\""); - file->store_line("msgstr[1] \"\"\n"); + file->store_line("msgstr[1] \"\""); } else { - file->store_line("msgstr \"\"\n"); + file->store_line("msgstr \"\""); } } } diff --git a/editor/project_settings_editor.cpp b/editor/project_settings_editor.cpp index b8ccab78dd..db12e90540 100644 --- a/editor/project_settings_editor.cpp +++ b/editor/project_settings_editor.cpp @@ -222,7 +222,6 @@ void ProjectSettingsEditor::_add_feature_overrides() { presets.insert("standalone"); presets.insert("32"); presets.insert("64"); - presets.insert("Server"); // Not available as an export platform yet, so it needs to be added manually EditorExport *ee = EditorExport::get_singleton(); diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp index 2ec4a088a2..a08a628216 100644 --- a/editor/scene_tree_dock.cpp +++ b/editor/scene_tree_dock.cpp @@ -2837,6 +2837,11 @@ void SceneTreeDock::set_filter(const String &p_filter) { scene_tree->set_filter(p_filter); } +void SceneTreeDock::save_branch_to_file(String p_directory) { + new_scene_from_dialog->set_current_dir(p_directory); + _tool_selected(TOOL_NEW_SCENE_FROM); +} + void SceneTreeDock::_focus_node() { Node *node = scene_tree->get_selected(); ERR_FAIL_COND(!node); diff --git a/editor/scene_tree_dock.h b/editor/scene_tree_dock.h index 387a35acbb..1086e8615f 100644 --- a/editor/scene_tree_dock.h +++ b/editor/scene_tree_dock.h @@ -269,6 +269,7 @@ protected: public: String get_filter(); void set_filter(const String &p_filter); + void save_branch_to_file(String p_directory); void _focus_node(); diff --git a/editor/scene_tree_editor.cpp b/editor/scene_tree_editor.cpp index e7ba80677d..d54bf73028 100644 --- a/editor/scene_tree_editor.cpp +++ b/editor/scene_tree_editor.cpp @@ -291,10 +291,12 @@ bool SceneTreeEditor::_add_nodes(Node *p_node, TreeItem *p_parent, bool p_scroll } } + // Display the node name in all tooltips so that long node names can be previewed + // without having to rename them. if (p_node == get_scene_node() && p_node->get_scene_inherited_state().is_valid()) { item->add_button(0, get_theme_icon(SNAME("InstanceOptions"), SNAME("EditorIcons")), BUTTON_SUBSCENE, false, TTR("Open in Editor")); - String tooltip = TTR("Inherits:") + " " + p_node->get_scene_inherited_state()->get_path() + "\n" + TTR("Type:") + " " + p_node->get_class(); + String tooltip = String(p_node->get_name()) + "\n" + TTR("Inherits:") + " " + p_node->get_scene_inherited_state()->get_path() + "\n" + TTR("Type:") + " " + p_node->get_class(); if (p_node->get_editor_description() != String()) { tooltip += "\n\n" + p_node->get_editor_description(); } @@ -303,7 +305,7 @@ bool SceneTreeEditor::_add_nodes(Node *p_node, TreeItem *p_parent, bool p_scroll } else if (p_node != get_scene_node() && p_node->get_filename() != "" && can_open_instance) { item->add_button(0, get_theme_icon(SNAME("InstanceOptions"), SNAME("EditorIcons")), BUTTON_SUBSCENE, false, TTR("Open in Editor")); - String tooltip = TTR("Instance:") + " " + p_node->get_filename() + "\n" + TTR("Type:") + " " + p_node->get_class(); + String tooltip = String(p_node->get_name()) + "\n" + TTR("Instance:") + " " + p_node->get_filename() + "\n" + TTR("Type:") + " " + p_node->get_class(); if (p_node->get_editor_description() != String()) { tooltip += "\n\n" + p_node->get_editor_description(); } @@ -315,7 +317,7 @@ bool SceneTreeEditor::_add_nodes(Node *p_node, TreeItem *p_parent, bool p_scroll type = p_node->get_class(); } - String tooltip = TTR("Type:") + " " + type; + String tooltip = String(p_node->get_name()) + "\n" + TTR("Type:") + " " + type; if (p_node->get_editor_description() != String()) { tooltip += "\n\n" + p_node->get_editor_description(); } diff --git a/main/main.cpp b/main/main.cpp index 9076d110bd..fe6df43364 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -403,6 +403,7 @@ Error Main::test_setup() { GLOBAL_DEF("debug/settings/crash_handler/message", String("Please include this when reporting the bug on https://github.com/godotengine/godot/issues")); + GLOBAL_DEF_RST("rendering/occlusion_culling/bvh_build_quality", 2); translation_server = memnew(TranslationServer); @@ -1559,8 +1560,8 @@ Error Main::setup2(Thread::ID p_main_tid_override) { { GLOBAL_DEF_RST_NOVAL("input_devices/pen_tablet/driver", ""); - GLOBAL_DEF_RST_NOVAL("input_devices/pen_tablet/driver.Windows", ""); - ProjectSettings::get_singleton()->set_custom_property_info("input_devices/pen_tablet/driver.Windows", PropertyInfo(Variant::STRING, "input_devices/pen_tablet/driver.Windows", PROPERTY_HINT_ENUM, "wintab,winink")); + GLOBAL_DEF_RST_NOVAL("input_devices/pen_tablet/driver.windows", ""); + ProjectSettings::get_singleton()->set_custom_property_info("input_devices/pen_tablet/driver.windows", PropertyInfo(Variant::STRING, "input_devices/pen_tablet/driver.windows", PROPERTY_HINT_ENUM, "wintab,winink")); } if (tablet_driver == "") { // specified in project.godot diff --git a/main/main_timer_sync.cpp b/main/main_timer_sync.cpp index 0d172be65e..42023e5a2f 100644 --- a/main/main_timer_sync.cpp +++ b/main/main_timer_sync.cpp @@ -57,7 +57,7 @@ int MainTimerSync::get_average_physics_steps(double &p_min, double &p_max) { const double typical_lower = typical_physics_steps[i]; const double current_min = typical_lower / (i + 1); if (current_min > p_max) { - return i; // bail out of further restrictions would void the interval + return i; // bail out if further restrictions would void the interval } else if (current_min > p_min) { p_min = current_min; } @@ -105,6 +105,12 @@ MainFrameTime MainTimerSync::advance_core(double p_physics_step, int p_physics_t } } +#ifdef DEBUG_ENABLED + if (max_typical_steps < 0) { + WARN_PRINT_ONCE("`max_typical_steps` is negative. This could hint at an engine bug or system timer misconfiguration."); + } +#endif + // try to keep it consistent with previous iterations if (ret.physics_steps < min_typical_steps) { const int max_possible_steps = floor((time_accum)*p_physics_ticks_per_second + get_physics_jitter_fix()); @@ -124,6 +130,10 @@ MainFrameTime MainTimerSync::advance_core(double p_physics_step, int p_physics_t } } + if (ret.physics_steps < 0) { + ret.physics_steps = 0; + } + time_accum -= ret.physics_steps * p_physics_step; // keep track of accumulated step counts @@ -151,6 +161,9 @@ MainFrameTime MainTimerSync::advance_checked(double p_physics_step, int p_physic p_process_step = 1.0 / fixed_fps; } + float min_output_step = p_process_step / 8; + min_output_step = MAX(min_output_step, 1E-6); + // compensate for last deficit p_process_step += time_deficit; @@ -177,9 +190,37 @@ MainFrameTime MainTimerSync::advance_checked(double p_physics_step, int p_physic // last clamping: make sure time_accum is between 0 and p_physics_step for consistency between physics and process ret.clamp_process_step(process_minus_accum, process_minus_accum + p_physics_step); + // all the operations above may have turned ret.p_process_step negative or zero, keep a minimal value + if (ret.process_step < min_output_step) { + ret.process_step = min_output_step; + } + // restore time_accum time_accum = ret.process_step - process_minus_accum; + // forcing ret.process_step to be positive may trigger a violation of the + // promise that time_accum is between 0 and p_physics_step +#ifdef DEBUG_ENABLED + if (time_accum < -1E-7) { + WARN_PRINT_ONCE("Intermediate value of `time_accum` is negative. This could hint at an engine bug or system timer misconfiguration."); + } +#endif + + if (time_accum > p_physics_step) { + const int extra_physics_steps = floor(time_accum * p_physics_ticks_per_second); + time_accum -= extra_physics_steps * p_physics_step; + ret.physics_steps += extra_physics_steps; + } + +#ifdef DEBUG_ENABLED + if (time_accum < -1E-7) { + WARN_PRINT_ONCE("Final value of `time_accum` is negative. It should always be between 0 and `p_physics_step`. This hints at an engine bug."); + } + if (time_accum > p_physics_step + 1E-7) { + WARN_PRINT_ONCE("Final value of `time_accum` is larger than `p_physics_step`. It should always be between 0 and `p_physics_step`. This hints at an engine bug."); + } +#endif + // track deficit time_deficit = p_process_step - ret.process_step; diff --git a/modules/bullet/space_bullet.h b/modules/bullet/space_bullet.h index 2070e0e633..cf8549030d 100644 --- a/modules/bullet/space_bullet.h +++ b/modules/bullet/space_bullet.h @@ -76,13 +76,13 @@ private: public: BulletPhysicsDirectSpaceState(SpaceBullet *p_space); - virtual int intersect_point(const Vector3 &p_point, ShapeResult *r_results, int p_result_max, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = 0xFFFFFFFF, bool p_collide_with_bodies = true, bool p_collide_with_areas = false) override; - virtual bool intersect_ray(const Vector3 &p_from, const Vector3 &p_to, RayResult &r_result, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = 0xFFFFFFFF, bool p_collide_with_bodies = true, bool p_collide_with_areas = false, bool p_pick_ray = false) override; - virtual int intersect_shape(const RID &p_shape, const Transform3D &p_xform, real_t p_margin, ShapeResult *r_results, int p_result_max, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = 0xFFFFFFFF, bool p_collide_with_bodies = true, bool p_collide_with_areas = false) override; - virtual bool cast_motion(const RID &p_shape, const Transform3D &p_xform, const Vector3 &p_motion, real_t p_margin, real_t &r_closest_safe, real_t &r_closest_unsafe, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = 0xFFFFFFFF, bool p_collide_with_bodies = true, bool p_collide_with_areas = false, ShapeRestInfo *r_info = nullptr) override; + virtual int intersect_point(const Vector3 &p_point, ShapeResult *r_results, int p_result_max, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = UINT32_MAX, bool p_collide_with_bodies = true, bool p_collide_with_areas = false) override; + virtual bool intersect_ray(const Vector3 &p_from, const Vector3 &p_to, RayResult &r_result, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = UINT32_MAX, bool p_collide_with_bodies = true, bool p_collide_with_areas = false, bool p_pick_ray = false) override; + virtual int intersect_shape(const RID &p_shape, const Transform3D &p_xform, real_t p_margin, ShapeResult *r_results, int p_result_max, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = UINT32_MAX, bool p_collide_with_bodies = true, bool p_collide_with_areas = false) override; + virtual bool cast_motion(const RID &p_shape, const Transform3D &p_xform, const Vector3 &p_motion, real_t p_margin, real_t &r_closest_safe, real_t &r_closest_unsafe, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = UINT32_MAX, bool p_collide_with_bodies = true, bool p_collide_with_areas = false, ShapeRestInfo *r_info = nullptr) override; /// Returns the list of contacts pairs in this order: Local contact, other body contact - virtual bool collide_shape(RID p_shape, const Transform3D &p_shape_xform, real_t p_margin, Vector3 *r_results, int p_result_max, int &r_result_count, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = 0xFFFFFFFF, bool p_collide_with_bodies = true, bool p_collide_with_areas = false) override; - virtual bool rest_info(RID p_shape, const Transform3D &p_shape_xform, real_t p_margin, ShapeRestInfo *r_info, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = 0xFFFFFFFF, bool p_collide_with_bodies = true, bool p_collide_with_areas = false) override; + virtual bool collide_shape(RID p_shape, const Transform3D &p_shape_xform, real_t p_margin, Vector3 *r_results, int p_result_max, int &r_result_count, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = UINT32_MAX, bool p_collide_with_bodies = true, bool p_collide_with_areas = false) override; + virtual bool rest_info(RID p_shape, const Transform3D &p_shape_xform, real_t p_margin, ShapeRestInfo *r_info, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = UINT32_MAX, bool p_collide_with_bodies = true, bool p_collide_with_areas = false) override; virtual Vector3 get_closest_point_to_object_volume(RID p_object, const Vector3 p_point) const override; }; diff --git a/modules/camera/camera_osx.mm b/modules/camera/camera_osx.mm index 4875eb578a..02f7287d1b 100644 --- a/modules/camera/camera_osx.mm +++ b/modules/camera/camera_osx.mm @@ -33,6 +33,7 @@ #include "camera_osx.h" #include "servers/camera/camera_feed.h" + #import <AVFoundation/AVFoundation.h> ////////////////////////////////////////////////////////////////////////// @@ -253,10 +254,25 @@ CameraFeedOSX::~CameraFeedOSX() { bool CameraFeedOSX::activate_feed() { if (capture_session) { - // already recording! + // Already recording! } else { - // start camera capture - capture_session = [[MyCaptureSession alloc] initForFeed:this andDevice:device]; + // Start camera capture, check permission. + if (@available(macOS 10.14, *)) { + AVAuthorizationStatus status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo]; + if (status == AVAuthorizationStatusAuthorized) { + capture_session = [[MyCaptureSession alloc] initForFeed:this andDevice:device]; + } else if (status == AVAuthorizationStatusNotDetermined) { + // Request permission. + [AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo + completionHandler:^(BOOL granted) { + if (granted) { + capture_session = [[MyCaptureSession alloc] initForFeed:this andDevice:device]; + } + }]; + } + } else { + capture_session = [[MyCaptureSession alloc] initForFeed:this andDevice:device]; + } }; return true; diff --git a/modules/enet/doc_classes/ENetMultiplayerPeer.xml b/modules/enet/doc_classes/ENetMultiplayerPeer.xml index 3a37b396a4..43e1d40e47 100644 --- a/modules/enet/doc_classes/ENetMultiplayerPeer.xml +++ b/modules/enet/doc_classes/ENetMultiplayerPeer.xml @@ -4,7 +4,7 @@ A MultiplayerPeer implementation using the [url=http://enet.bespin.org/index.html]ENet[/url] library. </brief_description> <description> - A MultiplayerPeer implementation that should be passed to [member MultiplayerAPI.network_peer] after being initialized as either a client, server, or mesh. Events can then be handled by connecting to [MultiplayerAPI] signals. See [ENetConnection] for more information on the ENet library wrapper. + A MultiplayerPeer implementation that should be passed to [member MultiplayerAPI.multiplayer_peer] after being initialized as either a client, server, or mesh. Events can then be handled by connecting to [MultiplayerAPI] signals. See [ENetConnection] for more information on the ENet library wrapper. [b]Note:[/b] ENet only uses UDP, not TCP. When forwarding the server port to make your server accessible on the public Internet, you only need to forward the server port in UDP. You can use the [UPNP] class to try to forward the server port automatically when starting the server. </description> <tutorials> @@ -44,7 +44,7 @@ <return type="int" enum="Error" /> <argument index="0" name="unique_id" type="int" /> <description> - Initialize this [MultiplayerPeer] in mesh mode. The provided [code]unique_id[/code] will be used as the local peer network unique ID once assigned as the [member MultiplayerAPI.network_peer]. In the mesh configuration you will need to set up each new peer manually using [ENetConnection] before calling [method add_mesh_peer]. While this technique is more advanced, it allows for better control over the connection process (e.g. when dealing with NAT punch-through) and for better distribution of the network load (which would otherwise be more taxing on the server). + Initialize this [MultiplayerPeer] in mesh mode. The provided [code]unique_id[/code] will be used as the local peer network unique ID once assigned as the [member MultiplayerAPI.multiplayer_peer]. In the mesh configuration you will need to set up each new peer manually using [ENetConnection] before calling [method add_mesh_peer]. While this technique is more advanced, it allows for better control over the connection process (e.g. when dealing with NAT punch-through) and for better distribution of the network load (which would otherwise be more taxing on the server). </description> </method> <method name="create_server"> @@ -81,7 +81,7 @@ <member name="server_relay" type="bool" setter="set_server_relay_enabled" getter="is_server_relay_enabled" default="true"> 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_mode" type="int" setter="set_transfer_mode" getter="get_transfer_mode" override="true" enum="MultiplayerPeer.TransferMode" default="2" /> + <member name="transfer_mode" type="int" setter="set_transfer_mode" getter="get_transfer_mode" override="true" enum="TransferMode" default="2" /> </members> <constants> </constants> diff --git a/modules/enet/enet_multiplayer_peer.cpp b/modules/enet/enet_multiplayer_peer.cpp index 38ca38385c..afd31207f6 100644 --- a/modules/enet/enet_multiplayer_peer.cpp +++ b/modules/enet/enet_multiplayer_peer.cpp @@ -41,11 +41,11 @@ int ENetMultiplayerPeer::get_transfer_channel() const { return transfer_channel; } -void ENetMultiplayerPeer::set_transfer_mode(TransferMode p_mode) { +void ENetMultiplayerPeer::set_transfer_mode(Multiplayer::TransferMode p_mode) { transfer_mode = p_mode; } -MultiplayerPeer::TransferMode ENetMultiplayerPeer::get_transfer_mode() const { +Multiplayer::TransferMode ENetMultiplayerPeer::get_transfer_mode() const { return transfer_mode; } @@ -393,7 +393,9 @@ bool ENetMultiplayerPeer::is_server() const { } void ENetMultiplayerPeer::close_connection(uint32_t wait_usec) { - ERR_FAIL_COND_MSG(!_is_active(), "The multiplayer instance isn't currently active."); + if (!_is_active()) { + return; + } _pop_current_packet(); @@ -453,15 +455,15 @@ Error ENetMultiplayerPeer::put_packet(const uint8_t *p_buffer, int p_buffer_size channel = SYSCH_MAX + transfer_channel - 1; } else { switch (transfer_mode) { - case TRANSFER_MODE_UNRELIABLE: { + case Multiplayer::TRANSFER_MODE_UNRELIABLE: { packet_flags = ENET_PACKET_FLAG_UNSEQUENCED; channel = SYSCH_UNRELIABLE; } break; - case TRANSFER_MODE_UNRELIABLE_ORDERED: { + case Multiplayer::TRANSFER_MODE_ORDERED: { packet_flags = 0; channel = SYSCH_UNRELIABLE; } break; - case TRANSFER_MODE_RELIABLE: { + case Multiplayer::TRANSFER_MODE_RELIABLE: { packet_flags = ENET_PACKET_FLAG_RELIABLE; channel = SYSCH_RELIABLE; } break; diff --git a/modules/enet/enet_multiplayer_peer.h b/modules/enet/enet_multiplayer_peer.h index 78e280db7c..b5316b8292 100644 --- a/modules/enet/enet_multiplayer_peer.h +++ b/modules/enet/enet_multiplayer_peer.h @@ -32,7 +32,7 @@ #define NETWORKED_MULTIPLAYER_ENET_H #include "core/crypto/crypto.h" -#include "core/io/multiplayer_peer.h" +#include "core/multiplayer/multiplayer_peer.h" #include "enet_connection.h" #include <enet/enet.h> @@ -66,7 +66,7 @@ private: int target_peer = 0; int transfer_channel = 0; - TransferMode transfer_mode = TRANSFER_MODE_RELIABLE; + Multiplayer::TransferMode transfer_mode = Multiplayer::TRANSFER_MODE_RELIABLE; bool refuse_connections = false; bool server_relay = true; @@ -104,8 +104,8 @@ public: virtual void set_transfer_channel(int p_channel) override; virtual int get_transfer_channel() const override; - virtual void set_transfer_mode(TransferMode p_mode) override; - virtual TransferMode get_transfer_mode() const override; + virtual void set_transfer_mode(Multiplayer::TransferMode p_mode) override; + virtual Multiplayer::TransferMode get_transfer_mode() const override; virtual void set_target_peer(int p_peer) override; virtual int get_packet_peer() const override; diff --git a/modules/gdnative/SCsub b/modules/gdnative/SCsub index 45354ce692..21ee39f3ed 100644 --- a/modules/gdnative/SCsub +++ b/modules/gdnative/SCsub @@ -17,7 +17,6 @@ env_gdnative.Prepend(CPPPATH=["#modules/gdnative/include/"]) Export("env_gdnative") SConscript("net/SCsub") -SConscript("xr/SCsub") SConscript("pluginscript/SCsub") SConscript("videodecoder/SCsub") SConscript("text/SCsub") diff --git a/modules/gdnative/nativescript/godot_nativescript.cpp b/modules/gdnative/nativescript/godot_nativescript.cpp index 70b14836bf..dadd1a9d10 100644 --- a/modules/gdnative/nativescript/godot_nativescript.cpp +++ b/modules/gdnative/nativescript/godot_nativescript.cpp @@ -127,9 +127,9 @@ void GDAPI godot_nativescript_register_method(void *p_gdnative_handle, const cha E->get().methods.insert(p_function_name, method); if (p_attr.rpc_type != GODOT_METHOD_RPC_MODE_DISABLED) { - MultiplayerAPI::RPCConfig nd; + Multiplayer::RPCConfig nd; nd.name = String(p_name); - nd.rpc_mode = MultiplayerAPI::RPCMode(p_attr.rpc_type); + nd.rpc_mode = Multiplayer::RPCMode(p_attr.rpc_type); E->get().rpc_methods.push_back(nd); } } diff --git a/modules/gdnative/nativescript/nativescript.cpp b/modules/gdnative/nativescript/nativescript.cpp index 26d3aed702..92ba9bd452 100644 --- a/modules/gdnative/nativescript/nativescript.cpp +++ b/modules/gdnative/nativescript/nativescript.cpp @@ -431,9 +431,9 @@ void NativeScript::get_script_property_list(List<PropertyInfo> *p_list) const { } } -const Vector<MultiplayerAPI::RPCConfig> NativeScript::get_rpc_methods() const { +const Vector<Multiplayer::RPCConfig> NativeScript::get_rpc_methods() const { NativeScriptDesc *script_data = get_script_desc(); - ERR_FAIL_COND_V(!script_data, Vector<MultiplayerAPI::RPCConfig>()); + ERR_FAIL_COND_V(!script_data, Vector<Multiplayer::RPCConfig>()); return script_data->rpc_methods; } @@ -828,7 +828,7 @@ Ref<Script> NativeScriptInstance::get_script() const { return script; } -const Vector<MultiplayerAPI::RPCConfig> NativeScriptInstance::get_rpc_methods() const { +const Vector<Multiplayer::RPCConfig> NativeScriptInstance::get_rpc_methods() const { return script->get_rpc_methods(); } diff --git a/modules/gdnative/nativescript/nativescript.h b/modules/gdnative/nativescript/nativescript.h index 777a878660..a7647e8c59 100644 --- a/modules/gdnative/nativescript/nativescript.h +++ b/modules/gdnative/nativescript/nativescript.h @@ -71,7 +71,7 @@ struct NativeScriptDesc { }; Map<StringName, Method> methods; - Vector<MultiplayerAPI::RPCConfig> rpc_methods; + Vector<Multiplayer::RPCConfig> rpc_methods; OrderedHashMap<StringName, Property> properties; Map<StringName, Signal> signals_; // QtCreator doesn't like the name signals StringName base; @@ -175,7 +175,7 @@ public: virtual void get_script_method_list(List<MethodInfo> *p_list) const override; virtual void get_script_property_list(List<PropertyInfo> *p_list) const override; - virtual const Vector<MultiplayerAPI::RPCConfig> get_rpc_methods() const override; + virtual const Vector<Multiplayer::RPCConfig> get_rpc_methods() const override; String get_class_documentation() const; String get_method_documentation(const StringName &p_method) const; @@ -213,7 +213,7 @@ public: String to_string(bool *r_valid); virtual Ref<Script> get_script() const; - virtual const Vector<MultiplayerAPI::RPCConfig> get_rpc_methods() const; + virtual const Vector<Multiplayer::RPCConfig> get_rpc_methods() const; virtual ScriptLanguage *get_language(); diff --git a/modules/gdnative/net/multiplayer_peer_gdnative.cpp b/modules/gdnative/net/multiplayer_peer_gdnative.cpp index 9908ed4533..575d5f5060 100644 --- a/modules/gdnative/net/multiplayer_peer_gdnative.cpp +++ b/modules/gdnative/net/multiplayer_peer_gdnative.cpp @@ -72,14 +72,14 @@ int MultiplayerPeerGDNative::get_transfer_channel() const { return interface->get_transfer_channel(interface->data); } -void MultiplayerPeerGDNative::set_transfer_mode(TransferMode p_mode) { +void MultiplayerPeerGDNative::set_transfer_mode(Multiplayer::TransferMode p_mode) { ERR_FAIL_COND(interface == nullptr); interface->set_transfer_mode(interface->data, (godot_int)p_mode); } -MultiplayerPeer::TransferMode MultiplayerPeerGDNative::get_transfer_mode() const { - ERR_FAIL_COND_V(interface == nullptr, TRANSFER_MODE_UNRELIABLE); - return (TransferMode)interface->get_transfer_mode(interface->data); +Multiplayer::TransferMode MultiplayerPeerGDNative::get_transfer_mode() const { + ERR_FAIL_COND_V(interface == nullptr, Multiplayer::TRANSFER_MODE_UNRELIABLE); + return (Multiplayer::TransferMode)interface->get_transfer_mode(interface->data); } void MultiplayerPeerGDNative::set_target_peer(int p_peer_id) { @@ -124,7 +124,7 @@ MultiplayerPeer::ConnectionStatus MultiplayerPeerGDNative::get_connection_status void MultiplayerPeerGDNative::_bind_methods() { ADD_PROPERTY_DEFAULT("transfer_channel", 0); - ADD_PROPERTY_DEFAULT("transfer_mode", TRANSFER_MODE_UNRELIABLE); + ADD_PROPERTY_DEFAULT("transfer_mode", Multiplayer::TRANSFER_MODE_UNRELIABLE); ADD_PROPERTY_DEFAULT("refuse_new_connections", true); } diff --git a/modules/gdnative/net/multiplayer_peer_gdnative.h b/modules/gdnative/net/multiplayer_peer_gdnative.h index ab084faae6..33e424d284 100644 --- a/modules/gdnative/net/multiplayer_peer_gdnative.h +++ b/modules/gdnative/net/multiplayer_peer_gdnative.h @@ -31,7 +31,7 @@ #ifndef MULTIPLAYER_PEER_GDNATIVE_H #define MULTIPLAYER_PEER_GDNATIVE_H -#include "core/io/multiplayer_peer.h" +#include "core/multiplayer/multiplayer_peer.h" #include "modules/gdnative/gdnative.h" #include "modules/gdnative/include/net/godot_net.h" @@ -58,8 +58,8 @@ public: /* Specific to MultiplayerPeer */ virtual void set_transfer_channel(int p_channel) override; virtual int get_transfer_channel() const override; - virtual void set_transfer_mode(TransferMode p_mode) override; - virtual TransferMode get_transfer_mode() const override; + virtual void set_transfer_mode(Multiplayer::TransferMode p_mode) override; + virtual Multiplayer::TransferMode get_transfer_mode() const override; virtual void set_target_peer(int p_peer_id) override; virtual int get_packet_peer() const override; diff --git a/modules/gdnative/pluginscript/pluginscript_instance.cpp b/modules/gdnative/pluginscript/pluginscript_instance.cpp index ed1c0af3ed..feae81397e 100644 --- a/modules/gdnative/pluginscript/pluginscript_instance.cpp +++ b/modules/gdnative/pluginscript/pluginscript_instance.cpp @@ -100,7 +100,7 @@ String PluginScriptInstance::to_string(bool *r_valid) { return str_ret; } -const Vector<MultiplayerAPI::RPCConfig> PluginScriptInstance::get_rpc_methods() const { +const Vector<Multiplayer::RPCConfig> PluginScriptInstance::get_rpc_methods() const { return _script->get_rpc_methods(); } diff --git a/modules/gdnative/pluginscript/pluginscript_instance.h b/modules/gdnative/pluginscript/pluginscript_instance.h index 25b62ae8ab..bdae265db2 100644 --- a/modules/gdnative/pluginscript/pluginscript_instance.h +++ b/modules/gdnative/pluginscript/pluginscript_instance.h @@ -71,7 +71,7 @@ public: void set_path(const String &p_path); - virtual const Vector<MultiplayerAPI::RPCConfig> get_rpc_methods() const; + virtual const Vector<Multiplayer::RPCConfig> get_rpc_methods() const; virtual void refcount_incremented(); virtual bool refcount_decremented(); diff --git a/modules/gdnative/pluginscript/pluginscript_script.cpp b/modules/gdnative/pluginscript/pluginscript_script.cpp index 5380858582..2b4ceda49d 100644 --- a/modules/gdnative/pluginscript/pluginscript_script.cpp +++ b/modules/gdnative/pluginscript/pluginscript_script.cpp @@ -334,9 +334,9 @@ Error PluginScript::reload(bool p_keep_state) { // rpc_mode is passed as an optional field and is not part of MethodInfo Variant var = v["rpc_mode"]; if (var != Variant()) { - MultiplayerAPI::RPCConfig nd; + Multiplayer::RPCConfig nd; nd.name = mi.name; - nd.rpc_mode = MultiplayerAPI::RPCMode(int(var)); + nd.rpc_mode = Multiplayer::RPCMode(int(var)); // TODO Transfer Channel if (_rpc_methods.find(nd) == -1) { _rpc_methods.push_back(nd); @@ -345,7 +345,7 @@ Error PluginScript::reload(bool p_keep_state) { } // Sort so we are 100% that they are always the same. - _rpc_methods.sort_custom<MultiplayerAPI::SortRPCConfig>(); + _rpc_methods.sort_custom<Multiplayer::SortRPCConfig>(); Array *signals = (Array *)&manifest.signals; for (int i = 0; i < signals->size(); ++i) { @@ -484,7 +484,7 @@ int PluginScript::get_member_line(const StringName &p_member) const { return -1; } -const Vector<MultiplayerAPI::RPCConfig> PluginScript::get_rpc_methods() const { +const Vector<Multiplayer::RPCConfig> PluginScript::get_rpc_methods() const { return _rpc_methods; } diff --git a/modules/gdnative/pluginscript/pluginscript_script.h b/modules/gdnative/pluginscript/pluginscript_script.h index 838195147f..1a12a130d1 100644 --- a/modules/gdnative/pluginscript/pluginscript_script.h +++ b/modules/gdnative/pluginscript/pluginscript_script.h @@ -61,7 +61,7 @@ private: Map<StringName, PropertyInfo> _properties_info; Map<StringName, MethodInfo> _signals_info; Map<StringName, MethodInfo> _methods_info; - Vector<MultiplayerAPI::RPCConfig> _rpc_methods; + Vector<Multiplayer::RPCConfig> _rpc_methods; Set<Object *> _instances; //exported members @@ -136,7 +136,7 @@ public: virtual int get_member_line(const StringName &p_member) const override; - virtual const Vector<MultiplayerAPI::RPCConfig> get_rpc_methods() const override; + virtual const Vector<Multiplayer::RPCConfig> get_rpc_methods() const override; PluginScript(); void init(PluginScriptLanguage *language); diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp index 7943cf7469..bc8801b8b9 100644 --- a/modules/gdscript/gdscript.cpp +++ b/modules/gdscript/gdscript.cpp @@ -87,6 +87,19 @@ Object *GDScriptNativeClass::instantiate() { return ClassDB::instantiate(name); } +GDScriptFunction *GDScript::_super_constructor(GDScript *p_script) { + if (p_script->initializer) { + return p_script->initializer; + } else { + GDScript *base = p_script->_base; + if (base != nullptr) { + return _super_constructor(base); + } else { + return nullptr; + } + } +} + void GDScript::_super_implicit_constructor(GDScript *p_script, GDScriptInstance *p_instance, Callable::CallError &r_error) { GDScript *base = p_script->_base; if (base != nullptr) { @@ -135,6 +148,8 @@ GDScriptInstance *GDScript::_create_instance(const Variant **p_args, int p_argco if (p_argcount < 0) { return instance; } + + initializer = _super_constructor(this); if (initializer != nullptr) { initializer->call(instance, p_args, p_argcount, r_error); if (r_error.error != Callable::CallError::CALL_OK) { @@ -900,7 +915,7 @@ void GDScript::get_members(Set<StringName> *p_members) { } } -const Vector<MultiplayerAPI::RPCConfig> GDScript::get_rpc_methods() const { +const Vector<Multiplayer::RPCConfig> GDScript::get_rpc_methods() const { return rpc_functions; } @@ -1164,8 +1179,8 @@ void GDScript::_init_rpc_methods_properties() { while (cscript) { // RPC Methods for (Map<StringName, GDScriptFunction *>::Element *E = cscript->member_functions.front(); E; E = E->next()) { - MultiplayerAPI::RPCConfig config = E->get()->get_rpc_config(); - if (config.rpc_mode != MultiplayerAPI::RPC_MODE_DISABLED) { + Multiplayer::RPCConfig config = E->get()->get_rpc_config(); + if (config.rpc_mode != Multiplayer::RPC_MODE_DISABLED) { config.name = E->get()->get_name(); if (rpc_functions.find(config) == -1) { rpc_functions.push_back(config); @@ -1185,7 +1200,7 @@ void GDScript::_init_rpc_methods_properties() { } // Sort so we are 100% that they are always the same. - rpc_functions.sort_custom<MultiplayerAPI::SortRPCConfig>(); + rpc_functions.sort_custom<Multiplayer::SortRPCConfig>(); } GDScript::~GDScript() { @@ -1541,7 +1556,7 @@ ScriptLanguage *GDScriptInstance::get_language() { return GDScriptLanguage::get_singleton(); } -const Vector<MultiplayerAPI::RPCConfig> GDScriptInstance::get_rpc_methods() const { +const Vector<Multiplayer::RPCConfig> GDScriptInstance::get_rpc_methods() const { return script->get_rpc_methods(); } diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h index 24809ad5fd..791f8a1431 100644 --- a/modules/gdscript/gdscript.h +++ b/modules/gdscript/gdscript.h @@ -85,7 +85,7 @@ class GDScript : public Script { Map<StringName, MemberInfo> member_indices; //members are just indices to the instantiated script. Map<StringName, Ref<GDScript>> subclasses; Map<StringName, Vector<StringName>> _signals; - Vector<MultiplayerAPI::RPCConfig> rpc_functions; + Vector<Multiplayer::RPCConfig> rpc_functions; #ifdef TOOLS_ENABLED @@ -130,6 +130,7 @@ class GDScript : public Script { SelfList<GDScriptFunctionState>::List pending_func_states; + GDScriptFunction *_super_constructor(GDScript *p_script); void _super_implicit_constructor(GDScript *p_script, GDScriptInstance *p_instance, Callable::CallError &r_error); GDScriptInstance *_create_instance(const Variant **p_args, int p_argcount, Object *p_owner, bool p_is_ref_counted, Callable::CallError &r_error); @@ -245,7 +246,7 @@ public: virtual void get_constants(Map<StringName, Variant> *p_constants) override; virtual void get_members(Set<StringName> *p_members) override; - virtual const Vector<MultiplayerAPI::RPCConfig> get_rpc_methods() const override; + virtual const Vector<Multiplayer::RPCConfig> get_rpc_methods() const override; #ifdef TOOLS_ENABLED virtual bool is_placeholder_fallback_enabled() const override { return placeholder_fallback_enabled; } @@ -298,7 +299,7 @@ public: void reload_members(); - virtual const Vector<MultiplayerAPI::RPCConfig> get_rpc_methods() const; + virtual const Vector<Multiplayer::RPCConfig> get_rpc_methods() const; GDScriptInstance(); ~GDScriptInstance(); diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index a0cf41d5a3..06db46173c 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -171,7 +171,7 @@ Error GDScriptAnalyzer::check_native_member_name_conflict(const StringName &p_me } if (class_exists(p_member_name)) { - push_error(vformat(R"(The class "%s" shadows a native class.)", p_member_name), p_member_node); + push_error(vformat(R"(The member "%s" shadows a native class.)", p_member_name), p_member_node); return ERR_PARSE_ERROR; } @@ -218,6 +218,17 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class, p_class->fqcn = p_class->outer->fqcn + "::" + String(p_class->identifier->name); } + if (p_class->identifier) { + StringName class_name = p_class->identifier->name; + if (class_exists(class_name)) { + push_error(vformat(R"(Class "%s" hides a native class.)", class_name), p_class->identifier); + } else if (ScriptServer::is_global_class(class_name) && (ScriptServer::get_global_class_path(class_name) != parser->script_path || p_class != parser->head)) { + push_error(vformat(R"(Class "%s" hides a global script class.)", class_name), p_class->identifier); + } else if (ProjectSettings::get_singleton()->has_autoload(class_name) && ProjectSettings::get_singleton()->get_autoload(class_name).is_singleton) { + push_error(vformat(R"(Class "%s" hides an autoload singleton.)", class_name), p_class->identifier); + } + } + GDScriptParser::DataType result; // Set datatype for class. @@ -681,8 +692,9 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas specified_type.is_meta_type = false; } - GDScriptParser::DataType datatype = member.constant->get_datatype(); + GDScriptParser::DataType datatype; if (member.constant->initializer) { + datatype = member.constant->initializer->get_datatype(); if (member.constant->initializer->type == GDScriptParser::Node::ARRAY) { GDScriptParser::ArrayNode *array = static_cast<GDScriptParser::ArrayNode *>(member.constant->initializer); const_fold_array(array); @@ -2494,6 +2506,7 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod case GDScriptParser::ClassNode::Member::VARIABLE: p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_VARIABLE; p_identifier->variable_source = member.variable; + member.variable->usages += 1; break; case GDScriptParser::ClassNode::Member::FUNCTION: resolve_function_signature(member.function); @@ -2515,14 +2528,29 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod while (outer != nullptr) { if (outer->has_member(name)) { const GDScriptParser::ClassNode::Member &member = outer->get_member(name); - if (member.type == GDScriptParser::ClassNode::Member::CONSTANT) { - // TODO: Make sure loops won't cause problem. And make special error message for those. - // For out-of-order resolution: - reduce_expression(member.constant->initializer); - p_identifier->set_datatype(member.get_datatype()); - p_identifier->is_constant = true; - p_identifier->reduced_value = member.constant->initializer->reduced_value; - return; + switch (member.type) { + case GDScriptParser::ClassNode::Member::CONSTANT: { + // TODO: Make sure loops won't cause problem. And make special error message for those. + // For out-of-order resolution: + reduce_expression(member.constant->initializer); + p_identifier->set_datatype(member.get_datatype()); + p_identifier->is_constant = true; + p_identifier->reduced_value = member.constant->initializer->reduced_value; + return; + } break; + case GDScriptParser::ClassNode::Member::ENUM_VALUE: { + p_identifier->set_datatype(member.get_datatype()); + p_identifier->is_constant = true; + p_identifier->reduced_value = member.enum_value.value; + return; + } break; + case GDScriptParser::ClassNode::Member::ENUM: { + p_identifier->set_datatype(member.get_datatype()); + p_identifier->is_constant = false; + return; + } break; + default: + break; } } outer = outer->outer; @@ -2836,6 +2864,9 @@ void GDScriptAnalyzer::reduce_self(GDScriptParser::SelfNode *p_self) { } void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscript) { + if (p_subscript->base == nullptr) { + return; + } if (p_subscript->base->type == GDScriptParser::Node::IDENTIFIER) { reduce_identifier(static_cast<GDScriptParser::IdentifierNode *>(p_subscript->base), true); } else { diff --git a/modules/gdscript/gdscript_byte_codegen.cpp b/modules/gdscript/gdscript_byte_codegen.cpp index 5958326315..1127488db8 100644 --- a/modules/gdscript/gdscript_byte_codegen.cpp +++ b/modules/gdscript/gdscript_byte_codegen.cpp @@ -155,7 +155,7 @@ void GDScriptByteCodeGenerator::end_parameters() { function->default_arguments.reverse(); } -void GDScriptByteCodeGenerator::write_start(GDScript *p_script, const StringName &p_function_name, bool p_static, MultiplayerAPI::RPCConfig p_rpc_config, const GDScriptDataType &p_return_type) { +void GDScriptByteCodeGenerator::write_start(GDScript *p_script, const StringName &p_function_name, bool p_static, Multiplayer::RPCConfig p_rpc_config, const GDScriptDataType &p_return_type) { function = memnew(GDScriptFunction); debug_stack = EngineDebugger::is_active(); @@ -864,6 +864,12 @@ void GDScriptByteCodeGenerator::write_assign_default_parameter(const Address &p_ function->default_arguments.push_back(opcodes.size()); } +void GDScriptByteCodeGenerator::write_store_global(const Address &p_dst, int p_global_index) { + append(GDScriptFunction::OPCODE_STORE_GLOBAL, 1); + append(p_dst); + append(p_global_index); +} + void GDScriptByteCodeGenerator::write_store_named_global(const Address &p_dst, const StringName &p_global) { append(GDScriptFunction::OPCODE_STORE_NAMED_GLOBAL, 1); append(p_dst); diff --git a/modules/gdscript/gdscript_byte_codegen.h b/modules/gdscript/gdscript_byte_codegen.h index 3d6fb291ad..dcc11ebdce 100644 --- a/modules/gdscript/gdscript_byte_codegen.h +++ b/modules/gdscript/gdscript_byte_codegen.h @@ -419,7 +419,7 @@ public: virtual void start_block() override; virtual void end_block() override; - virtual void write_start(GDScript *p_script, const StringName &p_function_name, bool p_static, MultiplayerAPI::RPCConfig p_rpc_config, const GDScriptDataType &p_return_type) override; + virtual void write_start(GDScript *p_script, const StringName &p_function_name, bool p_static, Multiplayer::RPCConfig p_rpc_config, const GDScriptDataType &p_return_type) override; virtual GDScriptFunction *write_end() override; #ifdef DEBUG_ENABLED @@ -454,6 +454,7 @@ public: virtual void write_assign_true(const Address &p_target) override; virtual void write_assign_false(const Address &p_target) override; virtual void write_assign_default_parameter(const Address &p_dst, const Address &p_src) override; + virtual void write_store_global(const Address &p_dst, int p_global_index) override; virtual void write_store_named_global(const Address &p_dst, const StringName &p_global) override; virtual void write_cast(const Address &p_target, const Address &p_source, const GDScriptDataType &p_type) override; virtual void write_call(const Address &p_target, const Address &p_base, const StringName &p_function_name, const Vector<Address> &p_arguments) override; diff --git a/modules/gdscript/gdscript_codegen.h b/modules/gdscript/gdscript_codegen.h index ecc86c37f3..e6ecc92d55 100644 --- a/modules/gdscript/gdscript_codegen.h +++ b/modules/gdscript/gdscript_codegen.h @@ -31,7 +31,7 @@ #ifndef GDSCRIPT_CODEGEN #define GDSCRIPT_CODEGEN -#include "core/io/multiplayer_api.h" +#include "core/multiplayer/multiplayer.h" #include "core/string/string_name.h" #include "core/variant/variant.h" #include "gdscript_function.h" @@ -80,7 +80,7 @@ public: virtual void start_block() = 0; virtual void end_block() = 0; - virtual void write_start(GDScript *p_script, const StringName &p_function_name, bool p_static, MultiplayerAPI::RPCConfig p_rpc_config, const GDScriptDataType &p_return_type) = 0; + virtual void write_start(GDScript *p_script, const StringName &p_function_name, bool p_static, Multiplayer::RPCConfig p_rpc_config, const GDScriptDataType &p_return_type) = 0; virtual GDScriptFunction *write_end() = 0; #ifdef DEBUG_ENABLED @@ -115,6 +115,7 @@ public: virtual void write_assign_true(const Address &p_target) = 0; virtual void write_assign_false(const Address &p_target) = 0; virtual void write_assign_default_parameter(const Address &dst, const Address &src) = 0; + virtual void write_store_global(const Address &p_dst, int p_global_index) = 0; virtual void write_store_named_global(const Address &p_dst, const StringName &p_global) = 0; virtual void write_cast(const Address &p_target, const Address &p_source, const GDScriptDataType &p_type) = 0; virtual void write_call(const Address &p_target, const Address &p_base, const StringName &p_function_name, const Vector<Address> &p_arguments) = 0; diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp index e5cbcc545f..b0d0b02443 100644 --- a/modules/gdscript/gdscript_compiler.cpp +++ b/modules/gdscript/gdscript_compiler.cpp @@ -35,6 +35,8 @@ #include "gdscript_cache.h" #include "gdscript_utility_functions.h" +#include "core/config/project_settings.h" + bool GDScriptCompiler::_is_class_member_property(CodeGen &codegen, const StringName &p_name) { if (codegen.function_node && codegen.function_node->is_static) { return false; @@ -316,10 +318,21 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code } } + // Try globals. if (GDScriptLanguage::get_singleton()->get_global_map().has(identifier)) { - int idx = GDScriptLanguage::get_singleton()->get_global_map()[identifier]; - Variant global = GDScriptLanguage::get_singleton()->get_global_array()[idx]; - return codegen.add_constant(global); // TODO: Get type. + // If it's an autoload singleton, we postpone to load it at runtime. + // This is so one autoload doesn't try to load another before it's compiled. + OrderedHashMap<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list(); + if (autoloads.has(identifier) && autoloads[identifier].is_singleton) { + GDScriptCodeGenerator::Address global = codegen.add_temporary(_gdtype_from_datatype(in->get_datatype())); + int idx = GDScriptLanguage::get_singleton()->get_global_map()[identifier]; + gen->write_store_global(global, idx); + return global; + } else { + int idx = GDScriptLanguage::get_singleton()->get_global_map()[identifier]; + Variant global = GDScriptLanguage::get_singleton()->get_global_array()[idx]; + return codegen.add_constant(global); + } } // Try global classes. @@ -1857,7 +1870,7 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_ StringName func_name; bool is_static = false; - MultiplayerAPI::RPCConfig rpc_config; + Multiplayer::RPCConfig rpc_config; GDScriptDataType return_type; return_type.has_type = true; return_type.kind = GDScriptDataType::BUILTIN; @@ -2086,7 +2099,7 @@ Error GDScriptCompiler::_parse_setter_getter(GDScript *p_script, const GDScriptP return_type = _gdtype_from_datatype(p_variable->get_datatype(), p_script); } - codegen.generator->write_start(p_script, func_name, false, MultiplayerAPI::RPCConfig(), return_type); + codegen.generator->write_start(p_script, func_name, false, Multiplayer::RPCConfig(), return_type); if (p_is_setter) { uint32_t par_addr = codegen.generator->add_parameter(p_variable->setter_parameter->name, false, _gdtype_from_datatype(p_variable->get_datatype())); diff --git a/modules/gdscript/gdscript_disassembler.cpp b/modules/gdscript/gdscript_disassembler.cpp index 1acb9ceddc..9287df2ea0 100644 --- a/modules/gdscript/gdscript_disassembler.cpp +++ b/modules/gdscript/gdscript_disassembler.cpp @@ -914,6 +914,14 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const { incr += 5; } break; DISASSEMBLE_ITERATE_TYPES(DISASSEMBLE_ITERATE); + case OPCODE_STORE_GLOBAL: { + text += "store global "; + text += DADDR(1); + text += " = "; + text += String::num_int64(_code_ptr[ip + 2]); + + incr += 3; + } break; case OPCODE_STORE_NAMED_GLOBAL: { text += "store named global "; text += DADDR(1); diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index 70e18c6e6c..f79e5726ce 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -1733,7 +1733,7 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, } } - if (is_function_parameter && p_context.current_function && p_context.current_class) { + if (is_function_parameter && p_context.current_function && p_context.current_function->source_lambda == nullptr && p_context.current_class) { // Check if it's override of native function, then we can assume the type from the signature. GDScriptParser::DataType base_type = p_context.current_class->base_type; while (base_type.is_set()) { @@ -3078,6 +3078,15 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co r_result.class_member = p_symbol; return OK; } + } else { + List<StringName> utility_functions; + Variant::get_utility_function_list(&utility_functions); + if (utility_functions.find(p_symbol) != nullptr) { + r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_TBD_GLOBALSCOPE; + r_result.class_name = "@GlobalScope"; + r_result.class_member = p_symbol; + return OK; + } } } } break; diff --git a/modules/gdscript/gdscript_function.h b/modules/gdscript/gdscript_function.h index 87d8c03494..9d076a8e4c 100644 --- a/modules/gdscript/gdscript_function.h +++ b/modules/gdscript/gdscript_function.h @@ -348,6 +348,7 @@ public: OPCODE_ITERATE_PACKED_VECTOR3_ARRAY, OPCODE_ITERATE_PACKED_COLOR_ARRAY, OPCODE_ITERATE_OBJECT, + OPCODE_STORE_GLOBAL, OPCODE_STORE_NAMED_GLOBAL, OPCODE_TYPE_ADJUST_BOOL, OPCODE_TYPE_ADJUST_INT, @@ -468,7 +469,7 @@ private: int _initial_line = 0; bool _static = false; - MultiplayerAPI::RPCConfig rpc_config; + Multiplayer::RPCConfig rpc_config; GDScript *_script = nullptr; @@ -588,7 +589,7 @@ public: void disassemble(const Vector<String> &p_code_lines) const; #endif - _FORCE_INLINE_ MultiplayerAPI::RPCConfig get_rpc_config() const { return rpc_config; } + _FORCE_INLINE_ Multiplayer::RPCConfig get_rpc_config() const { return rpc_config; } GDScriptFunction(); ~GDScriptFunction(); }; diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index f4c721e00a..19584ce194 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -133,7 +133,7 @@ GDScriptParser::GDScriptParser() { register_annotation(MethodInfo("@export_flags_3d_physics"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_3D_PHYSICS, Variant::INT>); register_annotation(MethodInfo("@export_flags_3d_navigation"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_3D_NAVIGATION, Variant::INT>); // Networking. - register_annotation(MethodInfo("@rpc", { Variant::STRING, "mode" }, { Variant::STRING, "sync" }, { Variant::STRING, "transfer_mode" }, { Variant::INT, "transfer_channel" }), AnnotationInfo::FUNCTION, &GDScriptParser::network_annotations<MultiplayerAPI::RPC_MODE_AUTHORITY>, 4, true); + register_annotation(MethodInfo("@rpc", { Variant::STRING, "mode" }, { Variant::STRING, "sync" }, { Variant::STRING, "transfer_mode" }, { Variant::INT, "transfer_channel" }), AnnotationInfo::FUNCTION, &GDScriptParser::network_annotations<Multiplayer::RPC_MODE_AUTHORITY>, 4, true); // TODO: Warning annotations. } @@ -337,12 +337,29 @@ Error GDScriptParser::parse(const String &p_source_code, const String &p_script_ tokenizer.set_cursor_position(cursor_line, cursor_column); script_path = p_script_path; current = tokenizer.scan(); - // Avoid error as the first token. - while (current.type == GDScriptTokenizer::Token::ERROR) { - push_error(current.literal); + // Avoid error or newline as the first token. + // The latter can mess with the parser when opening files filled exclusively with comments and newlines. + while (current.type == GDScriptTokenizer::Token::ERROR || current.type == GDScriptTokenizer::Token::NEWLINE) { + if (current.type == GDScriptTokenizer::Token::ERROR) { + push_error(current.literal); + } current = tokenizer.scan(); } +#ifdef DEBUG_ENABLED + // Warn about parsing an empty script file: + if (current.type == GDScriptTokenizer::Token::TK_EOF) { + // Create a dummy Node for the warning, pointing to the very beginning of the file + Node *nd = alloc_node<PassNode>(); + nd->start_line = 1; + nd->start_column = 0; + nd->end_line = 1; + nd->leftmost_column = 0; + nd->rightmost_column = 0; + push_warning(nd, GDScriptWarning::EMPTY_FILE); + } +#endif + push_multiline(false); // Keep one for the whole parsing. parse_program(); pop_multiline(); @@ -1682,6 +1699,7 @@ GDScriptParser::MatchNode *GDScriptParser::parse_match() { while (!check(GDScriptTokenizer::Token::DEDENT) && !is_at_end()) { MatchBranchNode *branch = parse_match_branch(); if (branch == nullptr) { + advance(); continue; } @@ -1745,7 +1763,9 @@ GDScriptParser::MatchBranchNode *GDScriptParser::parse_match_branch() { push_error(R"(No pattern found for "match" branch.)"); } - consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" after "match" patterns.)"); + if (!consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" after "match" patterns.)")) { + return nullptr; + } // Save continue state. bool could_continue = can_continue; @@ -1778,15 +1798,6 @@ GDScriptParser::PatternNode *GDScriptParser::parse_match_pattern(PatternNode *p_ PatternNode *pattern = alloc_node<PatternNode>(); switch (current.type) { - case GDScriptTokenizer::Token::LITERAL: - advance(); - pattern->pattern_type = PatternNode::PT_LITERAL; - pattern->literal = parse_literal(); - if (pattern->literal == nullptr) { - // Error happened. - return nullptr; - } - break; case GDScriptTokenizer::Token::VAR: { // Bind. advance(); @@ -1849,44 +1860,44 @@ GDScriptParser::PatternNode *GDScriptParser::parse_match_pattern(PatternNode *p_ // Dictionary. advance(); pattern->pattern_type = PatternNode::PT_DICTIONARY; - - if (!check(GDScriptTokenizer::Token::BRACE_CLOSE) && !is_at_end()) { - do { - if (match(GDScriptTokenizer::Token::PERIOD_PERIOD)) { - // Rest. + do { + if (check(GDScriptTokenizer::Token::BRACE_CLOSE) || is_at_end()) { + break; + } + if (match(GDScriptTokenizer::Token::PERIOD_PERIOD)) { + // Rest. + if (pattern->rest_used) { + push_error(R"(The ".." pattern must be the last element in the pattern dictionary.)"); + } else { + PatternNode *sub_pattern = alloc_node<PatternNode>(); + sub_pattern->pattern_type = PatternNode::PT_REST; + pattern->dictionary.push_back({ nullptr, sub_pattern }); + pattern->rest_used = true; + } + } else { + ExpressionNode *key = parse_expression(false); + if (key == nullptr) { + push_error(R"(Expected expression as key for dictionary pattern.)"); + } + if (match(GDScriptTokenizer::Token::COLON)) { + // Value pattern. + PatternNode *sub_pattern = parse_match_pattern(p_root_pattern != nullptr ? p_root_pattern : pattern); + if (sub_pattern == nullptr) { + continue; + } if (pattern->rest_used) { push_error(R"(The ".." pattern must be the last element in the pattern dictionary.)"); + } else if (sub_pattern->pattern_type == PatternNode::PT_REST) { + push_error(R"(The ".." pattern cannot be used as a value.)"); } else { - PatternNode *sub_pattern = alloc_node<PatternNode>(); - sub_pattern->pattern_type = PatternNode::PT_REST; - pattern->dictionary.push_back({ nullptr, sub_pattern }); - pattern->rest_used = true; + pattern->dictionary.push_back({ key, sub_pattern }); } } else { - ExpressionNode *key = parse_expression(false); - if (key == nullptr) { - push_error(R"(Expected expression as key for dictionary pattern.)"); - } - if (match(GDScriptTokenizer::Token::COLON)) { - // Value pattern. - PatternNode *sub_pattern = parse_match_pattern(p_root_pattern != nullptr ? p_root_pattern : pattern); - if (sub_pattern == nullptr) { - continue; - } - if (pattern->rest_used) { - push_error(R"(The ".." pattern must be the last element in the pattern dictionary.)"); - } else if (sub_pattern->pattern_type == PatternNode::PT_REST) { - push_error(R"(The ".." pattern cannot be used as a value.)"); - } else { - pattern->dictionary.push_back({ key, sub_pattern }); - } - } else { - // Key match only. - pattern->dictionary.push_back({ key, nullptr }); - } + // Key match only. + pattern->dictionary.push_back({ key, nullptr }); } - } while (match(GDScriptTokenizer::Token::COMMA)); - } + } + } while (match(GDScriptTokenizer::Token::COMMA)); consume(GDScriptTokenizer::Token::BRACE_CLOSE, R"(Expected "}" to close the dictionary pattern.)"); break; } @@ -1895,8 +1906,13 @@ GDScriptParser::PatternNode *GDScriptParser::parse_match_pattern(PatternNode *p_ ExpressionNode *expression = parse_expression(false); if (expression == nullptr) { push_error(R"(Expected expression for match pattern.)"); + return nullptr; } else { - pattern->pattern_type = PatternNode::PT_EXPRESSION; + if (expression->type == GDScriptParser::Node::LITERAL) { + pattern->pattern_type = PatternNode::PT_LITERAL; + } else { + pattern->pattern_type = PatternNode::PT_EXPRESSION; + } pattern->expression = expression; } break; @@ -2370,8 +2386,12 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_assignment(ExpressionNode } #ifdef DEBUG_ENABLED - if (has_operator && source_variable != nullptr && source_variable->assignments == 0) { - push_warning(assignment, GDScriptWarning::UNASSIGNED_VARIABLE_OP_ASSIGN, source_variable->identifier->name); + if (source_variable != nullptr) { + if (has_operator && source_variable->assignments == 0) { + push_warning(assignment, GDScriptWarning::UNASSIGNED_VARIABLE_OP_ASSIGN, source_variable->identifier->name); + } + + source_variable->assignments += 1; } #endif @@ -2386,7 +2406,9 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_await(ExpressionNode *p_pr } await->to_await = element; - current_function->is_coroutine = true; + if (current_function) { // Might be null in a getter or setter. + current_function->is_coroutine = true; + } return await; } @@ -2463,8 +2485,10 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_dictionary(ExpressionNode push_error(R"(Expected "=" after dictionary key.)"); } } - key->is_constant = true; - key->reduced_value = static_cast<IdentifierNode *>(key)->name; + if (key != nullptr) { + key->is_constant = true; + key->reduced_value = static_cast<IdentifierNode *>(key)->name; + } break; case DictionaryNode::PYTHON_DICT: if (!match(GDScriptTokenizer::Token::COLON)) { @@ -3393,11 +3417,11 @@ bool GDScriptParser::warning_annotations(const AnnotationNode *p_annotation, Nod ERR_FAIL_V_MSG(false, "Not implemented."); } -template <MultiplayerAPI::RPCMode t_mode> +template <Multiplayer::RPCMode t_mode> bool GDScriptParser::network_annotations(const AnnotationNode *p_annotation, Node *p_node) { ERR_FAIL_COND_V_MSG(p_node->type != Node::VARIABLE && p_node->type != Node::FUNCTION, false, vformat(R"("%s" annotation can only be applied to variables and functions.)", p_annotation->name)); - MultiplayerAPI::RPCConfig rpc_config; + Multiplayer::RPCConfig rpc_config; rpc_config.rpc_mode = t_mode; if (p_annotation->resolved_arguments.size()) { int last = p_annotation->resolved_arguments.size() - 1; @@ -3412,19 +3436,19 @@ bool GDScriptParser::network_annotations(const AnnotationNode *p_annotation, Nod for (int i = last; i >= 0; i--) { String mode = p_annotation->resolved_arguments[i].operator String(); if (mode == "any") { - rpc_config.rpc_mode = MultiplayerAPI::RPC_MODE_ANY; + rpc_config.rpc_mode = Multiplayer::RPC_MODE_ANY; } else if (mode == "auth") { - rpc_config.rpc_mode = MultiplayerAPI::RPC_MODE_AUTHORITY; + rpc_config.rpc_mode = Multiplayer::RPC_MODE_AUTHORITY; } else if (mode == "sync") { rpc_config.sync = true; } else if (mode == "nosync") { rpc_config.sync = false; } else if (mode == "reliable") { - rpc_config.transfer_mode = MultiplayerPeer::TRANSFER_MODE_RELIABLE; + rpc_config.transfer_mode = Multiplayer::TRANSFER_MODE_RELIABLE; } else if (mode == "unreliable") { - rpc_config.transfer_mode = MultiplayerPeer::TRANSFER_MODE_UNRELIABLE; + rpc_config.transfer_mode = Multiplayer::TRANSFER_MODE_UNRELIABLE; } else if (mode == "ordered") { - rpc_config.transfer_mode = MultiplayerPeer::TRANSFER_MODE_UNRELIABLE_ORDERED; + rpc_config.transfer_mode = Multiplayer::TRANSFER_MODE_ORDERED; } else { push_error(R"(Invalid RPC argument. Must be one of: 'sync'/'nosync' (local calls), 'any'/'auth' (permission), 'reliable'/'unreliable'/'ordered' (transfer mode).)", p_annotation); } @@ -3433,7 +3457,7 @@ bool GDScriptParser::network_annotations(const AnnotationNode *p_annotation, Nod switch (p_node->type) { case Node::FUNCTION: { FunctionNode *function = static_cast<FunctionNode *>(p_node); - if (function->rpc_config.rpc_mode != MultiplayerAPI::RPC_MODE_DISABLED) { + if (function->rpc_config.rpc_mode != Multiplayer::RPC_MODE_DISABLED) { push_error(R"(RPC annotations can only be used once per function.)", p_annotation); return false; } diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h index 0bce8d7ddd..4902f0d4a6 100644 --- a/modules/gdscript/gdscript_parser.h +++ b/modules/gdscript/gdscript_parser.h @@ -31,8 +31,8 @@ #ifndef GDSCRIPT_PARSER_H #define GDSCRIPT_PARSER_H -#include "core/io/multiplayer_api.h" #include "core/io/resource.h" +#include "core/multiplayer/multiplayer.h" #include "core/object/ref_counted.h" #include "core/object/script_language.h" #include "core/string/string_name.h" @@ -729,7 +729,7 @@ public: SuiteNode *body = nullptr; bool is_static = false; bool is_coroutine = false; - MultiplayerAPI::RPCConfig rpc_config; + Multiplayer::RPCConfig rpc_config; MethodInfo info; LambdaNode *source_lambda = nullptr; #ifdef TOOLS_ENABLED @@ -1340,7 +1340,7 @@ private: template <PropertyHint t_hint, Variant::Type t_type> bool export_annotations(const AnnotationNode *p_annotation, Node *p_target); bool warning_annotations(const AnnotationNode *p_annotation, Node *p_target); - template <MultiplayerAPI::RPCMode t_mode> + template <Multiplayer::RPCMode t_mode> bool network_annotations(const AnnotationNode *p_annotation, Node *p_target); // Statements. Node *parse_statement(); diff --git a/modules/gdscript/gdscript_vm.cpp b/modules/gdscript/gdscript_vm.cpp index 882256b7e3..64fd7eca8a 100644 --- a/modules/gdscript/gdscript_vm.cpp +++ b/modules/gdscript/gdscript_vm.cpp @@ -322,6 +322,7 @@ void (*type_init_function_table[])(Variant *) = { &&OPCODE_ITERATE_PACKED_VECTOR3_ARRAY, \ &&OPCODE_ITERATE_PACKED_COLOR_ARRAY, \ &&OPCODE_ITERATE_OBJECT, \ + &&OPCODE_STORE_GLOBAL, \ &&OPCODE_STORE_NAMED_GLOBAL, \ &&OPCODE_TYPE_ADJUST_BOOL, \ &&OPCODE_TYPE_ADJUST_INT, \ @@ -3116,6 +3117,18 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a } DISPATCH_OPCODE; + OPCODE(OPCODE_STORE_GLOBAL) { + CHECK_SPACE(3); + int global_idx = _code_ptr[ip + 2]; + GD_ERR_BREAK(global_idx < 0 || global_idx >= GDScriptLanguage::get_singleton()->get_global_array_size()); + + GET_INSTRUCTION_ARG(dst, 0); + *dst = GDScriptLanguage::get_singleton()->get_global_array()[global_idx]; + + ip += 3; + } + DISPATCH_OPCODE; + OPCODE(OPCODE_STORE_NAMED_GLOBAL) { CHECK_SPACE(3); int globalname_idx = _code_ptr[ip + 2]; diff --git a/modules/gdscript/gdscript_warning.cpp b/modules/gdscript/gdscript_warning.cpp index ad41b60a4e..7a483a16ba 100644 --- a/modules/gdscript/gdscript_warning.cpp +++ b/modules/gdscript/gdscript_warning.cpp @@ -145,6 +145,9 @@ String GDScriptWarning::get_message() const { case REDUNDANT_AWAIT: { return R"("await" keyword not needed in this case, because the expression isn't a coroutine nor a signal.)"; } + case EMPTY_FILE: { + return "Empty script file."; + } case WARNING_MAX: break; // Can't happen, but silences warning } @@ -190,6 +193,7 @@ String GDScriptWarning::get_name_from_code(Code p_code) { "ASSERT_ALWAYS_TRUE", "ASSERT_ALWAYS_FALSE", "REDUNDANT_AWAIT", + "EMPTY_FILE", }; static_assert((sizeof(names) / sizeof(*names)) == WARNING_MAX, "Amount of warning types don't match the amount of warning names."); diff --git a/modules/gdscript/gdscript_warning.h b/modules/gdscript/gdscript_warning.h index 4b295b5eb8..8de46b08c1 100644 --- a/modules/gdscript/gdscript_warning.h +++ b/modules/gdscript/gdscript_warning.h @@ -68,6 +68,7 @@ public: ASSERT_ALWAYS_TRUE, // Expression for assert argument is always true. ASSERT_ALWAYS_FALSE, // Expression for assert argument is always false. REDUNDANT_AWAIT, // await is used but expression is synchronous (not a signal nor a coroutine). + EMPTY_FILE, // A script file is empty. WARNING_MAX, }; diff --git a/modules/gdscript/tests/gdscript_test_runner.cpp b/modules/gdscript/tests/gdscript_test_runner.cpp index 6225e5d1eb..f65402b578 100644 --- a/modules/gdscript/tests/gdscript_test_runner.cpp +++ b/modules/gdscript/tests/gdscript_test_runner.cpp @@ -496,7 +496,10 @@ GDScriptTest::TestResult GDScriptTest::execute_test_code(bool p_is_generating) { } return result; } - + // Script files matching this pattern are allowed to not contain a test() function. + if (source_file.match("*.notest.gd")) { + return result; + } // Test running. const Map<StringName, GDScriptFunction *>::Element *test_function_element = script->get_member_functions().find(GDScriptTestRunner::test_function_name); if (test_function_element == nullptr) { diff --git a/modules/gdscript/tests/scripts/analyzer/features/constants_from_parent.gd b/modules/gdscript/tests/scripts/analyzer/features/constants_from_parent.gd new file mode 100644 index 0000000000..135b6c3d85 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/constants_from_parent.gd @@ -0,0 +1,16 @@ +extends Node + +const NO_TYPE_CONST = 0 +const TYPE_CONST: int = 1 +const GUESS_TYPE_CONST := 2 + +class Test: + var a = NO_TYPE_CONST + var b = TYPE_CONST + var c = GUESS_TYPE_CONST + +func test(): + var test_instance = Test.new() + prints("a", test_instance.a, test_instance.a == NO_TYPE_CONST) + prints("b", test_instance.b, test_instance.b == TYPE_CONST) + prints("c", test_instance.c, test_instance.c == GUESS_TYPE_CONST) diff --git a/modules/gdscript/tests/scripts/analyzer/features/constants_from_parent.out b/modules/gdscript/tests/scripts/analyzer/features/constants_from_parent.out new file mode 100644 index 0000000000..a96bb84246 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/constants_from_parent.out @@ -0,0 +1,4 @@ +GDTEST_OK +a 0 true +b 1 true +c 2 true diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_from_parent.gd b/modules/gdscript/tests/scripts/analyzer/features/enum_from_parent.gd new file mode 100644 index 0000000000..5f57c5b8c2 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/enum_from_parent.gd @@ -0,0 +1,14 @@ +extends Node + +enum Named { VALUE_A, VALUE_B, VALUE_C = 42 } + +class Test: + var a = Named.VALUE_A + var b = Named.VALUE_B + var c = Named.VALUE_C + +func test(): + var test_instance = Test.new() + prints("a", test_instance.a, test_instance.a == Named.VALUE_A) + prints("b", test_instance.b, test_instance.b == Named.VALUE_B) + prints("c", test_instance.c, test_instance.c == Named.VALUE_C) diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_from_parent.out b/modules/gdscript/tests/scripts/analyzer/features/enum_from_parent.out new file mode 100644 index 0000000000..c160839da3 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/enum_from_parent.out @@ -0,0 +1,4 @@ +GDTEST_OK +a 0 true +b 1 true +c 42 true diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_value_from_parent.gd b/modules/gdscript/tests/scripts/analyzer/features/enum_value_from_parent.gd new file mode 100644 index 0000000000..26edce353d --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/enum_value_from_parent.gd @@ -0,0 +1,14 @@ +extends Node + +enum { VALUE_A, VALUE_B, VALUE_C = 42 } + +class Test: + var a = VALUE_A + var b = VALUE_B + var c = VALUE_C + +func test(): + var test_instance = Test.new() + prints("a", test_instance.a, test_instance.a == VALUE_A) + prints("b", test_instance.b, test_instance.b == VALUE_B) + prints("c", test_instance.c, test_instance.c == VALUE_C) diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_value_from_parent.out b/modules/gdscript/tests/scripts/analyzer/features/enum_value_from_parent.out new file mode 100644 index 0000000000..c160839da3 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/enum_value_from_parent.out @@ -0,0 +1,4 @@ +GDTEST_OK +a 0 true +b 1 true +c 42 true diff --git a/modules/gdscript/tests/scripts/parser/errors/double_dictionary_comma.gd b/modules/gdscript/tests/scripts/parser/errors/double_dictionary_comma.gd new file mode 100644 index 0000000000..92dfb2366d --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/double_dictionary_comma.gd @@ -0,0 +1,2 @@ +func test(): + var dictionary = { hello = "world",, } diff --git a/modules/gdscript/tests/scripts/parser/errors/double_dictionary_comma.out b/modules/gdscript/tests/scripts/parser/errors/double_dictionary_comma.out new file mode 100644 index 0000000000..d1dcd1cb4b --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/double_dictionary_comma.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Expected expression as dictionary key. diff --git a/modules/gdscript/tests/scripts/parser/features/advanced_expression_matching.gd b/modules/gdscript/tests/scripts/parser/features/advanced_expression_matching.gd new file mode 100644 index 0000000000..43b513045b --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/advanced_expression_matching.gd @@ -0,0 +1,34 @@ +func foo(x): + match x: + 1 + 1: + print("1+1") + [1,2,[1,{1:2,2:var z,..}]]: + print("[1,2,[1,{1:2,2:var z,..}]]") + print(z) + 1 if true else 2: + print("1 if true else 2") + 1 < 2: + print("1 < 2") + 1 or 2 and 1: + print("1 or 2 and 1") + 6 | 1: + print("1 | 1") + 1 >> 1: + print("1 >> 1") + 1, 2 or 3, 4: + print("1, 2 or 3, 4") + _: + print("wildcard") + +func test(): + foo(6 | 1) + foo(1 >> 1) + foo(2) + foo(1) + foo(1+1) + foo(1 < 2) + foo([2, 1]) + foo(4) + foo([1, 2, [1, {1 : 2, 2:3}]]) + foo([1, 2, [1, {1 : 2, 2:[1,3,5, "123"], 4:2}]]) + foo([1, 2, [1, {1 : 2}]]) diff --git a/modules/gdscript/tests/scripts/parser/features/advanced_expression_matching.out b/modules/gdscript/tests/scripts/parser/features/advanced_expression_matching.out new file mode 100644 index 0000000000..67c7e28046 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/advanced_expression_matching.out @@ -0,0 +1,14 @@ +GDTEST_OK +1 | 1 +1 >> 1 +1+1 +1 if true else 2 +1+1 +1 < 2 +wildcard +1, 2 or 3, 4 +[1,2,[1,{1:2,2:var z,..}]] +3 +[1,2,[1,{1:2,2:var z,..}]] +[1, 3, 5, 123] +wildcard diff --git a/modules/gdscript/tests/scripts/parser/features/basic_expression_matching.gd b/modules/gdscript/tests/scripts/parser/features/basic_expression_matching.gd new file mode 100644 index 0000000000..2b46f1e88a --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/basic_expression_matching.gd @@ -0,0 +1,27 @@ +func foo(x): + match x: + 1: + print("1") + 2: + print("2") + [1, 2]: + print("[1, 2]") + 3 or 4: + print("3 or 4") + 4: + print("4") + {1 : 2, 2 : 3}: + print("{1 : 2, 2 : 3}") + _: + print("wildcard") + +func test(): + foo(0) + foo(1) + foo(2) + foo([1, 2]) + foo(3) + foo(4) + foo([4,4]) + foo({1 : 2, 2 : 3}) + foo({1 : 2, 4 : 3}) diff --git a/modules/gdscript/tests/scripts/parser/features/basic_expression_matching.out b/modules/gdscript/tests/scripts/parser/features/basic_expression_matching.out new file mode 100644 index 0000000000..46ee4b04da --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/basic_expression_matching.out @@ -0,0 +1,10 @@ +GDTEST_OK +wildcard +1 +2 +[1, 2] +wildcard +4 +wildcard +{1 : 2, 2 : 3} +wildcard diff --git a/modules/gdscript/tests/scripts/parser/features/variable_declaration.gd b/modules/gdscript/tests/scripts/parser/features/variable_declaration.gd index 3b48f10ca7..38567d35c6 100644 --- a/modules/gdscript/tests/scripts/parser/features/variable_declaration.gd +++ b/modules/gdscript/tests/scripts/parser/features/variable_declaration.gd @@ -1,12 +1,19 @@ -var a # No init. -var b = 42 # Init. +var m1 # No init. +var m2 = 22 # Init. +var m3: String # No init, typed. +var m4: String = "44" # Init, typed. func test(): - var c # No init, local. - var d = 23 # Init, local. + var loc5 # No init, local. + var loc6 = 66 # Init, local. + var loc7: String # No init, typed. + var loc8: String = "88" # Init, typed. - a = 1 - c = 2 + m1 = 11 + m3 = "33" - prints(a, b, c, d) + loc5 = 55 + loc7 = "77" + + prints(m1, m2, m3, m4, loc5, loc6, loc7, loc8) print("OK") diff --git a/modules/gdscript/tests/scripts/parser/features/variable_declaration.out b/modules/gdscript/tests/scripts/parser/features/variable_declaration.out index 2e0a63c024..7817dd3169 100644 --- a/modules/gdscript/tests/scripts/parser/features/variable_declaration.out +++ b/modules/gdscript/tests/scripts/parser/features/variable_declaration.out @@ -1,7 +1,3 @@ GDTEST_OK ->> WARNING ->> Line: 5 ->> UNASSIGNED_VARIABLE ->> The variable 'c' was used but never assigned a value. -1 42 2 23 +11 22 33 44 55 66 77 88 OK diff --git a/modules/gdscript/tests/scripts/parser/warnings/empty_file.notest.gd b/modules/gdscript/tests/scripts/parser/warnings/empty_file.notest.gd new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/empty_file.notest.gd @@ -0,0 +1 @@ + diff --git a/modules/gdscript/tests/scripts/parser/warnings/empty_file.notest.out b/modules/gdscript/tests/scripts/parser/warnings/empty_file.notest.out new file mode 100644 index 0000000000..20eec212ba --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/empty_file.notest.out @@ -0,0 +1,4 @@ +>> WARNING +>> Line: 1 +>> EMPTY_FILE +>> Empty script file. diff --git a/modules/gdscript/tests/scripts/parser/warnings/empty_file_comment.notest.gd b/modules/gdscript/tests/scripts/parser/warnings/empty_file_comment.notest.gd new file mode 100644 index 0000000000..15cd95ff2b --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/empty_file_comment.notest.gd @@ -0,0 +1 @@ +#a comment diff --git a/modules/gdscript/tests/scripts/parser/warnings/empty_file_comment.notest.out b/modules/gdscript/tests/scripts/parser/warnings/empty_file_comment.notest.out new file mode 100644 index 0000000000..20eec212ba --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/empty_file_comment.notest.out @@ -0,0 +1,4 @@ +>> WARNING +>> Line: 1 +>> EMPTY_FILE +>> Empty script file. diff --git a/modules/gdscript/tests/scripts/parser/warnings/empty_file_newline.notest.gd b/modules/gdscript/tests/scripts/parser/warnings/empty_file_newline.notest.gd new file mode 100644 index 0000000000..b28b04f643 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/empty_file_newline.notest.gd @@ -0,0 +1,3 @@ + + + diff --git a/modules/gdscript/tests/scripts/parser/warnings/empty_file_newline.notest.out b/modules/gdscript/tests/scripts/parser/warnings/empty_file_newline.notest.out new file mode 100644 index 0000000000..20eec212ba --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/empty_file_newline.notest.out @@ -0,0 +1,4 @@ +>> WARNING +>> Line: 1 +>> EMPTY_FILE +>> Empty script file. diff --git a/modules/gdscript/tests/scripts/parser/warnings/empty_file_newline_comment.notest.gd b/modules/gdscript/tests/scripts/parser/warnings/empty_file_newline_comment.notest.gd new file mode 100644 index 0000000000..ecdba44d21 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/empty_file_newline_comment.notest.gd @@ -0,0 +1,4 @@ +#a comment, followed by a bunch of newlines + + + diff --git a/modules/gdscript/tests/scripts/parser/warnings/empty_file_newline_comment.notest.out b/modules/gdscript/tests/scripts/parser/warnings/empty_file_newline_comment.notest.out new file mode 100644 index 0000000000..20eec212ba --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/empty_file_newline_comment.notest.out @@ -0,0 +1,4 @@ +>> WARNING +>> Line: 1 +>> EMPTY_FILE +>> Empty script file. diff --git a/modules/gltf/config.py b/modules/gltf/config.py index a4ee871eff..52a97c93aa 100644 --- a/modules/gltf/config.py +++ b/modules/gltf/config.py @@ -22,7 +22,6 @@ def get_doc_classes(): "GLTFSpecGloss", "GLTFState", "GLTFTexture", - "PackedSceneGLTF", ] diff --git a/modules/gltf/doc_classes/GLTFDocument.xml b/modules/gltf/doc_classes/GLTFDocument.xml index 04c40dd752..f8e0007684 100644 --- a/modules/gltf/doc_classes/GLTFDocument.xml +++ b/modules/gltf/doc_classes/GLTFDocument.xml @@ -7,6 +7,28 @@ <tutorials> </tutorials> <methods> + <method name="import_scene"> + <return type="Node" /> + <argument index="0" name="path" type="String" /> + <argument index="1" name="flags" type="int" default="0" /> + <argument index="2" name="bake_fps" type="int" default="30" /> + <argument index="3" name="state" type="GLTFState" default="null" /> + <description> + Import a scene from glTF2 ".gltf" or ".glb" file. + </description> + </method> + <method name="save_scene"> + <return type="int" enum="Error" /> + <argument index="0" name="node" type="Node" /> + <argument index="1" name="path" type="String" /> + <argument index="2" name="src_path" type="String" /> + <argument index="3" name="flags" type="int" default="0" /> + <argument index="4" name="bake_fps" type="float" default="30" /> + <argument index="5" name="state" type="GLTFState" default="null" /> + <description> + Save a scene as a glTF2 ".glb" or ".gltf" file. + </description> + </method> </methods> <constants> </constants> diff --git a/modules/gltf/doc_classes/PackedSceneGLTF.xml b/modules/gltf/doc_classes/PackedSceneGLTF.xml deleted file mode 100644 index d0136c6402..0000000000 --- a/modules/gltf/doc_classes/PackedSceneGLTF.xml +++ /dev/null @@ -1,43 +0,0 @@ -<?xml version="1.0" encoding="UTF-8" ?> -<class name="PackedSceneGLTF" inherits="PackedScene" version="4.0"> - <brief_description> - </brief_description> - <description> - </description> - <tutorials> - </tutorials> - <methods> - <method name="export_gltf"> - <return type="int" enum="Error" /> - <argument index="0" name="node" type="Node" /> - <argument index="1" name="path" type="String" /> - <argument index="2" name="flags" type="int" default="0" /> - <argument index="3" name="bake_fps" type="float" default="1000.0" /> - <description> - </description> - </method> - <method name="import_gltf_scene"> - <return type="Node" /> - <argument index="0" name="path" type="String" /> - <argument index="1" name="flags" type="int" default="0" /> - <argument index="2" name="bake_fps" type="float" default="1000.0" /> - <argument index="3" name="state" type="GLTFState" default="null" /> - <description> - </description> - </method> - <method name="pack_gltf"> - <return type="void" /> - <argument index="0" name="path" type="String" /> - <argument index="1" name="flags" type="int" default="0" /> - <argument index="2" name="bake_fps" type="float" default="1000.0" /> - <argument index="3" name="state" type="GLTFState" default="null" /> - <description> - </description> - </method> - </methods> - <members> - <member name="_bundled" type="Dictionary" setter="_set_bundled_scene" getter="_get_bundled_scene" override="true" default="{"conn_count": 0,"conns": PackedInt32Array(),"editable_instances": [],"names": PackedStringArray(),"node_count": 0,"node_paths": [],"nodes": PackedInt32Array(),"variants": [],"version": 2}" /> - </members> - <constants> - </constants> -</class> diff --git a/modules/gltf/editor_scene_exporter_gltf_plugin.cpp b/modules/gltf/editor_scene_exporter_gltf_plugin.cpp index ae080bcc9a..fd9f758f10 100644 --- a/modules/gltf/editor_scene_exporter_gltf_plugin.cpp +++ b/modules/gltf/editor_scene_exporter_gltf_plugin.cpp @@ -30,9 +30,11 @@ #include "editor_scene_exporter_gltf_plugin.h" #include "core/config/project_settings.h" +#include "core/error/error_list.h" #include "core/object/object.h" #include "core/templates/vector.h" #include "editor/editor_file_system.h" +#include "gltf_document.h" #include "scene/3d/mesh_instance_3d.h" #include "scene/gui/check_box.h" #include "scene/main/node.h" @@ -49,7 +51,6 @@ bool SceneExporterGLTFPlugin::has_main_screen() const { SceneExporterGLTFPlugin::SceneExporterGLTFPlugin(EditorNode *p_node) { editor = p_node; - convert_gltf2.instantiate(); file_export_lib = memnew(EditorFileDialog); editor->get_gui_base()->add_child(file_export_lib); file_export_lib->connect("file_selected", callable_mp(this, &SceneExporterGLTFPlugin::_gltf2_dialog_action)); @@ -71,8 +72,12 @@ void SceneExporterGLTFPlugin::_gltf2_dialog_action(String p_file) { return; } List<String> deps; - convert_gltf2->save_scene(root, p_file, p_file, 0, 1000.0f, &deps); - EditorFileSystem::get_singleton()->scan_changes(); + Ref<GLTFDocument> doc; + doc.instantiate(); + Error err = doc->save_scene(root, p_file, p_file, 0, 30.0f, Ref<GLTFState>()); + if (err != OK) { + ERR_PRINT(vformat("glTF2 save scene error %s.", itos(err))); + } } void SceneExporterGLTFPlugin::convert_scene_to_gltf2() { diff --git a/modules/gltf/editor_scene_exporter_gltf_plugin.h b/modules/gltf/editor_scene_exporter_gltf_plugin.h index d952894c16..c4f277fca2 100644 --- a/modules/gltf/editor_scene_exporter_gltf_plugin.h +++ b/modules/gltf/editor_scene_exporter_gltf_plugin.h @@ -37,7 +37,6 @@ class SceneExporterGLTFPlugin : public EditorPlugin { GDCLASS(SceneExporterGLTFPlugin, EditorPlugin); - Ref<PackedSceneGLTF> convert_gltf2; EditorNode *editor = nullptr; EditorFileDialog *file_export_lib = nullptr; void _gltf2_dialog_action(String p_file); diff --git a/modules/gltf/editor_scene_importer_gltf.cpp b/modules/gltf/editor_scene_importer_gltf.cpp index eca1c85bf3..12796c41d7 100644 --- a/modules/gltf/editor_scene_importer_gltf.cpp +++ b/modules/gltf/editor_scene_importer_gltf.cpp @@ -50,9 +50,9 @@ Node *EditorSceneImporterGLTF::import_scene(const String &p_path, uint32_t p_flags, int p_bake_fps, List<String> *r_missing_deps, Error *r_err) { - Ref<PackedSceneGLTF> importer; - importer.instantiate(); - return importer->import_scene(p_path, p_flags, p_bake_fps, r_missing_deps, r_err, Ref<GLTFState>()); + Ref<GLTFDocument> doc; + doc.instantiate(); + return doc->import_scene_gltf(p_path, p_flags, p_bake_fps, Ref<GLTFState>(), r_missing_deps, r_err); } Ref<Animation> EditorSceneImporterGLTF::import_animation(const String &p_path, @@ -60,114 +60,3 @@ Ref<Animation> EditorSceneImporterGLTF::import_animation(const String &p_path, int p_bake_fps) { return Ref<Animation>(); } - -void PackedSceneGLTF::_bind_methods() { - ClassDB::bind_method( - D_METHOD("export_gltf", "node", "path", "flags", "bake_fps"), - &PackedSceneGLTF::export_gltf, DEFVAL(0), DEFVAL(1000.0f)); - ClassDB::bind_method(D_METHOD("pack_gltf", "path", "flags", "bake_fps", "state"), - &PackedSceneGLTF::pack_gltf, DEFVAL(0), DEFVAL(1000.0f), DEFVAL(Ref<GLTFState>())); - ClassDB::bind_method(D_METHOD("import_gltf_scene", "path", "flags", "bake_fps", "state"), - &PackedSceneGLTF::import_gltf_scene, DEFVAL(0), DEFVAL(1000.0f), DEFVAL(Ref<GLTFState>())); -} -Node *PackedSceneGLTF::import_gltf_scene(const String &p_path, uint32_t p_flags, float p_bake_fps, Ref<GLTFState> r_state) { - Error err = FAILED; - List<String> deps; - return import_scene(p_path, p_flags, p_bake_fps, &deps, &err, r_state); -} - -Node *PackedSceneGLTF::import_scene(const String &p_path, uint32_t p_flags, - int p_bake_fps, - List<String> *r_missing_deps, - Error *r_err, - Ref<GLTFState> r_state) { - if (r_state == Ref<GLTFState>()) { - r_state.instantiate(); - } - r_state->use_named_skin_binds = - p_flags & EditorSceneImporter::IMPORT_USE_NAMED_SKIN_BINDS; - - Ref<GLTFDocument> gltf_document; - gltf_document.instantiate(); - Error err = gltf_document->parse(r_state, p_path); - if (r_err) { - *r_err = err; - } - ERR_FAIL_COND_V(err != Error::OK, nullptr); - - Node3D *root = memnew(Node3D); - for (int32_t root_i = 0; root_i < r_state->root_nodes.size(); root_i++) { - gltf_document->_generate_scene_node(r_state, root, root, r_state->root_nodes[root_i]); - } - gltf_document->_process_mesh_instances(r_state, root); - if (r_state->animations.size()) { - AnimationPlayer *ap = memnew(AnimationPlayer); - root->add_child(ap); - ap->set_owner(root); - for (int i = 0; i < r_state->animations.size(); i++) { - gltf_document->_import_animation(r_state, ap, i, p_bake_fps); - } - } - - return cast_to<Node3D>(root); -} - -void PackedSceneGLTF::pack_gltf(String p_path, int32_t p_flags, - real_t p_bake_fps, Ref<GLTFState> r_state) { - Error err = FAILED; - List<String> deps; - Node *root = import_scene(p_path, p_flags, p_bake_fps, &deps, &err, r_state); - ERR_FAIL_COND(err != OK); - pack(root); -} - -void PackedSceneGLTF::save_scene(Node *p_node, const String &p_path, - const String &p_src_path, uint32_t p_flags, - int p_bake_fps, List<String> *r_missing_deps, - Error *r_err) { - Error err = FAILED; - if (r_err) { - *r_err = err; - } - Ref<GLTFDocument> gltf_document; - gltf_document.instantiate(); - Ref<GLTFState> state; - state.instantiate(); - err = gltf_document->serialize(state, p_node, p_path); - if (r_err) { - *r_err = err; - } -} - -void PackedSceneGLTF::_build_parent_hierachy(Ref<GLTFState> state) { - // build the hierarchy - for (GLTFNodeIndex node_i = 0; node_i < state->nodes.size(); node_i++) { - for (int j = 0; j < state->nodes[node_i]->children.size(); j++) { - GLTFNodeIndex child_i = state->nodes[node_i]->children[j]; - ERR_FAIL_INDEX(child_i, state->nodes.size()); - if (state->nodes.write[child_i]->parent != -1) { - continue; - } - state->nodes.write[child_i]->parent = node_i; - } - } -} - -Error PackedSceneGLTF::export_gltf(Node *p_root, String p_path, - int32_t p_flags, - real_t p_bake_fps) { - ERR_FAIL_COND_V(!p_root, FAILED); - List<String> deps; - Error err; - String path = p_path; - int32_t flags = p_flags; - real_t baked_fps = p_bake_fps; - Ref<PackedSceneGLTF> exporter; - exporter.instantiate(); - exporter->save_scene(p_root, path, "", flags, baked_fps, &deps, &err); - int32_t error_code = err; - if (error_code != 0) { - return Error(error_code); - } - return OK; -} diff --git a/modules/gltf/editor_scene_importer_gltf.h b/modules/gltf/editor_scene_importer_gltf.h index 7bc5f594ed..eb8775b137 100644 --- a/modules/gltf/editor_scene_importer_gltf.h +++ b/modules/gltf/editor_scene_importer_gltf.h @@ -46,35 +46,9 @@ class EditorSceneImporterGLTF : public EditorSceneImporter { public: virtual uint32_t get_import_flags() const override; virtual void get_extensions(List<String> *r_extensions) const override; - virtual Node *import_scene(const String &p_path, uint32_t p_flags, - int p_bake_fps, - List<String> *r_missing_deps = nullptr, - Error *r_err = nullptr) override; + virtual Node *import_scene(const String &p_path, uint32_t p_flags, int p_bake_fps, List<String> *r_missing_deps, Error *r_err = nullptr) override; virtual Ref<Animation> import_animation(const String &p_path, uint32_t p_flags, int p_bake_fps) override; }; #endif - -class PackedSceneGLTF : public PackedScene { - GDCLASS(PackedSceneGLTF, PackedScene); - -protected: - static void _bind_methods(); - -public: - virtual void save_scene(Node *p_node, const String &p_path, const String &p_src_path, - uint32_t p_flags, int p_bake_fps, - List<String> *r_missing_deps, Error *r_err = nullptr); - virtual void _build_parent_hierachy(Ref<GLTFState> state); - virtual Error export_gltf(Node *p_root, String p_path, int32_t p_flags = 0, - real_t p_bake_fps = 1000.0f); - virtual Node *import_scene(const String &p_path, uint32_t p_flags, - int p_bake_fps, - List<String> *r_missing_deps, - Error *r_err, - Ref<GLTFState> r_state); - virtual Node *import_gltf_scene(const String &p_path, uint32_t p_flags, float p_bake_fps, Ref<GLTFState> r_state = Ref<GLTFState>()); - virtual void pack_gltf(String p_path, int32_t p_flags = 0, - real_t p_bake_fps = 1000.0f, Ref<GLTFState> r_state = Ref<GLTFState>()); -}; #endif // EDITOR_SCENE_IMPORTER_GLTF_H diff --git a/modules/gltf/gltf_accessor.h b/modules/gltf/gltf_accessor.h index 949a601730..57aea1026c 100644 --- a/modules/gltf/gltf_accessor.h +++ b/modules/gltf/gltf_accessor.h @@ -44,8 +44,7 @@ private: int component_type = 0; bool normalized = false; int count = 0; - GLTFDocument::GLTFType - type = GLTFDocument::TYPE_SCALAR; + GLTFDocument::GLTFType type = GLTFDocument::TYPE_SCALAR; Vector<double> min; Vector<double> max; int sparse_count = 0; diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp index ff0579a11c..db324e23b7 100644 --- a/modules/gltf/gltf_document.cpp +++ b/modules/gltf/gltf_document.cpp @@ -48,6 +48,7 @@ #include "core/io/file_access.h" #include "core/io/json.h" #include "core/math/disjoint_set.h" +#include "core/math/vector2.h" #include "core/variant/typed_array.h" #include "core/variant/variant.h" #include "core/version.h" @@ -61,6 +62,7 @@ #include "scene/resources/surface_tool.h" #include "modules/modules_enabled.gen.h" + #ifdef MODULE_CSG_ENABLED #include "modules/csg/csg_shape.h" #endif // MODULE_CSG_ENABLED @@ -70,6 +72,7 @@ #include <stdio.h> #include <stdlib.h> +#include <cstdint> #include <limits> Error GLTFDocument::serialize(Ref<GLTFState> state, Node *p_root, const String &p_path) { @@ -2170,11 +2173,14 @@ Error GLTFDocument::_serialize_meshes(Ref<GLTFState> state) { } Array array = import_mesh->get_surface_arrays(surface_i); + uint32_t format = import_mesh->get_surface_format(surface_i); + int32_t vertex_num = 0; Dictionary attributes; { Vector<Vector3> a = array[Mesh::ARRAY_VERTEX]; ERR_FAIL_COND_V(!a.size(), ERR_INVALID_DATA); attributes["POSITION"] = _encode_accessor_as_vec3(state, a, true); + vertex_num = a.size(); } { Vector<real_t> a = array[Mesh::ARRAY_TANGENT]; @@ -2217,6 +2223,58 @@ Error GLTFDocument::_serialize_meshes(Ref<GLTFState> state) { attributes["TEXCOORD_1"] = _encode_accessor_as_vec2(state, a, true); } } + for (int custom_i = 0; custom_i < 3; custom_i++) { + Vector<float> a = array[Mesh::ARRAY_CUSTOM0 + custom_i]; + if (a.size()) { + int num_channels = 4; + int custom_shift = Mesh::ARRAY_FORMAT_CUSTOM0_SHIFT + custom_i * Mesh::ARRAY_FORMAT_CUSTOM_BITS; + switch ((format >> custom_shift) & Mesh::ARRAY_FORMAT_CUSTOM_MASK) { + case Mesh::ARRAY_CUSTOM_R_FLOAT: + num_channels = 1; + break; + case Mesh::ARRAY_CUSTOM_RG_FLOAT: + num_channels = 2; + break; + case Mesh::ARRAY_CUSTOM_RGB_FLOAT: + num_channels = 3; + break; + case Mesh::ARRAY_CUSTOM_RGBA_FLOAT: + num_channels = 4; + break; + } + int texcoord_i = 2 + 2 * custom_i; + String gltf_texcoord_key; + for (int prev_texcoord_i = 0; prev_texcoord_i < texcoord_i; prev_texcoord_i++) { + gltf_texcoord_key = vformat("TEXCOORD_%d", prev_texcoord_i); + if (!attributes.has(gltf_texcoord_key)) { + Vector<Vector2> empty; + empty.resize(vertex_num); + attributes[gltf_texcoord_key] = _encode_accessor_as_vec2(state, empty, true); + } + } + + LocalVector<Vector2> first_channel; + first_channel.resize(vertex_num); + LocalVector<Vector2> second_channel; + second_channel.resize(vertex_num); + for (int32_t vert_i = 0; vert_i < vertex_num; vert_i++) { + float u = a[vert_i * num_channels + 0]; + float v = (num_channels == 1 ? 0.0f : a[vert_i * num_channels + 1]); + first_channel[vert_i] = Vector2(u, v); + u = 0; + v = 0; + if (num_channels >= 3) { + u = a[vert_i * num_channels + 2]; + v = (num_channels == 3 ? 0.0f : a[vert_i * num_channels + 3]); + second_channel[vert_i] = Vector2(u, v); + } + } + gltf_texcoord_key = vformat("TEXCOORD_%d", texcoord_i); + attributes[gltf_texcoord_key] = _encode_accessor_as_vec2(state, first_channel, true); + gltf_texcoord_key = vformat("TEXCOORD_%d", texcoord_i + 1); + attributes[gltf_texcoord_key] = _encode_accessor_as_vec2(state, second_channel, true); + } + } { Vector<Color> a = array[Mesh::ARRAY_COLOR]; if (a.size()) { @@ -2252,13 +2310,12 @@ Error GLTFDocument::_serialize_meshes(Ref<GLTFState> state) { } attributes["JOINTS_0"] = _encode_accessor_as_joints(state, attribs, true); } else if ((a.size() / (JOINT_GROUP_SIZE * 2)) >= vertex_array.size()) { - int32_t vertex_count = vertex_array.size(); Vector<Color> joints_0; - joints_0.resize(vertex_count); + joints_0.resize(vertex_num); Vector<Color> joints_1; - joints_1.resize(vertex_count); + joints_1.resize(vertex_num); int32_t weights_8_count = JOINT_GROUP_SIZE * 2; - for (int32_t vertex_i = 0; vertex_i < vertex_count; vertex_i++) { + for (int32_t vertex_i = 0; vertex_i < vertex_num; vertex_i++) { Color joint_0; joint_0.r = a[vertex_i * weights_8_count + 0]; joint_0.g = a[vertex_i * weights_8_count + 1]; @@ -2288,13 +2345,12 @@ Error GLTFDocument::_serialize_meshes(Ref<GLTFState> state) { } attributes["WEIGHTS_0"] = _encode_accessor_as_weights(state, attribs, true); } else if ((a.size() / (JOINT_GROUP_SIZE * 2)) >= vertex_array.size()) { - int32_t vertex_count = vertex_array.size(); Vector<Color> weights_0; - weights_0.resize(vertex_count); + weights_0.resize(vertex_num); Vector<Color> weights_1; - weights_1.resize(vertex_count); + weights_1.resize(vertex_num); int32_t weights_8_count = JOINT_GROUP_SIZE * 2; - for (int32_t vertex_i = 0; vertex_i < vertex_count; vertex_i++) { + for (int32_t vertex_i = 0; vertex_i < vertex_num; vertex_i++) { Color weight_0; weight_0.r = a[vertex_i * weights_8_count + 0]; weight_0.g = a[vertex_i * weights_8_count + 1]; @@ -2458,7 +2514,8 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> state) { ERR_FAIL_COND_V(!d.has("primitives"), ERR_PARSE_ERROR); Array primitives = d["primitives"]; - const Dictionary &extras = d.has("extras") ? (Dictionary)d["extras"] : Dictionary(); + const Dictionary &extras = d.has("extras") ? (Dictionary)d["extras"] : + Dictionary(); Ref<EditorSceneImporterMesh> import_mesh; import_mesh.instantiate(); String mesh_name = "mesh"; @@ -2468,6 +2525,7 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> state) { import_mesh->set_name(_gen_unique_name(state, vformat("%s_%s", state->scene_name, mesh_name))); for (int j = 0; j < primitives.size(); j++) { + uint32_t flags = 0; Dictionary p = primitives[j]; Array array; @@ -2499,8 +2557,11 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> state) { } ERR_FAIL_COND_V(!a.has("POSITION"), ERR_PARSE_ERROR); + int32_t vertex_num = 0; if (a.has("POSITION")) { - array[Mesh::ARRAY_VERTEX] = _decode_accessor_as_vec3(state, a["POSITION"], true); + PackedVector3Array vertices = _decode_accessor_as_vec3(state, a["POSITION"], true); + array[Mesh::ARRAY_VERTEX] = vertices; + vertex_num = vertices.size(); } if (a.has("NORMAL")) { array[Mesh::ARRAY_NORMAL] = _decode_accessor_as_vec3(state, a["NORMAL"], true); @@ -2514,6 +2575,60 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> state) { if (a.has("TEXCOORD_1")) { array[Mesh::ARRAY_TEX_UV2] = _decode_accessor_as_vec2(state, a["TEXCOORD_1"], true); } + for (int custom_i = 0; custom_i < 3; custom_i++) { + Vector<float> cur_custom; + Vector<Vector2> texcoord_first; + Vector<Vector2> texcoord_second; + + int texcoord_i = 2 + 2 * custom_i; + String gltf_texcoord_key = vformat("TEXCOORD_%d", texcoord_i); + int num_channels = 0; + if (a.has(gltf_texcoord_key)) { + texcoord_first = _decode_accessor_as_vec2(state, a[gltf_texcoord_key], true); + num_channels = 2; + } + gltf_texcoord_key = vformat("TEXCOORD_%d", texcoord_i + 1); + if (a.has(gltf_texcoord_key)) { + texcoord_second = _decode_accessor_as_vec2(state, a[gltf_texcoord_key], true); + num_channels = 4; + } + if (!num_channels) { + break; + } + if (num_channels == 2 || num_channels == 4) { + cur_custom.resize(vertex_num * num_channels); + for (int32_t uv_i = 0; uv_i < texcoord_first.size() && uv_i < vertex_num; uv_i++) { + cur_custom.write[uv_i * num_channels + 0] = texcoord_first[uv_i].x; + cur_custom.write[uv_i * num_channels + 1] = texcoord_first[uv_i].y; + } + // Vector.resize seems to not zero-initialize. Ensure all unused elements are 0: + for (int32_t uv_i = texcoord_first.size(); uv_i < vertex_num; uv_i++) { + cur_custom.write[uv_i * num_channels + 0] = 0; + cur_custom.write[uv_i * num_channels + 1] = 0; + } + } + if (num_channels == 4) { + for (int32_t uv_i = 0; uv_i < texcoord_second.size() && uv_i < vertex_num; uv_i++) { + // num_channels must be 4 + cur_custom.write[uv_i * num_channels + 2] = texcoord_second[uv_i].x; + cur_custom.write[uv_i * num_channels + 3] = texcoord_second[uv_i].y; + } + // Vector.resize seems to not zero-initialize. Ensure all unused elements are 0: + for (int32_t uv_i = texcoord_second.size(); uv_i < vertex_num; uv_i++) { + cur_custom.write[uv_i * num_channels + 2] = 0; + cur_custom.write[uv_i * num_channels + 3] = 0; + } + } + if (cur_custom.size() > 0) { + array[Mesh::ARRAY_CUSTOM0 + custom_i] = cur_custom; + int custom_shift = Mesh::ARRAY_FORMAT_CUSTOM0_SHIFT + custom_i * Mesh::ARRAY_FORMAT_CUSTOM_BITS; + if (num_channels == 2) { + flags |= Mesh::ARRAY_CUSTOM_RG_FLOAT << custom_shift; + } else { + flags |= Mesh::ARRAY_CUSTOM_RGBA_FLOAT << custom_shift; + } + } + } if (a.has("COLOR_0")) { array[Mesh::ARRAY_COLOR] = _decode_accessor_as_color(state, a["COLOR_0"], true); has_vertex_color = true; @@ -2525,10 +2640,9 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> state) { PackedInt32Array joints_1 = _decode_accessor_as_ints(state, a["JOINTS_1"], true); ERR_FAIL_COND_V(joints_0.size() != joints_0.size(), ERR_INVALID_DATA); int32_t weight_8_count = JOINT_GROUP_SIZE * 2; - int32_t vertex_count = joints_0.size() / JOINT_GROUP_SIZE; Vector<int> joints; - joints.resize(vertex_count * weight_8_count); - for (int32_t vertex_i = 0; vertex_i < vertex_count; vertex_i++) { + joints.resize(vertex_num * weight_8_count); + for (int32_t vertex_i = 0; vertex_i < vertex_num; vertex_i++) { joints.write[vertex_i * weight_8_count + 0] = joints_0[vertex_i * JOINT_GROUP_SIZE + 0]; joints.write[vertex_i * weight_8_count + 1] = joints_0[vertex_i * JOINT_GROUP_SIZE + 1]; joints.write[vertex_i * weight_8_count + 2] = joints_0[vertex_i * JOINT_GROUP_SIZE + 2]; @@ -2567,9 +2681,8 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> state) { Vector<float> weights; ERR_FAIL_COND_V(weights_0.size() != weights_1.size(), ERR_INVALID_DATA); int32_t weight_8_count = JOINT_GROUP_SIZE * 2; - int32_t vertex_count = weights_0.size() / JOINT_GROUP_SIZE; - weights.resize(vertex_count * weight_8_count); - for (int32_t vertex_i = 0; vertex_i < vertex_count; vertex_i++) { + weights.resize(vertex_num * weight_8_count); + for (int32_t vertex_i = 0; vertex_i < vertex_num; vertex_i++) { weights.write[vertex_i * weight_8_count + 0] = weights_0[vertex_i * JOINT_GROUP_SIZE + 0]; weights.write[vertex_i * weight_8_count + 1] = weights_0[vertex_i * JOINT_GROUP_SIZE + 1]; weights.write[vertex_i * weight_8_count + 2] = weights_0[vertex_i * JOINT_GROUP_SIZE + 2]; @@ -2797,7 +2910,7 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> state) { mat = mat3d; } - import_mesh->add_surface(primitive, array, morphs, Dictionary(), mat, mat.is_valid() ? mat->get_name() : String()); + import_mesh->add_surface(primitive, array, morphs, Dictionary(), mat, mat.is_valid() ? mat->get_name() : String(), flags); } Vector<float> blend_weights; @@ -2953,6 +3066,7 @@ Error GLTFDocument::_parse_images(Ref<GLTFState> state, const String &p_base_pat } } } else { // Relative path to an external image file. + uri = uri.uri_decode(); uri = p_base_path.plus_file(uri).replace("\\", "/"); // Fix for Windows. // ResourceLoader will rely on the file extension to use the relevant loader. // The spec says that if mimeType is defined, it should take precedence (e.g. @@ -4896,7 +5010,7 @@ GLTFMeshIndex GLTFDocument::_convert_mesh_instance(Ref<GLTFState> state, MeshIns if (p_mesh_instance->get_material_override().is_valid()) { mat = p_mesh_instance->get_material_override(); } - import_mesh->add_surface(primitive_type, arrays, blend_shape_arrays, Dictionary(), mat, surface_name); + import_mesh->add_surface(primitive_type, arrays, blend_shape_arrays, Dictionary(), mat, surface_name, godot_mesh->surface_get_format(surface_i)); } for (int32_t blend_i = 0; blend_i < blend_count; blend_i++) { blend_weights.write[blend_i] = 0.0f; @@ -6630,3 +6744,78 @@ Error GLTFDocument::_serialize_file(Ref<GLTFState> state, const String p_path) { } return err; } + +Error GLTFDocument::save_scene(Node *p_node, const String &p_path, + const String &p_src_path, uint32_t p_flags, + float p_bake_fps, Ref<GLTFState> r_state) { + Ref<GLTFDocument> gltf_document; + gltf_document.instantiate(); + if (r_state == Ref<GLTFState>()) { + r_state.instantiate(); + } + return gltf_document->serialize(r_state, p_node, p_path); +} + +Node *GLTFDocument::import_scene_gltf(const String &p_path, uint32_t p_flags, int32_t p_bake_fps, Ref<GLTFState> r_state, List<String> *r_missing_deps, Error *r_err) { + // TODO Add missing texture and missing .bin file paths to r_missing_deps 2021-09-10 fire + if (r_state == Ref<GLTFState>()) { + r_state.instantiate(); + } + r_state->use_named_skin_binds = + p_flags & EditorSceneImporter::IMPORT_USE_NAMED_SKIN_BINDS; + + Ref<GLTFDocument> gltf_document; + gltf_document.instantiate(); + Error err = gltf_document->parse(r_state, p_path); + if (r_err) { + *r_err = err; + } + ERR_FAIL_COND_V(err != Error::OK, nullptr); + + Node3D *root = memnew(Node3D); + for (int32_t root_i = 0; root_i < r_state->root_nodes.size(); root_i++) { + gltf_document->_generate_scene_node(r_state, root, root, r_state->root_nodes[root_i]); + } + gltf_document->_process_mesh_instances(r_state, root); + if (r_state->animations.size()) { + AnimationPlayer *ap = memnew(AnimationPlayer); + root->add_child(ap); + ap->set_owner(root); + for (int i = 0; i < r_state->animations.size(); i++) { + gltf_document->_import_animation(r_state, ap, i, p_bake_fps); + } + } + + return root; +} + +void GLTFDocument::_bind_methods() { + ClassDB::bind_method(D_METHOD("save_scene", "node", "path", "src_path", "flags", "bake_fps", "state"), + &GLTFDocument::save_scene, DEFVAL(0), DEFVAL(30), DEFVAL(Ref<GLTFState>())); + ClassDB::bind_method(D_METHOD("import_scene", "path", "flags", "bake_fps", "state"), + &GLTFDocument::import_scene, DEFVAL(0), DEFVAL(30), DEFVAL(Ref<GLTFState>())); +} + +void GLTFDocument::_build_parent_hierachy(Ref<GLTFState> state) { + // build the hierarchy + for (GLTFNodeIndex node_i = 0; node_i < state->nodes.size(); node_i++) { + for (int j = 0; j < state->nodes[node_i]->children.size(); j++) { + GLTFNodeIndex child_i = state->nodes[node_i]->children[j]; + ERR_FAIL_INDEX(child_i, state->nodes.size()); + if (state->nodes.write[child_i]->parent != -1) { + continue; + } + state->nodes.write[child_i]->parent = node_i; + } + } +} + +Node *GLTFDocument::import_scene(const String &p_path, uint32_t p_flags, int32_t p_bake_fps, Ref<GLTFState> r_state) { + Error err = FAILED; + List<String> deps; + Node *node = import_scene_gltf(p_path, p_flags, p_bake_fps, r_state, &deps, &err); + if (err != OK) { + return nullptr; + } + return node; +} diff --git a/modules/gltf/gltf_document.h b/modules/gltf/gltf_document.h index 7a826897a9..fb798a055a 100644 --- a/modules/gltf/gltf_document.h +++ b/modules/gltf/gltf_document.h @@ -44,6 +44,7 @@ #include "scene/resources/texture.h" #include "modules/modules_enabled.gen.h" +#include <cstdint> class GLTFState; class GLTFSkin; @@ -102,6 +103,16 @@ public: COMPONENT_TYPE_FLOAT = 5126, }; +protected: + static void _bind_methods(); + +public: + Node *import_scene(const String &p_path, uint32_t p_flags, int32_t p_bake_fps, Ref<GLTFState> r_state); + Node *import_scene_gltf(const String &p_path, uint32_t p_flags, int32_t p_bake_fps, Ref<GLTFState> r_state, List<String> *r_missing_deps, Error *r_err = nullptr); + Error save_scene(Node *p_node, const String &p_path, + const String &p_src_path, uint32_t p_flags, + float p_bake_fps, Ref<GLTFState> r_state); + private: template <class T> static Array to_array(const Vector<T> &p_inp) { @@ -155,6 +166,7 @@ private: r_out[keys[i]] = p_inp[keys[i]]; } } + void _build_parent_hierachy(Ref<GLTFState> state); double _filter_number(double p_float); String _get_component_type_name(const uint32_t p_component); int _get_component_type_size(const int component_type); diff --git a/modules/gltf/gltf_node.h b/modules/gltf/gltf_node.h index 378b6da8bf..eca3acb239 100644 --- a/modules/gltf/gltf_node.h +++ b/modules/gltf/gltf_node.h @@ -37,7 +37,6 @@ class GLTFNode : public Resource { GDCLASS(GLTFNode, Resource); friend class GLTFDocument; - friend class PackedSceneGLTF; private: // matrices need to be transformed to this diff --git a/modules/gltf/gltf_state.h b/modules/gltf/gltf_state.h index d8209523c5..896ea5fc56 100644 --- a/modules/gltf/gltf_state.h +++ b/modules/gltf/gltf_state.h @@ -51,7 +51,6 @@ class GLTFState : public Resource { GDCLASS(GLTFState, Resource); friend class GLTFDocument; - friend class PackedSceneGLTF; String filename; Dictionary json; diff --git a/modules/gltf/register_types.cpp b/modules/gltf/register_types.cpp index 85921490d2..d6020f50f0 100644 --- a/modules/gltf/register_types.cpp +++ b/modules/gltf/register_types.cpp @@ -80,7 +80,6 @@ void register_gltf_types() { GDREGISTER_CLASS(GLTFLight); GDREGISTER_CLASS(GLTFState); GDREGISTER_CLASS(GLTFDocument); - GDREGISTER_CLASS(PackedSceneGLTF); #endif } diff --git a/modules/gridmap/grid_map.cpp b/modules/gridmap/grid_map.cpp index 8e8b6f14ad..487e6deac0 100644 --- a/modules/gridmap/grid_map.cpp +++ b/modules/gridmap/grid_map.cpp @@ -475,7 +475,7 @@ bool GridMap::_octant_update(const OctantKey &p_key) { } Pair<Transform3D, IndexKey> p; - p.first = xform; + p.first = xform * mesh_library->get_item_mesh_transform(c.item); p.second = E->get(); multimesh_items[c.item].push_back(p); } diff --git a/modules/gridmap/grid_map_editor_plugin.cpp b/modules/gridmap/grid_map_editor_plugin.cpp index 2331a12d0f..c170bb107e 100644 --- a/modules/gridmap/grid_map_editor_plugin.cpp +++ b/modules/gridmap/grid_map_editor_plugin.cpp @@ -255,6 +255,12 @@ void GridMapEditor::_update_cursor_transform() { cursor_transform.basis *= node->get_cell_scale(); cursor_transform = node->get_global_transform() * cursor_transform; + if (selected_palette >= 0) { + if (node && !node->get_mesh_library().is_null()) { + cursor_transform *= node->get_mesh_library()->get_item_mesh_transform(selected_palette); + } + } + if (cursor_instance.is_valid()) { RenderingServer::get_singleton()->instance_set_transform(cursor_instance, cursor_transform); RenderingServer::get_singleton()->instance_set_visible(cursor_instance, cursor_visible); diff --git a/modules/lightmapper_rd/SCsub b/modules/lightmapper_rd/SCsub index 2f04f1833e..5cc9d8ee8b 100644 --- a/modules/lightmapper_rd/SCsub +++ b/modules/lightmapper_rd/SCsub @@ -7,6 +7,9 @@ env_lightmapper_rd = env_modules.Clone() env_lightmapper_rd.GLSL_HEADER("lm_raster.glsl") env_lightmapper_rd.GLSL_HEADER("lm_compute.glsl") env_lightmapper_rd.GLSL_HEADER("lm_blendseams.glsl") +env_lightmapper_rd.Depends("lm_raster.glsl.gen.h", "lm_common_inc.glsl") +env_lightmapper_rd.Depends("lm_compute.glsl.gen.h", "lm_common_inc.glsl") +env_lightmapper_rd.Depends("lm_blendseams.glsl.gen.h", "lm_common_inc.glsl") # Godot source files env_lightmapper_rd.add_source_files(env.modules_sources, "*.cpp") diff --git a/modules/lightmapper_rd/lightmapper_rd.cpp b/modules/lightmapper_rd/lightmapper_rd.cpp index fe941e25e7..ba4ef3be8d 100644 --- a/modules/lightmapper_rd/lightmapper_rd.cpp +++ b/modules/lightmapper_rd/lightmapper_rd.cpp @@ -274,13 +274,12 @@ Lightmapper::BakeError LightmapperRD::_blit_meshes_into_atlas(int p_max_texture_ return BAKE_OK; } -void LightmapperRD::_create_acceleration_structures(RenderingDevice *rd, Size2i atlas_size, int atlas_slices, AABB &bounds, int grid_size, Vector<Probe> &probe_positions, GenerateProbes p_generate_probes, Vector<int> &slice_triangle_count, Vector<int> &slice_seam_count, RID &vertex_buffer, RID &triangle_buffer, RID &box_buffer, RID &lights_buffer, RID &triangle_cell_indices_buffer, RID &probe_positions_buffer, RID &grid_texture, RID &seams_buffer, BakeStepFunc p_step_function, void *p_bake_userdata) { +void LightmapperRD::_create_acceleration_structures(RenderingDevice *rd, Size2i atlas_size, int atlas_slices, AABB &bounds, int grid_size, Vector<Probe> &probe_positions, GenerateProbes p_generate_probes, Vector<int> &slice_triangle_count, Vector<int> &slice_seam_count, RID &vertex_buffer, RID &triangle_buffer, RID &lights_buffer, RID &triangle_cell_indices_buffer, RID &probe_positions_buffer, RID &grid_texture, RID &seams_buffer, BakeStepFunc p_step_function, void *p_bake_userdata) { HashMap<Vertex, uint32_t, VertexHash> vertex_map; //fill triangles array and vertex array LocalVector<Triangle> triangles; LocalVector<Vertex> vertex_array; - LocalVector<Box> box_array; LocalVector<Seam> seams; slice_triangle_count.resize(atlas_slices); @@ -387,16 +386,13 @@ void LightmapperRD::_create_acceleration_structures(RenderingDevice *rd, Size2i } } - Box box; - box.min_bounds[0] = taabb.position.x; - box.min_bounds[1] = taabb.position.y; - box.min_bounds[2] = taabb.position.z; - box.max_bounds[0] = taabb.position.x + MAX(taabb.size.x, 0.0001); - box.max_bounds[1] = taabb.position.y + MAX(taabb.size.y, 0.0001); - box.max_bounds[2] = taabb.position.z + MAX(taabb.size.z, 0.0001); - box.pad0 = box.pad1 = 0; //make valgrind not complain - box_array.push_back(box); - + t.min_bounds[0] = taabb.position.x; + t.min_bounds[1] = taabb.position.y; + t.min_bounds[2] = taabb.position.z; + t.max_bounds[0] = taabb.position.x + MAX(taabb.size.x, 0.0001); + t.max_bounds[1] = taabb.position.y + MAX(taabb.size.y, 0.0001); + t.max_bounds[2] = taabb.position.z + MAX(taabb.size.z, 0.0001); + t.pad0 = t.pad1 = 0; //make valgrind not complain triangles.push_back(t); slice_triangle_count.write[t.slice]++; } @@ -505,9 +501,6 @@ void LightmapperRD::_create_acceleration_structures(RenderingDevice *rd, Size2i Vector<uint8_t> tb = triangles.to_byte_array(); triangle_buffer = rd->storage_buffer_create(tb.size(), tb); - Vector<uint8_t> bb = box_array.to_byte_array(); - box_buffer = rd->storage_buffer_create(bb.size(), bb); - Vector<uint8_t> tib = triangle_indices.to_byte_array(); triangle_cell_indices_buffer = rd->storage_buffer_create(tib.size(), tib); @@ -755,7 +748,6 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d Vector<int> slice_triangle_count; RID vertex_buffer; RID triangle_buffer; - RID box_buffer; RID lights_buffer; RID triangle_cell_indices_buffer; RID grid_texture; @@ -767,14 +759,13 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d #define FREE_BUFFERS \ rd->free(vertex_buffer); \ rd->free(triangle_buffer); \ - rd->free(box_buffer); \ rd->free(lights_buffer); \ rd->free(triangle_cell_indices_buffer); \ rd->free(grid_texture); \ rd->free(seams_buffer); \ rd->free(probe_positions_buffer); - _create_acceleration_structures(rd, atlas_size, atlas_slices, bounds, grid_size, probe_positions, p_generate_probes, slice_triangle_count, slice_seam_count, vertex_buffer, triangle_buffer, box_buffer, lights_buffer, triangle_cell_indices_buffer, probe_positions_buffer, grid_texture, seams_buffer, p_step_function, p_bake_userdata); + _create_acceleration_structures(rd, atlas_size, atlas_slices, bounds, grid_size, probe_positions, p_generate_probes, slice_triangle_count, slice_seam_count, vertex_buffer, triangle_buffer, lights_buffer, triangle_cell_indices_buffer, probe_positions_buffer, grid_texture, seams_buffer, p_step_function, p_bake_userdata); if (p_step_function) { p_step_function(0.47, TTR("Preparing shaders"), p_bake_userdata, true); @@ -828,62 +819,55 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d RD::Uniform u; u.uniform_type = RD::UNIFORM_TYPE_STORAGE_BUFFER; u.binding = 3; - u.ids.push_back(box_buffer); - base_uniforms.push_back(u); - } - { - RD::Uniform u; - u.uniform_type = RD::UNIFORM_TYPE_STORAGE_BUFFER; - u.binding = 4; u.ids.push_back(triangle_cell_indices_buffer); base_uniforms.push_back(u); } { RD::Uniform u; u.uniform_type = RD::UNIFORM_TYPE_STORAGE_BUFFER; - u.binding = 5; + u.binding = 4; u.ids.push_back(lights_buffer); base_uniforms.push_back(u); } { RD::Uniform u; u.uniform_type = RD::UNIFORM_TYPE_STORAGE_BUFFER; - u.binding = 6; + u.binding = 5; u.ids.push_back(seams_buffer); base_uniforms.push_back(u); } { RD::Uniform u; u.uniform_type = RD::UNIFORM_TYPE_STORAGE_BUFFER; - u.binding = 7; + u.binding = 6; u.ids.push_back(probe_positions_buffer); base_uniforms.push_back(u); } { RD::Uniform u; u.uniform_type = RD::UNIFORM_TYPE_TEXTURE; - u.binding = 8; + u.binding = 7; u.ids.push_back(grid_texture); base_uniforms.push_back(u); } { RD::Uniform u; u.uniform_type = RD::UNIFORM_TYPE_TEXTURE; - u.binding = 9; + u.binding = 8; u.ids.push_back(albedo_array_tex); base_uniforms.push_back(u); } { RD::Uniform u; u.uniform_type = RD::UNIFORM_TYPE_TEXTURE; - u.binding = 10; + u.binding = 9; u.ids.push_back(emission_array_tex); base_uniforms.push_back(u); } { RD::Uniform u; u.uniform_type = RD::UNIFORM_TYPE_SAMPLER; - u.binding = 11; + u.binding = 10; u.ids.push_back(sampler); base_uniforms.push_back(u); } diff --git a/modules/lightmapper_rd/lightmapper_rd.h b/modules/lightmapper_rd/lightmapper_rd.h index 7ab7f34464..a6a3740051 100644 --- a/modules/lightmapper_rd/lightmapper_rd.h +++ b/modules/lightmapper_rd/lightmapper_rd.h @@ -157,16 +157,13 @@ class LightmapperRD : public Lightmapper { } }; - struct Box { + struct Triangle { + uint32_t indices[3] = {}; + uint32_t slice = 0; float min_bounds[3] = {}; float pad0 = 0.0; float max_bounds[3] = {}; float pad1 = 0.0; - }; - - struct Triangle { - uint32_t indices[3] = {}; - uint32_t slice = 0; bool operator<(const Triangle &p_triangle) const { return slice < p_triangle.slice; } @@ -231,7 +228,7 @@ class LightmapperRD : public Lightmapper { Vector<Color> probe_values; BakeError _blit_meshes_into_atlas(int p_max_texture_size, Vector<Ref<Image>> &albedo_images, Vector<Ref<Image>> &emission_images, AABB &bounds, Size2i &atlas_size, int &atlas_slices, BakeStepFunc p_step_function, void *p_bake_userdata); - void _create_acceleration_structures(RenderingDevice *rd, Size2i atlas_size, int atlas_slices, AABB &bounds, int grid_size, Vector<Probe> &probe_positions, GenerateProbes p_generate_probes, Vector<int> &slice_triangle_count, Vector<int> &slice_seam_count, RID &vertex_buffer, RID &triangle_buffer, RID &box_buffer, RID &lights_buffer, RID &triangle_cell_indices_buffer, RID &probe_positions_buffer, RID &grid_texture, RID &seams_buffer, BakeStepFunc p_step_function, void *p_bake_userdata); + void _create_acceleration_structures(RenderingDevice *rd, Size2i atlas_size, int atlas_slices, AABB &bounds, int grid_size, Vector<Probe> &probe_positions, GenerateProbes p_generate_probes, Vector<int> &slice_triangle_count, Vector<int> &slice_seam_count, RID &vertex_buffer, RID &triangle_buffer, RID &lights_buffer, RID &triangle_cell_indices_buffer, RID &probe_positions_buffer, RID &grid_texture, RID &seams_buffer, BakeStepFunc p_step_function, void *p_bake_userdata); void _raster_geometry(RenderingDevice *rd, Size2i atlas_size, int atlas_slices, int grid_size, AABB bounds, float p_bias, Vector<int> slice_triangle_count, RID position_tex, RID unocclude_tex, RID normal_tex, RID raster_depth_buffer, RID rasterize_shader, RID raster_base_uniform); public: diff --git a/modules/lightmapper_rd/lm_common_inc.glsl b/modules/lightmapper_rd/lm_common_inc.glsl index 1581639036..22172d50e4 100644 --- a/modules/lightmapper_rd/lm_common_inc.glsl +++ b/modules/lightmapper_rd/lm_common_inc.glsl @@ -16,26 +16,18 @@ vertices; struct Triangle { uvec3 indices; uint slice; -}; - -layout(set = 0, binding = 2, std430) restrict readonly buffer Triangles { - Triangle data[]; -} -triangles; - -struct Box { vec3 min_bounds; uint pad0; vec3 max_bounds; uint pad1; }; -layout(set = 0, binding = 3, std430) restrict readonly buffer Boxes { - Box data[]; +layout(set = 0, binding = 2, std430) restrict readonly buffer Triangles { + Triangle data[]; } -boxes; +triangles; -layout(set = 0, binding = 4, std430) restrict readonly buffer GridIndices { +layout(set = 0, binding = 3, std430) restrict readonly buffer GridIndices { uint data[]; } grid_indices; @@ -63,7 +55,7 @@ struct Light { uint pad[3]; }; -layout(set = 0, binding = 5, std430) restrict readonly buffer Lights { +layout(set = 0, binding = 4, std430) restrict readonly buffer Lights { Light data[]; } lights; @@ -73,19 +65,19 @@ struct Seam { uvec2 b; }; -layout(set = 0, binding = 6, std430) restrict readonly buffer Seams { +layout(set = 0, binding = 5, std430) restrict readonly buffer Seams { Seam data[]; } seams; -layout(set = 0, binding = 7, std430) restrict readonly buffer Probes { +layout(set = 0, binding = 6, std430) restrict readonly buffer Probes { vec4 data[]; } probe_positions; -layout(set = 0, binding = 8) uniform utexture3D grid; +layout(set = 0, binding = 7) uniform utexture3D grid; -layout(set = 0, binding = 9) uniform texture2DArray albedo_tex; -layout(set = 0, binding = 10) uniform texture2DArray emission_tex; +layout(set = 0, binding = 8) uniform texture2DArray albedo_tex; +layout(set = 0, binding = 9) uniform texture2DArray emission_tex; -layout(set = 0, binding = 11) uniform sampler linear_sampler; +layout(set = 0, binding = 10) uniform sampler linear_sampler; diff --git a/modules/lightmapper_rd/lm_compute.glsl b/modules/lightmapper_rd/lm_compute.glsl index 9ca40535f9..a71652d5c4 100644 --- a/modules/lightmapper_rd/lm_compute.glsl +++ b/modules/lightmapper_rd/lm_compute.glsl @@ -160,18 +160,19 @@ bool trace_ray(vec3 p_from, vec3 p_to uint tidx = grid_indices.data[cell_data.y + i]; //Ray-Box test - vec3 t0 = (boxes.data[tidx].min_bounds - p_from) * inv_dir; - vec3 t1 = (boxes.data[tidx].max_bounds - p_from) * inv_dir; + Triangle triangle = triangles.data[tidx]; + vec3 t0 = (triangle.min_bounds - p_from) * inv_dir; + vec3 t1 = (triangle.max_bounds - p_from) * inv_dir; vec3 tmin = min(t0, t1), tmax = max(t0, t1); - if (max(tmin.x, max(tmin.y, tmin.z)) <= min(tmax.x, min(tmax.y, tmax.z))) { + if (max(tmin.x, max(tmin.y, tmin.z)) > min(tmax.x, min(tmax.y, tmax.z))) { continue; //ray box failed } //prepare triangle vertices - vec3 vtx0 = vertices.data[triangles.data[tidx].indices.x].position; - vec3 vtx1 = vertices.data[triangles.data[tidx].indices.y].position; - vec3 vtx2 = vertices.data[triangles.data[tidx].indices.z].position; + vec3 vtx0 = vertices.data[triangle.indices.x].position; + vec3 vtx1 = vertices.data[triangle.indices.y].position; + vec3 vtx2 = vertices.data[triangle.indices.z].position; #if defined(MODE_UNOCCLUDE) vec3 normal = -normalize(cross((vtx0 - vtx1), (vtx0 - vtx2))); diff --git a/modules/minimp3/audio_stream_mp3.cpp b/modules/minimp3/audio_stream_mp3.cpp index fb741f6266..7b52ef178a 100644 --- a/modules/minimp3/audio_stream_mp3.cpp +++ b/modules/minimp3/audio_stream_mp3.cpp @@ -214,6 +214,10 @@ float AudioStreamMP3::get_length() const { return length; } +bool AudioStreamMP3::is_monophonic() const { + return false; +} + void AudioStreamMP3::_bind_methods() { ClassDB::bind_method(D_METHOD("set_data", "data"), &AudioStreamMP3::set_data); ClassDB::bind_method(D_METHOD("get_data"), &AudioStreamMP3::get_data); diff --git a/modules/minimp3/audio_stream_mp3.h b/modules/minimp3/audio_stream_mp3.h index 5dd88779f8..3c8bdd8c53 100644 --- a/modules/minimp3/audio_stream_mp3.h +++ b/modules/minimp3/audio_stream_mp3.h @@ -103,6 +103,8 @@ public: virtual float get_length() const override; + virtual bool is_monophonic() const override; + AudioStreamMP3(); virtual ~AudioStreamMP3(); }; diff --git a/modules/mobile_vr/mobile_vr_interface.cpp b/modules/mobile_vr/mobile_vr_interface.cpp index 12bb0e5b44..bbf1db689d 100644 --- a/modules/mobile_vr/mobile_vr_interface.cpp +++ b/modules/mobile_vr/mobile_vr_interface.cpp @@ -455,16 +455,16 @@ Vector<BlitToScreen> MobileVRInterface::commit_views(RID p_render_target, const blit.lens_distortion.aspect_ratio = aspect; // left eye - blit.rect = p_screen_rect; - blit.rect.size.width *= 0.5; + blit.dst_rect = p_screen_rect; + blit.dst_rect.size.width *= 0.5; blit.multi_view.layer = 0; blit.lens_distortion.eye_center.x = ((-intraocular_dist / 2.0) + (display_width / 4.0)) / (display_width / 2.0); blit_to_screen.push_back(blit); // right eye - blit.rect = p_screen_rect; - blit.rect.size.width *= 0.5; - blit.rect.position.x = blit.rect.size.width; + blit.dst_rect = p_screen_rect; + blit.dst_rect.size.width *= 0.5; + blit.dst_rect.position.x = blit.dst_rect.size.width; blit.multi_view.layer = 1; blit.lens_distortion.eye_center.x = ((intraocular_dist / 2.0) - (display_width / 4.0)) / (display_width / 2.0); blit_to_screen.push_back(blit); diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp index 978093b7d5..fab950019f 100644 --- a/modules/mono/csharp_script.cpp +++ b/modules/mono/csharp_script.cpp @@ -337,7 +337,7 @@ void CSharpLanguage::get_comment_delimiters(List<String> *p_delimiters) const { void CSharpLanguage::get_string_delimiters(List<String> *p_delimiters) const { p_delimiters->push_back("' '"); // character literal p_delimiters->push_back("\" \""); // regular string literal - // Verbatim string literals (`@" "`) don't render correctly, so don't highlight them. + p_delimiters->push_back("@\" \""); // verbatim string literal // Generic string highlighting suffices as a workaround for now. } @@ -1861,6 +1861,28 @@ Variant::Type CSharpInstance::get_property_type(const StringName &p_name, bool * return Variant::NIL; } +void CSharpInstance::get_method_list(List<MethodInfo> *p_list) const { + if (!script->is_valid() || !script->script_class) + return; + + GD_MONO_SCOPE_THREAD_ATTACH; + + // TODO: We're filtering out constructors but there may be other methods unsuitable for explicit calls. + GDMonoClass *top = script->script_class; + + while (top && top != script->native) { + const Vector<GDMonoMethod *> &methods = top->get_all_methods(); + for (int i = 0; i < methods.size(); ++i) { + MethodInfo minfo = methods[i]->get_method_info(); + if (minfo.name != CACHED_STRING_NAME(dotctor)) { + p_list->push_back(minfo); + } + } + + top = top->get_parent_class(); + } +} + bool CSharpInstance::has_method(const StringName &p_method) const { if (!script.is_valid()) { return false; @@ -2124,7 +2146,7 @@ bool CSharpInstance::refcount_decremented() { return ref_dying; } -const Vector<MultiplayerAPI::RPCConfig> CSharpInstance::get_rpc_methods() const { +const Vector<Multiplayer::RPCConfig> CSharpInstance::get_rpc_methods() const { return script->get_rpc_methods(); } @@ -3034,13 +3056,13 @@ void CSharpScript::update_script_class_info(Ref<CSharpScript> p_script) { Vector<GDMonoMethod *> methods = top->get_all_methods(); for (int i = 0; i < methods.size(); i++) { if (!methods[i]->is_static()) { - MultiplayerAPI::RPCMode mode = p_script->_member_get_rpc_mode(methods[i]); - if (MultiplayerAPI::RPC_MODE_DISABLED != mode) { - MultiplayerAPI::RPCConfig nd; + Multiplayer::RPCMode mode = p_script->_member_get_rpc_mode(methods[i]); + if (Multiplayer::RPC_MODE_DISABLED != mode) { + Multiplayer::RPCConfig nd; nd.name = methods[i]->get_name(); nd.rpc_mode = mode; // TODO Transfer mode, channel - nd.transfer_mode = MultiplayerPeer::TRANSFER_MODE_RELIABLE; + nd.transfer_mode = Multiplayer::TRANSFER_MODE_RELIABLE; nd.channel = 0; if (-1 == p_script->rpc_functions.find(nd)) { p_script->rpc_functions.push_back(nd); @@ -3054,7 +3076,7 @@ void CSharpScript::update_script_class_info(Ref<CSharpScript> p_script) { } // Sort so we are 100% that they are always the same. - p_script->rpc_functions.sort_custom<MultiplayerAPI::SortRPCConfig>(); + p_script->rpc_functions.sort_custom<Multiplayer::SortRPCConfig>(); p_script->load_script_signals(p_script->script_class, p_script->native); } @@ -3280,10 +3302,19 @@ void CSharpScript::get_script_method_list(List<MethodInfo> *p_list) const { GD_MONO_SCOPE_THREAD_ATTACH; - // TODO: Filter out things unsuitable for explicit calls, like constructors. - const Vector<GDMonoMethod *> &methods = script_class->get_all_methods(); - for (int i = 0; i < methods.size(); ++i) { - p_list->push_back(methods[i]->get_method_info()); + // TODO: We're filtering out constructors but there may be other methods unsuitable for explicit calls. + GDMonoClass *top = script_class; + + while (top && top != native) { + const Vector<GDMonoMethod *> &methods = top->get_all_methods(); + for (int i = 0; i < methods.size(); ++i) { + MethodInfo minfo = methods[i]->get_method_info(); + if (minfo.name != CACHED_STRING_NAME(dotctor)) { + p_list->push_back(methods[i]->get_method_info()); + } + } + + top = top->get_parent_class(); } } @@ -3464,18 +3495,18 @@ int CSharpScript::get_member_line(const StringName &p_member) const { return -1; } -MultiplayerAPI::RPCMode CSharpScript::_member_get_rpc_mode(IMonoClassMember *p_member) const { +Multiplayer::RPCMode CSharpScript::_member_get_rpc_mode(IMonoClassMember *p_member) const { if (p_member->has_attribute(CACHED_CLASS(RemoteAttribute))) { - return MultiplayerAPI::RPC_MODE_ANY; + return Multiplayer::RPC_MODE_ANY; } if (p_member->has_attribute(CACHED_CLASS(PuppetAttribute))) { - return MultiplayerAPI::RPC_MODE_AUTHORITY; + return Multiplayer::RPC_MODE_AUTHORITY; } - return MultiplayerAPI::RPC_MODE_DISABLED; + return Multiplayer::RPC_MODE_DISABLED; } -const Vector<MultiplayerAPI::RPCConfig> CSharpScript::get_rpc_methods() const { +const Vector<Multiplayer::RPCConfig> CSharpScript::get_rpc_methods() const { return rpc_functions; } diff --git a/modules/mono/csharp_script.h b/modules/mono/csharp_script.h index 4552f376d0..afc17f694a 100644 --- a/modules/mono/csharp_script.h +++ b/modules/mono/csharp_script.h @@ -136,7 +136,7 @@ private: Map<StringName, EventSignal> event_signals; bool signals_invalidated = true; - Vector<MultiplayerAPI::RPCConfig> rpc_functions; + Vector<Multiplayer::RPCConfig> rpc_functions; #ifdef TOOLS_ENABLED List<PropertyInfo> exported_members_cache; // members_cache @@ -179,7 +179,7 @@ private: static void update_script_class_info(Ref<CSharpScript> p_script); static void initialize_for_managed_type(Ref<CSharpScript> p_script, GDMonoClass *p_class, GDMonoClass *p_native); - MultiplayerAPI::RPCMode _member_get_rpc_mode(IMonoClassMember *p_member) const; + Multiplayer::RPCMode _member_get_rpc_mode(IMonoClassMember *p_member) const; protected: static void _bind_methods(); @@ -234,7 +234,7 @@ public: int get_member_line(const StringName &p_member) const override; - const Vector<MultiplayerAPI::RPCConfig> get_rpc_methods() const override; + const Vector<Multiplayer::RPCConfig> get_rpc_methods() const override; #ifdef TOOLS_ENABLED bool is_placeholder_fallback_enabled() const override { return placeholder_fallback_enabled; } @@ -293,7 +293,7 @@ public: void get_property_list(List<PropertyInfo> *p_properties) const override; Variant::Type get_property_type(const StringName &p_name, bool *r_is_valid) const override; - /* TODO */ void get_method_list(List<MethodInfo> *p_list) const override {} + void get_method_list(List<MethodInfo> *p_list) const override; bool has_method(const StringName &p_method) const override; Variant call(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) override; @@ -311,7 +311,7 @@ public: void refcount_incremented() override; bool refcount_decremented() override; - const Vector<MultiplayerAPI::RPCConfig> get_rpc_methods() const override; + const Vector<Multiplayer::RPCConfig> get_rpc_methods() const override; void notification(int p_notification) override; void _call_notification(int p_notification); diff --git a/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotBuildLogger.cs b/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotBuildLogger.cs index 9b7b422276..2bf1cb7a18 100644 --- a/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotBuildLogger.cs +++ b/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotBuildLogger.cs @@ -12,12 +12,16 @@ namespace GodotTools.BuildLogger public string Parameters { get; set; } public LoggerVerbosity Verbosity { get; set; } + private StreamWriter _logStreamWriter; + private StreamWriter _issuesStreamWriter; + private int _indent; + public void Initialize(IEventSource eventSource) { if (null == Parameters) throw new LoggerException("Log directory parameter not specified."); - var parameters = Parameters.Split(new[] { ';' }); + string[] parameters = Parameters.Split(new[] { ';' }); string logDir = parameters[0]; @@ -35,8 +39,8 @@ namespace GodotTools.BuildLogger if (!Directory.Exists(logDir)) Directory.CreateDirectory(logDir); - logStreamWriter = new StreamWriter(logFile); - issuesStreamWriter = new StreamWriter(issuesFile); + _logStreamWriter = new StreamWriter(logFile); + _issuesStreamWriter = new StreamWriter(issuesFile); } catch (Exception ex) { @@ -66,12 +70,12 @@ namespace GodotTools.BuildLogger private void eventSource_ProjectStarted(object sender, ProjectStartedEventArgs e) { WriteLine(e.Message); - indent++; + _indent++; } private void eventSource_ProjectFinished(object sender, ProjectFinishedEventArgs e) { - indent--; + _indent--; WriteLine(e.Message); } @@ -87,7 +91,7 @@ namespace GodotTools.BuildLogger string errorLine = $@"error,{e.File.CsvEscape()},{e.LineNumber},{e.ColumnNumber}," + $"{e.Code?.CsvEscape() ?? string.Empty},{e.Message.CsvEscape()}," + $"{e.ProjectFile?.CsvEscape() ?? string.Empty}"; - issuesStreamWriter.WriteLine(errorLine); + _issuesStreamWriter.WriteLine(errorLine); } private void eventSource_WarningRaised(object sender, BuildWarningEventArgs e) @@ -102,7 +106,7 @@ namespace GodotTools.BuildLogger string warningLine = $@"warning,{e.File.CsvEscape()},{e.LineNumber},{e.ColumnNumber}," + $"{e.Code?.CsvEscape() ?? string.Empty},{e.Message.CsvEscape()}," + $"{e.ProjectFile?.CsvEscape() ?? string.Empty}"; - issuesStreamWriter.WriteLine(warningLine); + _issuesStreamWriter.WriteLine(warningLine); } private void eventSource_MessageRaised(object sender, BuildMessageEventArgs e) @@ -136,28 +140,24 @@ namespace GodotTools.BuildLogger private void WriteLine(string line) { - for (int i = indent; i > 0; i--) + for (int i = _indent; i > 0; i--) { - logStreamWriter.Write("\t"); + _logStreamWriter.Write("\t"); } - logStreamWriter.WriteLine(line); + _logStreamWriter.WriteLine(line); } public void Shutdown() { - logStreamWriter.Close(); - issuesStreamWriter.Close(); + _logStreamWriter.Close(); + _issuesStreamWriter.Close(); } private bool IsVerbosityAtLeast(LoggerVerbosity checkVerbosity) { return Verbosity >= checkVerbosity; } - - private StreamWriter logStreamWriter; - private StreamWriter issuesStreamWriter; - private int indent; } internal static class StringExtensions diff --git a/modules/mono/editor/GodotTools/GodotTools.Core/ProcessExtensions.cs b/modules/mono/editor/GodotTools/GodotTools.Core/ProcessExtensions.cs index 43d40f2ad9..a4d7dedbd5 100644 --- a/modules/mono/editor/GodotTools/GodotTools.Core/ProcessExtensions.cs +++ b/modules/mono/editor/GodotTools/GodotTools.Core/ProcessExtensions.cs @@ -7,7 +7,7 @@ namespace GodotTools.Core { public static class ProcessExtensions { - public static async Task WaitForExitAsync(this Process process, CancellationToken cancellationToken = default(CancellationToken)) + public static async Task WaitForExitAsync(this Process process, CancellationToken cancellationToken = default) { var tcs = new TaskCompletionSource<bool>(); diff --git a/modules/mono/editor/GodotTools/GodotTools.Core/StringExtensions.cs b/modules/mono/editor/GodotTools/GodotTools.Core/StringExtensions.cs index b217ae4bf7..60a4f297c9 100644 --- a/modules/mono/editor/GodotTools/GodotTools.Core/StringExtensions.cs +++ b/modules/mono/editor/GodotTools/GodotTools.Core/StringExtensions.cs @@ -7,6 +7,8 @@ namespace GodotTools.Core { public static class StringExtensions { + private static readonly string _driveRoot = Path.GetPathRoot(Environment.CurrentDirectory); + public static string RelativeToPath(this string path, string dir) { // Make sure the directory ends with a path separator @@ -49,13 +51,11 @@ namespace GodotTools.Core return Path.DirectorySeparatorChar + path; } - private static readonly string DriveRoot = Path.GetPathRoot(Environment.CurrentDirectory); - public static bool IsAbsolutePath(this string path) { return path.StartsWith("/", StringComparison.Ordinal) || path.StartsWith("\\", StringComparison.Ordinal) || - path.StartsWith(DriveRoot, StringComparison.Ordinal); + path.StartsWith(_driveRoot, StringComparison.Ordinal); } public static string ToSafeDirName(this string dirName, bool allowDirSeparator = false) diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/DotNetSolution.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/DotNetSolution.cs index 284e94810a..355b21d63a 100644 --- a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/DotNetSolution.cs +++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/DotNetSolution.cs @@ -9,15 +9,40 @@ namespace GodotTools.ProjectEditor { public class DotNetSolution { - private string directoryPath; - private readonly Dictionary<string, ProjectInfo> projects = new Dictionary<string, ProjectInfo>(); + private const string _solutionTemplate = +@"Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2012 +{0} +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution +{1} + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution +{2} + EndGlobalSection +EndGlobal +"; + + private const string _projectDeclaration = +@"Project(""{{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}}"") = ""{0}"", ""{1}"", ""{{{2}}}"" +EndProject"; + + private const string _solutionPlatformsConfig = +@" {0}|Any CPU = {0}|Any CPU"; + + private const string _projectPlatformsConfig = +@" {{{0}}}.{1}|Any CPU.ActiveCfg = {1}|Any CPU + {{{0}}}.{1}|Any CPU.Build.0 = {1}|Any CPU"; + + private string _directoryPath; + private readonly Dictionary<string, ProjectInfo> _projects = new Dictionary<string, ProjectInfo>(); public string Name { get; } public string DirectoryPath { - get => directoryPath; - set => directoryPath = value.IsAbsolutePath() ? value : Path.GetFullPath(value); + get => _directoryPath; + set => _directoryPath = value.IsAbsolutePath() ? value : Path.GetFullPath(value); } public class ProjectInfo @@ -29,22 +54,22 @@ namespace GodotTools.ProjectEditor public void AddNewProject(string name, ProjectInfo projectInfo) { - projects[name] = projectInfo; + _projects[name] = projectInfo; } public bool HasProject(string name) { - return projects.ContainsKey(name); + return _projects.ContainsKey(name); } public ProjectInfo GetProjectInfo(string name) { - return projects[name]; + return _projects[name]; } public bool RemoveProject(string name) { - return projects.Remove(name); + return _projects.Remove(name); } public void Save() @@ -58,7 +83,7 @@ namespace GodotTools.ProjectEditor bool isFirstProject = true; - foreach (var pair in projects) + foreach (var pair in _projects) { string name = pair.Key; ProjectInfo projectInfo = pair.Value; @@ -66,7 +91,7 @@ namespace GodotTools.ProjectEditor if (!isFirstProject) projectsDecl += "\n"; - projectsDecl += string.Format(ProjectDeclaration, + projectsDecl += string.Format(_projectDeclaration, name, projectInfo.PathRelativeToSolution.Replace("/", "\\"), projectInfo.Guid); for (int i = 0; i < projectInfo.Configs.Count; i++) @@ -79,15 +104,15 @@ namespace GodotTools.ProjectEditor projPlatformsCfg += "\n"; } - slnPlatformsCfg += string.Format(SolutionPlatformsConfig, config); - projPlatformsCfg += string.Format(ProjectPlatformsConfig, projectInfo.Guid, config); + slnPlatformsCfg += string.Format(_solutionPlatformsConfig, config); + projPlatformsCfg += string.Format(_projectPlatformsConfig, projectInfo.Guid, config); } isFirstProject = false; } string solutionPath = Path.Combine(DirectoryPath, Name + ".sln"); - string content = string.Format(SolutionTemplate, projectsDecl, slnPlatformsCfg, projPlatformsCfg); + string content = string.Format(_solutionTemplate, projectsDecl, slnPlatformsCfg, projPlatformsCfg); File.WriteAllText(solutionPath, content, Encoding.UTF8); // UTF-8 with BOM } @@ -97,37 +122,12 @@ namespace GodotTools.ProjectEditor Name = name; } - const string SolutionTemplate = -@"Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 2012 -{0} -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution -{1} - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution -{2} - EndGlobalSection -EndGlobal -"; - - const string ProjectDeclaration = -@"Project(""{{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}}"") = ""{0}"", ""{1}"", ""{{{2}}}"" -EndProject"; - - const string SolutionPlatformsConfig = -@" {0}|Any CPU = {0}|Any CPU"; - - const string ProjectPlatformsConfig = -@" {{{0}}}.{1}|Any CPU.ActiveCfg = {1}|Any CPU - {{{0}}}.{1}|Any CPU.Build.0 = {1}|Any CPU"; - public static void MigrateFromOldConfigNames(string slnPath) { if (!File.Exists(slnPath)) return; - var input = File.ReadAllText(slnPath); + string input = File.ReadAllText(slnPath); if (!Regex.IsMatch(input, Regex.Escape("Tools|Any CPU"))) return; @@ -151,7 +151,7 @@ EndProject"; }; var regex = new Regex(string.Join("|", dict.Keys.Select(Regex.Escape))); - var result = regex.Replace(input, m => dict[m.Value]); + string result = regex.Replace(input, m => dict[m.Value]); if (result != input) { diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/IdentifierUtils.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/IdentifierUtils.cs index ed77076df3..31363df9ef 100644 --- a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/IdentifierUtils.cs +++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/IdentifierUtils.cs @@ -91,7 +91,7 @@ namespace GodotTools.ProjectEditor return identifier; } - static bool IsKeyword(string value, bool anyDoubleUnderscore) + private static bool IsKeyword(string value, bool anyDoubleUnderscore) { // Identifiers that start with double underscore are meant to be used for reserved keywords. // Only existing keywords are enforced, but it may be useful to forbid any identifier @@ -103,14 +103,14 @@ namespace GodotTools.ProjectEditor } else { - if (DoubleUnderscoreKeywords.Contains(value)) + if (_doubleUnderscoreKeywords.Contains(value)) return true; } - return Keywords.Contains(value); + return _keywords.Contains(value); } - private static readonly HashSet<string> DoubleUnderscoreKeywords = new HashSet<string> + private static readonly HashSet<string> _doubleUnderscoreKeywords = new HashSet<string> { "__arglist", "__makeref", @@ -118,7 +118,7 @@ namespace GodotTools.ProjectEditor "__refvalue", }; - private static readonly HashSet<string> Keywords = new HashSet<string> + private static readonly HashSet<string> _keywords = new HashSet<string> { "as", "do", diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildInfo.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildInfo.cs index 27737c3da0..28bf57dc21 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/BuildInfo.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildInfo.cs @@ -13,7 +13,8 @@ namespace GodotTools.Build public string[] Targets { get; } public string Configuration { get; } public bool Restore { get; } - public Array<string> CustomProperties { get; } = new Array<string>(); // TODO Use List once we have proper serialization + // TODO Use List once we have proper serialization + public Array<string> CustomProperties { get; } = new Array<string>(); public string LogsDirPath => Path.Combine(GodotSharpDirs.BuildLogsDirs, $"{Solution.MD5Text()}_{Configuration}"); @@ -32,12 +33,12 @@ namespace GodotTools.Build unchecked { int hash = 17; - hash = hash * 29 + Solution.GetHashCode(); - hash = hash * 29 + Targets.GetHashCode(); - hash = hash * 29 + Configuration.GetHashCode(); - hash = hash * 29 + Restore.GetHashCode(); - hash = hash * 29 + CustomProperties.GetHashCode(); - hash = hash * 29 + LogsDirPath.GetHashCode(); + hash = (hash * 29) + Solution.GetHashCode(); + hash = (hash * 29) + Targets.GetHashCode(); + hash = (hash * 29) + Configuration.GetHashCode(); + hash = (hash * 29) + Restore.GetHashCode(); + hash = (hash * 29) + CustomProperties.GetHashCode(); + hash = (hash * 29) + LogsDirPath.GetHashCode(); return hash; } } diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs index 2b6f972529..21bff70b15 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs @@ -32,7 +32,7 @@ namespace GodotTools.Build private static void RemoveOldIssuesFile(BuildInfo buildInfo) { - var issuesFile = GetIssuesFilePath(buildInfo); + string issuesFile = GetIssuesFilePath(buildInfo); if (!File.Exists(issuesFile)) return; diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildOutputView.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildOutputView.cs index 25e260beed..b53347fc4c 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/BuildOutputView.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildOutputView.cs @@ -22,14 +22,6 @@ namespace GodotTools.Build public string ProjectFile { get; set; } } - private readonly Array<BuildIssue> issues = new Array<BuildIssue>(); // TODO Use List once we have proper serialization - private ItemList issuesList; - private TextEdit buildLog; - private PopupMenu issuesListContextMenu; - - private readonly object pendingBuildLogTextLock = new object(); - [NotNull] private string pendingBuildLogText = string.Empty; - [Signal] public event Action BuildStateChanged; public bool HasBuildExited { get; private set; } = false; @@ -60,13 +52,21 @@ namespace GodotTools.Build } } - private BuildInfo BuildInfo { get; set; } - public bool LogVisible { - set => buildLog.Visible = value; + set => _buildLog.Visible = value; } + // TODO Use List once we have proper serialization. + private readonly Array<BuildIssue> _issues = new Array<BuildIssue>(); + private ItemList _issuesList; + private PopupMenu _issuesListContextMenu; + private TextEdit _buildLog; + private BuildInfo _buildInfo; + + private readonly object _pendingBuildLogTextLock = new object(); + [NotNull] private string _pendingBuildLogText = string.Empty; + private void LoadIssuesFromFile(string csvFile) { using (var file = new Godot.File()) @@ -107,7 +107,7 @@ namespace GodotTools.Build else ErrorCount += 1; - issues.Add(issue); + _issues.Add(issue); } } finally @@ -119,21 +119,21 @@ namespace GodotTools.Build private void IssueActivated(int idx) { - if (idx < 0 || idx >= issuesList.GetItemCount()) + if (idx < 0 || idx >= _issuesList.GetItemCount()) throw new IndexOutOfRangeException("Item list index out of range"); // Get correct issue idx from issue list - int issueIndex = (int)(long)issuesList.GetItemMetadata(idx); + int issueIndex = (int)(long)_issuesList.GetItemMetadata(idx); - if (issueIndex < 0 || issueIndex >= issues.Count) + if (issueIndex < 0 || issueIndex >= _issues.Count) throw new IndexOutOfRangeException("Issue index out of range"); - BuildIssue issue = issues[issueIndex]; + BuildIssue issue = _issues[issueIndex]; if (string.IsNullOrEmpty(issue.ProjectFile) && string.IsNullOrEmpty(issue.File)) return; - string projectDir = issue.ProjectFile.Length > 0 ? issue.ProjectFile.GetBaseDir() : BuildInfo.Solution.GetBaseDir(); + string projectDir = issue.ProjectFile.Length > 0 ? issue.ProjectFile.GetBaseDir() : _buildInfo.Solution.GetBaseDir(); string file = Path.Combine(projectDir.SimplifyGodotPath(), issue.File.SimplifyGodotPath()); @@ -153,14 +153,14 @@ namespace GodotTools.Build public void UpdateIssuesList() { - issuesList.Clear(); + _issuesList.Clear(); using (var warningIcon = GetThemeIcon("Warning", "EditorIcons")) using (var errorIcon = GetThemeIcon("Error", "EditorIcons")) { - for (int i = 0; i < issues.Count; i++) + for (int i = 0; i < _issues.Count; i++) { - BuildIssue issue = issues[i]; + BuildIssue issue = _issues[i]; if (!(issue.Warning ? WarningsVisible : ErrorsVisible)) continue; @@ -191,11 +191,11 @@ namespace GodotTools.Build int lineBreakIdx = text.IndexOf("\n", StringComparison.Ordinal); string itemText = lineBreakIdx == -1 ? text : text.Substring(0, lineBreakIdx); - issuesList.AddItem(itemText, issue.Warning ? warningIcon : errorIcon); + _issuesList.AddItem(itemText, issue.Warning ? warningIcon : errorIcon); - int index = issuesList.GetItemCount() - 1; - issuesList.SetItemTooltip(index, tooltip); - issuesList.SetItemMetadata(index, i); + int index = _issuesList.GetItemCount() - 1; + _issuesList.SetItemTooltip(index, tooltip); + _issuesList.SetItemMetadata(index, i); } } } @@ -205,12 +205,12 @@ namespace GodotTools.Build HasBuildExited = true; BuildResult = Build.BuildResult.Error; - issuesList.Clear(); + _issuesList.Clear(); var issue = new BuildIssue {Message = cause, Warning = false}; ErrorCount += 1; - issues.Add(issue); + _issues.Add(issue); UpdateIssuesList(); @@ -219,13 +219,13 @@ namespace GodotTools.Build private void BuildStarted(BuildInfo buildInfo) { - BuildInfo = buildInfo; + _buildInfo = buildInfo; HasBuildExited = false; - issues.Clear(); + _issues.Clear(); WarningCount = 0; ErrorCount = 0; - buildLog.Text = string.Empty; + _buildLog.Text = string.Empty; UpdateIssuesList(); @@ -237,7 +237,7 @@ namespace GodotTools.Build HasBuildExited = true; BuildResult = result; - LoadIssuesFromFile(Path.Combine(BuildInfo.LogsDirPath, BuildManager.MsBuildIssuesFileName)); + LoadIssuesFromFile(Path.Combine(_buildInfo.LogsDirPath, BuildManager.MsBuildIssuesFileName)); UpdateIssuesList(); @@ -246,46 +246,46 @@ namespace GodotTools.Build private void UpdateBuildLogText() { - lock (pendingBuildLogTextLock) + lock (_pendingBuildLogTextLock) { - buildLog.Text += pendingBuildLogText; - pendingBuildLogText = string.Empty; + _buildLog.Text += _pendingBuildLogText; + _pendingBuildLogText = string.Empty; ScrollToLastNonEmptyLogLine(); } } private void StdOutputReceived(string text) { - lock (pendingBuildLogTextLock) + lock (_pendingBuildLogTextLock) { - if (pendingBuildLogText.Length == 0) + if (_pendingBuildLogText.Length == 0) CallDeferred(nameof(UpdateBuildLogText)); - pendingBuildLogText += text + "\n"; + _pendingBuildLogText += text + "\n"; } } private void StdErrorReceived(string text) { - lock (pendingBuildLogTextLock) + lock (_pendingBuildLogTextLock) { - if (pendingBuildLogText.Length == 0) + if (_pendingBuildLogText.Length == 0) CallDeferred(nameof(UpdateBuildLogText)); - pendingBuildLogText += text + "\n"; + _pendingBuildLogText += text + "\n"; } } private void ScrollToLastNonEmptyLogLine() { int line; - for (line = buildLog.GetLineCount(); line > 0; line--) + for (line = _buildLog.GetLineCount(); line > 0; line--) { - string lineText = buildLog.GetLine(line); + string lineText = _buildLog.GetLine(line); if (!string.IsNullOrEmpty(lineText) || !string.IsNullOrEmpty(lineText?.Trim())) break; } - buildLog.SetCaretLine(line); + _buildLog.SetCaretLine(line); } public void RestartBuild() @@ -318,11 +318,11 @@ namespace GodotTools.Build // We don't allow multi-selection but just in case that changes later... string text = null; - foreach (int issueIndex in issuesList.GetSelectedItems()) + foreach (int issueIndex in _issuesList.GetSelectedItems()) { if (text != null) text += "\n"; - text += issuesList.GetItemText(issueIndex); + text += _issuesList.GetItemText(issueIndex); } if (text != null) @@ -338,20 +338,20 @@ namespace GodotTools.Build { _ = index; // Unused - issuesListContextMenu.Clear(); - issuesListContextMenu.Size = new Vector2i(1, 1); + _issuesListContextMenu.Clear(); + _issuesListContextMenu.Size = new Vector2i(1, 1); - if (issuesList.IsAnythingSelected()) + if (_issuesList.IsAnythingSelected()) { // Add menu entries for the selected item - issuesListContextMenu.AddIconItem(GetThemeIcon("ActionCopy", "EditorIcons"), + _issuesListContextMenu.AddIconItem(GetThemeIcon("ActionCopy", "EditorIcons"), label: "Copy Error".TTR(), (int)IssuesContextMenuOption.Copy); } - if (issuesListContextMenu.GetItemCount() > 0) + if (_issuesListContextMenu.GetItemCount() > 0) { - issuesListContextMenu.Position = (Vector2i)(issuesList.RectGlobalPosition + atPosition); - issuesListContextMenu.Popup(); + _issuesListContextMenu.Position = (Vector2i)(_issuesList.RectGlobalPosition + atPosition); + _issuesListContextMenu.Popup(); } } @@ -368,27 +368,27 @@ namespace GodotTools.Build }; AddChild(hsc); - issuesList = new ItemList + _issuesList = new ItemList { SizeFlagsVertical = (int)SizeFlags.ExpandFill, SizeFlagsHorizontal = (int)SizeFlags.ExpandFill // Avoid being squashed by the build log }; - issuesList.ItemActivated += IssueActivated; - issuesList.AllowRmbSelect = true; - issuesList.ItemRmbSelected += IssuesListRmbSelected; - hsc.AddChild(issuesList); + _issuesList.ItemActivated += IssueActivated; + _issuesList.AllowRmbSelect = true; + _issuesList.ItemRmbSelected += IssuesListRmbSelected; + hsc.AddChild(_issuesList); - issuesListContextMenu = new PopupMenu(); - issuesListContextMenu.IdPressed += IssuesListContextOptionPressed; - issuesList.AddChild(issuesListContextMenu); + _issuesListContextMenu = new PopupMenu(); + _issuesListContextMenu.IdPressed += IssuesListContextOptionPressed; + _issuesList.AddChild(_issuesListContextMenu); - buildLog = new TextEdit + _buildLog = new TextEdit { Editable = false, SizeFlagsVertical = (int)SizeFlags.ExpandFill, SizeFlagsHorizontal = (int)SizeFlags.ExpandFill // Avoid being squashed by the issues list }; - hsc.AddChild(buildLog); + hsc.AddChild(_buildLog); AddBuildEventListeners(); } diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs b/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs index 5f35d506de..e9cf7911be 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs @@ -11,10 +11,10 @@ namespace GodotTools.Build { public BuildOutputView BuildOutputView { get; private set; } - private MenuButton buildMenuBtn; - private Button errorsBtn; - private Button warningsBtn; - private Button viewLogBtn; + private MenuButton _buildMenuBtn; + private Button _errorsBtn; + private Button _warningsBtn; + private Button _viewLogBtn; private void WarningsToggled(bool pressed) { @@ -132,16 +132,16 @@ namespace GodotTools.Build var toolBarHBox = new HBoxContainer { SizeFlagsHorizontal = (int)SizeFlags.ExpandFill }; AddChild(toolBarHBox); - buildMenuBtn = new MenuButton { Text = "Build", Icon = GetThemeIcon("Play", "EditorIcons") }; - toolBarHBox.AddChild(buildMenuBtn); + _buildMenuBtn = new MenuButton { Text = "Build", Icon = GetThemeIcon("Play", "EditorIcons") }; + toolBarHBox.AddChild(_buildMenuBtn); - var buildMenu = buildMenuBtn.GetPopup(); + var buildMenu = _buildMenuBtn.GetPopup(); buildMenu.AddItem("Build Solution".TTR(), (int)BuildMenuOptions.BuildSolution); buildMenu.AddItem("Rebuild Solution".TTR(), (int)BuildMenuOptions.RebuildSolution); buildMenu.AddItem("Clean Solution".TTR(), (int)BuildMenuOptions.CleanSolution); buildMenu.IdPressed += BuildMenuOptionPressed; - errorsBtn = new Button + _errorsBtn = new Button { HintTooltip = "Show Errors".TTR(), Icon = GetThemeIcon("StatusError", "EditorIcons"), @@ -150,10 +150,10 @@ namespace GodotTools.Build Pressed = true, FocusMode = FocusModeEnum.None }; - errorsBtn.Toggled += ErrorsToggled; - toolBarHBox.AddChild(errorsBtn); + _errorsBtn.Toggled += ErrorsToggled; + toolBarHBox.AddChild(_errorsBtn); - warningsBtn = new Button + _warningsBtn = new Button { HintTooltip = "Show Warnings".TTR(), Icon = GetThemeIcon("NodeWarning", "EditorIcons"), @@ -162,18 +162,18 @@ namespace GodotTools.Build Pressed = true, FocusMode = FocusModeEnum.None }; - warningsBtn.Toggled += WarningsToggled; - toolBarHBox.AddChild(warningsBtn); + _warningsBtn.Toggled += WarningsToggled; + toolBarHBox.AddChild(_warningsBtn); - viewLogBtn = new Button + _viewLogBtn = new Button { Text = "Show Output".TTR(), ToggleMode = true, Pressed = true, FocusMode = FocusModeEnum.None }; - viewLogBtn.Toggled += ViewLogToggled; - toolBarHBox.AddChild(viewLogBtn); + _viewLogBtn.Toggled += ViewLogToggled; + toolBarHBox.AddChild(_viewLogBtn); BuildOutputView = new BuildOutputView(); AddChild(BuildOutputView); @@ -185,12 +185,12 @@ namespace GodotTools.Build if (what == NotificationThemeChanged) { - if (buildMenuBtn != null) - buildMenuBtn.Icon = GetThemeIcon("Play", "EditorIcons"); - if (errorsBtn != null) - errorsBtn.Icon = GetThemeIcon("StatusError", "EditorIcons"); - if (warningsBtn != null) - warningsBtn.Icon = GetThemeIcon("NodeWarning", "EditorIcons"); + if (_buildMenuBtn != null) + _buildMenuBtn.Icon = GetThemeIcon("Play", "EditorIcons"); + if (_errorsBtn != null) + _errorsBtn.Icon = GetThemeIcon("StatusError", "EditorIcons"); + if (_warningsBtn != null) + _warningsBtn.Icon = GetThemeIcon("NodeWarning", "EditorIcons"); } } } diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs b/modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs index 774c49e705..a859c6f717 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs @@ -61,7 +61,7 @@ namespace GodotTools.Build } case BuildTool.JetBrainsMsBuild: { - var editorPath = (string)editorSettings.GetSetting(RiderPathManager.EditorPathSettingName); + string editorPath = (string)editorSettings.GetSetting(RiderPathManager.EditorPathSettingName); if (!File.Exists(editorPath)) throw new FileNotFoundException($"Cannot find Rider executable. Tried with path: {editorPath}"); @@ -165,7 +165,9 @@ namespace GodotTools.Build // Try to find 15.0 with vswhere - var envNames = Internal.GodotIs32Bits() ? new[] {"ProgramFiles", "ProgramW6432"} : new[] {"ProgramFiles(x86)", "ProgramFiles"}; + string[] envNames = Internal.GodotIs32Bits() ? + envNames = new[] { "ProgramFiles", "ProgramW6432" } : + envNames = new[] { "ProgramFiles(x86)", "ProgramFiles" }; string vsWherePath = null; foreach (var envName in envNames) diff --git a/modules/mono/editor/GodotTools/GodotTools/Export/AotBuilder.cs b/modules/mono/editor/GodotTools/GodotTools/Export/AotBuilder.cs index f69307104f..37e6a34977 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Export/AotBuilder.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Export/AotBuilder.cs @@ -65,12 +65,12 @@ namespace GodotTools.Export if (platform == OS.Platforms.iOS) { - var architectures = GetEnablediOSArchs(features).ToArray(); + string[] architectures = GetEnablediOSArchs(features).ToArray(); CompileAssembliesForiOS(exporter, isDebug, architectures, aotOpts, aotTempDir, assembliesPrepared, bclDir); } else if (platform == OS.Platforms.Android) { - var abis = GetEnabledAndroidAbis(features).ToArray(); + string[] abis = GetEnabledAndroidAbis(features).ToArray(); CompileAssembliesForAndroid(exporter, isDebug, abis, aotOpts, aotTempDir, assembliesPrepared, bclDir); } else @@ -138,7 +138,8 @@ namespace GodotTools.Export } else { - string outputDataLibDir = Path.Combine(outputDataDir, "Mono", platform == OS.Platforms.Windows ? "bin" : "lib"); + string libDir = platform == OS.Platforms.Windows ? "bin" : "lib"; + string outputDataLibDir = Path.Combine(outputDataDir, "Mono", libDir); File.Copy(tempOutputFilePath, Path.Combine(outputDataLibDir, outputFileName)); } } diff --git a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs index 0b5aa72a81..3e46a89b7c 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs @@ -20,7 +20,7 @@ namespace GodotTools.Export public class ExportPlugin : EditorExportPlugin { [Flags] - enum I18NCodesets : long + private enum I18NCodesets : long { None = 0, CJK = 1, @@ -31,6 +31,8 @@ namespace GodotTools.Export All = CJK | MidEast | Other | Rare | West } + private string _maybeLastExportError; + private void AddI18NAssemblies(Godot.Collections.Dictionary<string, string> assemblies, string bclDir) { var codesets = (I18NCodesets)ProjectSettings.GetSetting("mono/export/i18n_codesets"); @@ -83,8 +85,6 @@ namespace GodotTools.Export GlobalDef("mono/export/aot/android_toolchain_path", ""); } - private string maybeLastExportError; - private void AddFile(string srcPath, string dstPath, bool remap = false) { // Add file to the PCK @@ -129,14 +129,14 @@ namespace GodotTools.Export } catch (Exception e) { - maybeLastExportError = e.Message; + _maybeLastExportError = e.Message; // 'maybeLastExportError' cannot be null or empty if there was an error, so we // must consider the possibility of exceptions being thrown without a message. - if (string.IsNullOrEmpty(maybeLastExportError)) - maybeLastExportError = $"Exception thrown: {e.GetType().Name}"; + if (string.IsNullOrEmpty(_maybeLastExportError)) + _maybeLastExportError = $"Exception thrown: {e.GetType().Name}"; - GD.PushError($"Failed to export project: {maybeLastExportError}"); + GD.PushError($"Failed to export project: {_maybeLastExportError}"); Console.Error.WriteLine(e); // TODO: Do something on error once _ExportBegin supports failing. } @@ -317,10 +317,10 @@ namespace GodotTools.Export Directory.Delete(aotTempDir, recursive: true); // TODO: Just a workaround until the export plugins can be made to abort with errors - if (!string.IsNullOrEmpty(maybeLastExportError)) // Check empty as well, because it's set to empty after hot-reloading + if (!string.IsNullOrEmpty(_maybeLastExportError)) // Check empty as well, because it's set to empty after hot-reloading { - string lastExportError = maybeLastExportError; - maybeLastExportError = null; + string lastExportError = _maybeLastExportError; + _maybeLastExportError = null; GodotSharpEditor.Instance.ShowErrorDialog(lastExportError, "Failed to export C# project"); } @@ -470,7 +470,7 @@ namespace GodotTools.Export private static string DetermineDataDirNameForProject() { - var appName = (string)ProjectSettings.GetSetting("application/config/name"); + string appName = (string)ProjectSettings.GetSetting("application/config/name"); string appNameSafe = appName.ToSafeDirName(); return $"data_{appNameSafe}"; } diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs index 1faa6eeac0..73cabf8561 100644 --- a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs +++ b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs @@ -21,18 +21,19 @@ namespace GodotTools { public class GodotSharpEditor : EditorPlugin, ISerializationListener { - private EditorSettings editorSettings; + private EditorSettings _editorSettings; - private PopupMenu menuPopup; + private PopupMenu _menuPopup; - private AcceptDialog errorDialog; + private AcceptDialog _errorDialog; - private Button bottomPanelBtn; - private Button toolBarBuildButton; + private Button _bottomPanelBtn; + private Button _toolBarBuildButton; - public GodotIdeManager GodotIdeManager { get; private set; } + // TODO Use WeakReference once we have proper serialization. + private WeakRef _exportPluginWeak; - private WeakRef exportPluginWeak; // TODO Use WeakReference once we have proper serialization + public GodotIdeManager GodotIdeManager { get; private set; } public MSBuildPanel MSBuildPanel { get; private set; } @@ -42,7 +43,7 @@ namespace GodotTools { get { - var projectAssemblyName = (string)ProjectSettings.GetSetting("application/config/name"); + string projectAssemblyName = (string)ProjectSettings.GetSetting("application/config/name"); projectAssemblyName = projectAssemblyName.ToSafeDirName(); if (string.IsNullOrEmpty(projectAssemblyName)) projectAssemblyName = "UnnamedProject"; @@ -123,9 +124,9 @@ namespace GodotTools private void _RemoveCreateSlnMenuOption() { - menuPopup.RemoveItem(menuPopup.GetItemIndex((int)MenuOptions.CreateSln)); - bottomPanelBtn.Show(); - toolBarBuildButton.Show(); + _menuPopup.RemoveItem(_menuPopup.GetItemIndex((int)MenuOptions.CreateSln)); + _bottomPanelBtn.Show(); + _toolBarBuildButton.Show(); } private void _MenuOptionPressed(int id) @@ -181,9 +182,9 @@ namespace GodotTools public void ShowErrorDialog(string message, string title = "Error") { - errorDialog.Title = title; - errorDialog.DialogText = message; - errorDialog.PopupCentered(); + _errorDialog.Title = title; + _errorDialog.DialogText = message; + _errorDialog.PopupCentered(); } private static string _vsCodePath = string.Empty; @@ -196,7 +197,7 @@ namespace GodotTools [UsedImplicitly] public Error OpenInExternalEditor(Script script, int line, int col) { - var editorId = (ExternalEditorId)editorSettings.GetSetting("mono/editor/external_editor"); + var editorId = (ExternalEditorId)_editorSettings.GetSetting("mono/editor/external_editor"); switch (editorId) { @@ -287,7 +288,7 @@ namespace GodotTools } } - var resourcePath = ProjectSettings.GlobalizePath("res://"); + string resourcePath = ProjectSettings.GlobalizePath("res://"); args.Add(resourcePath); string scriptPath = ProjectSettings.GlobalizePath(script.ResourcePath); @@ -346,7 +347,7 @@ namespace GodotTools [UsedImplicitly] public bool OverridesExternalEditor() { - return (ExternalEditorId)editorSettings.GetSetting("mono/editor/external_editor") != ExternalEditorId.None; + return (ExternalEditorId)_editorSettings.GetSetting("mono/editor/external_editor") != ExternalEditorId.None; } public override bool _Build() @@ -387,8 +388,8 @@ namespace GodotTools private void BuildStateChanged() { - if (bottomPanelBtn != null) - bottomPanelBtn.Icon = MSBuildPanel.BuildOutputView.BuildStateIcon; + if (_bottomPanelBtn != null) + _bottomPanelBtn.Icon = MSBuildPanel.BuildOutputView.BuildStateIcon; } public override void _EnablePlugin() @@ -402,29 +403,29 @@ namespace GodotTools var editorInterface = GetEditorInterface(); var editorBaseControl = editorInterface.GetBaseControl(); - editorSettings = editorInterface.GetEditorSettings(); + _editorSettings = editorInterface.GetEditorSettings(); - errorDialog = new AcceptDialog(); - editorBaseControl.AddChild(errorDialog); + _errorDialog = new AcceptDialog(); + editorBaseControl.AddChild(_errorDialog); MSBuildPanel = new MSBuildPanel(); - bottomPanelBtn = AddControlToBottomPanel(MSBuildPanel, "MSBuild".TTR()); + _bottomPanelBtn = AddControlToBottomPanel(MSBuildPanel, "MSBuild".TTR()); AddChild(new HotReloadAssemblyWatcher {Name = "HotReloadAssemblyWatcher"}); - menuPopup = new PopupMenu(); - menuPopup.Hide(); + _menuPopup = new PopupMenu(); + _menuPopup.Hide(); - AddToolSubmenuItem("C#", menuPopup); + AddToolSubmenuItem("C#", _menuPopup); - toolBarBuildButton = new Button + _toolBarBuildButton = new Button { Text = "Build", HintTooltip = "Build solution", FocusMode = Control.FocusModeEnum.None }; - toolBarBuildButton.PressedSignal += BuildSolutionPressed; - AddControlToContainer(CustomControlContainer.Toolbar, toolBarBuildButton); + _toolBarBuildButton.PressedSignal += BuildSolutionPressed; + AddControlToContainer(CustomControlContainer.Toolbar, _toolBarBuildButton); if (File.Exists(GodotSharpDirs.ProjectSlnPath) && File.Exists(GodotSharpDirs.ProjectCsProjPath)) { @@ -432,12 +433,12 @@ namespace GodotTools } else { - bottomPanelBtn.Hide(); - toolBarBuildButton.Hide(); - menuPopup.AddItem("Create C# solution".TTR(), (int)MenuOptions.CreateSln); + _bottomPanelBtn.Hide(); + _toolBarBuildButton.Hide(); + _menuPopup.AddItem("Create C# solution".TTR(), (int)MenuOptions.CreateSln); } - menuPopup.IdPressed += _MenuOptionPressed; + _menuPopup.IdPressed += _MenuOptionPressed; // External editor settings EditorDef("mono/editor/external_editor", ExternalEditorId.None); @@ -465,7 +466,7 @@ namespace GodotTools $",JetBrains Rider:{(int)ExternalEditorId.Rider}"; } - editorSettings.AddPropertyInfo(new Godot.Collections.Dictionary + _editorSettings.AddPropertyInfo(new Godot.Collections.Dictionary { ["type"] = Variant.Type.Int, ["name"] = "mono/editor/external_editor", @@ -477,7 +478,7 @@ namespace GodotTools var exportPlugin = new ExportPlugin(); AddExportPlugin(exportPlugin); exportPlugin.RegisterExportSettings(); - exportPluginWeak = WeakRef(exportPlugin); + _exportPluginWeak = WeakRef(exportPlugin); try { @@ -500,15 +501,15 @@ namespace GodotTools { base.Dispose(disposing); - if (exportPluginWeak != null) + if (_exportPluginWeak != null) { // We need to dispose our export plugin before the editor destroys EditorSettings. // Otherwise, if the GC disposes it at a later time, EditorExportPlatformAndroid // will be freed after EditorSettings already was, and its device polling thread // will try to access the EditorSettings singleton, resulting in null dereferencing. - (exportPluginWeak.GetRef() as ExportPlugin)?.Dispose(); + (_exportPluginWeak.GetRef() as ExportPlugin)?.Dispose(); - exportPluginWeak.Dispose(); + _exportPluginWeak.Dispose(); } GodotIdeManager?.Dispose(); diff --git a/modules/mono/editor/GodotTools/GodotTools/HotReloadAssemblyWatcher.cs b/modules/mono/editor/GodotTools/GodotTools/HotReloadAssemblyWatcher.cs index b30c857c64..dd05f28af0 100644 --- a/modules/mono/editor/GodotTools/GodotTools/HotReloadAssemblyWatcher.cs +++ b/modules/mono/editor/GodotTools/GodotTools/HotReloadAssemblyWatcher.cs @@ -6,7 +6,7 @@ namespace GodotTools { public class HotReloadAssemblyWatcher : Node { - private Timer watchTimer; + private Timer _watchTimer; public override void _Notification(int what) { @@ -27,22 +27,22 @@ namespace GodotTools public void RestartTimer() { - watchTimer.Stop(); - watchTimer.Start(); + _watchTimer.Stop(); + _watchTimer.Start(); } public override void _Ready() { base._Ready(); - watchTimer = new Timer + _watchTimer = new Timer { OneShot = false, WaitTime = (float)EditorDef("mono/assembly_watch_interval_sec", 0.5) }; - watchTimer.Timeout += TimerTimeout; - AddChild(watchTimer); - watchTimer.Start(); + _watchTimer.Timeout += TimerTimeout; + AddChild(_watchTimer); + _watchTimer.Start(); } } } diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs index 451ce39f5c..23339fe50b 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs @@ -10,22 +10,22 @@ namespace GodotTools.Ides { public sealed class GodotIdeManager : Node, ISerializationListener { - private MessagingServer MessagingServer { get; set; } + private MessagingServer _messagingServer; - private MonoDevelop.Instance monoDevelInstance; - private MonoDevelop.Instance vsForMacInstance; + private MonoDevelop.Instance _monoDevelInstance; + private MonoDevelop.Instance _vsForMacInstance; private MessagingServer GetRunningOrNewServer() { - if (MessagingServer != null && !MessagingServer.IsDisposed) - return MessagingServer; + if (_messagingServer != null && !_messagingServer.IsDisposed) + return _messagingServer; - MessagingServer?.Dispose(); - MessagingServer = new MessagingServer(OS.GetExecutablePath(), ProjectSettings.GlobalizePath(GodotSharpDirs.ResMetadataDir), new GodotLogger()); + _messagingServer?.Dispose(); + _messagingServer = new MessagingServer(OS.GetExecutablePath(), ProjectSettings.GlobalizePath(GodotSharpDirs.ResMetadataDir), new GodotLogger()); - _ = MessagingServer.Listen(); + _ = _messagingServer.Listen(); - return MessagingServer; + return _messagingServer; } public override void _Ready() @@ -48,7 +48,7 @@ namespace GodotTools.Ides if (disposing) { - MessagingServer?.Dispose(); + _messagingServer?.Dispose(); } } @@ -113,14 +113,14 @@ namespace GodotTools.Ides { if (Utils.OS.IsMacOS && editorId == ExternalEditorId.VisualStudioForMac) { - vsForMacInstance = (vsForMacInstance?.IsDisposed ?? true ? null : vsForMacInstance) ?? + _vsForMacInstance = (_vsForMacInstance?.IsDisposed ?? true ? null : _vsForMacInstance) ?? new MonoDevelop.Instance(solutionPath, MonoDevelop.EditorId.VisualStudioForMac); - return vsForMacInstance; + return _vsForMacInstance; } - monoDevelInstance = (monoDevelInstance?.IsDisposed ?? true ? null : monoDevelInstance) ?? + _monoDevelInstance = (_monoDevelInstance?.IsDisposed ?? true ? null : _monoDevelInstance) ?? new MonoDevelop.Instance(solutionPath, MonoDevelop.EditorId.MonoDevelop); - return monoDevelInstance; + return _monoDevelInstance; } try @@ -159,15 +159,15 @@ namespace GodotTools.Ides public readonly struct EditorPick { - private readonly string identity; + private readonly string _identity; public EditorPick(string identity) { - this.identity = identity; + _identity = identity; } public bool IsAnyConnected() => - GodotSharpEditor.Instance.GodotIdeManager.GetRunningOrNewServer().IsAnyConnected(identity); + GodotSharpEditor.Instance.GodotIdeManager.GetRunningOrNewServer().IsAnyConnected(_identity); private void SendRequest<TResponse>(Request request) where TResponse : Response, new() @@ -175,7 +175,7 @@ namespace GodotTools.Ides // Logs an error if no client is connected with the specified identity GodotSharpEditor.Instance.GodotIdeManager .GetRunningOrNewServer() - .BroadcastRequest<TResponse>(identity, request); + .BroadcastRequest<TResponse>(_identity, request); } public void SendOpenFile(string file) diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/MessagingServer.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/MessagingServer.cs index eb34a2d0f7..6f11831b80 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Ides/MessagingServer.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Ides/MessagingServer.cs @@ -21,24 +21,26 @@ namespace GodotTools.Ides { public sealed class MessagingServer : IDisposable { - private readonly ILogger logger; + private readonly ILogger _logger; - private readonly FileStream metaFile; - private string MetaFilePath { get; } + private readonly FileStream _metaFile; + private string _metaFilePath; - private readonly SemaphoreSlim peersSem = new SemaphoreSlim(1); + private readonly SemaphoreSlim _peersSem = new SemaphoreSlim(1); - private readonly TcpListener listener; + private readonly TcpListener _listener; - private readonly Dictionary<string, Queue<NotifyAwaiter<bool>>> clientConnectedAwaiters = new Dictionary<string, Queue<NotifyAwaiter<bool>>>(); - private readonly Dictionary<string, Queue<NotifyAwaiter<bool>>> clientDisconnectedAwaiters = new Dictionary<string, Queue<NotifyAwaiter<bool>>>(); + private readonly Dictionary<string, Queue<NotifyAwaiter<bool>>> _clientConnectedAwaiters = + new Dictionary<string, Queue<NotifyAwaiter<bool>>>(); + private readonly Dictionary<string, Queue<NotifyAwaiter<bool>>> _clientDisconnectedAwaiters = + new Dictionary<string, Queue<NotifyAwaiter<bool>>>(); public async Task<bool> AwaitClientConnected(string identity) { - if (!clientConnectedAwaiters.TryGetValue(identity, out var queue)) + if (!_clientConnectedAwaiters.TryGetValue(identity, out var queue)) { queue = new Queue<NotifyAwaiter<bool>>(); - clientConnectedAwaiters.Add(identity, queue); + _clientConnectedAwaiters.Add(identity, queue); } var awaiter = new NotifyAwaiter<bool>(); @@ -48,10 +50,10 @@ namespace GodotTools.Ides public async Task<bool> AwaitClientDisconnected(string identity) { - if (!clientDisconnectedAwaiters.TryGetValue(identity, out var queue)) + if (!_clientDisconnectedAwaiters.TryGetValue(identity, out var queue)) { queue = new Queue<NotifyAwaiter<bool>>(); - clientDisconnectedAwaiters.Add(identity, queue); + _clientDisconnectedAwaiters.Add(identity, queue); } var awaiter = new NotifyAwaiter<bool>(); @@ -77,7 +79,7 @@ namespace GodotTools.Ides if (IsDisposed) return; - using (await peersSem.UseAsync()) + using (await _peersSem.UseAsync()) { if (IsDisposed) // lock may not be fair return; @@ -95,19 +97,19 @@ namespace GodotTools.Ides foreach (var connection in Peers) connection.Dispose(); Peers.Clear(); - listener?.Stop(); + _listener?.Stop(); - metaFile?.Dispose(); + _metaFile?.Dispose(); - File.Delete(MetaFilePath); + File.Delete(_metaFilePath); } } public MessagingServer(string editorExecutablePath, string projectMetadataDir, ILogger logger) { - this.logger = logger; + this._logger = logger; - MetaFilePath = Path.Combine(projectMetadataDir, GodotIdeMetadata.DefaultFileName); + _metaFilePath = Path.Combine(projectMetadataDir, GodotIdeMetadata.DefaultFileName); // Make sure the directory exists Directory.CreateDirectory(projectMetadataDir); @@ -115,13 +117,13 @@ namespace GodotTools.Ides // The Godot editor's file system thread can keep the file open for writing, so we are forced to allow write sharing... const FileShare metaFileShare = FileShare.ReadWrite; - metaFile = File.Open(MetaFilePath, FileMode.Create, FileAccess.Write, metaFileShare); + _metaFile = File.Open(_metaFilePath, FileMode.Create, FileAccess.Write, metaFileShare); - listener = new TcpListener(new IPEndPoint(IPAddress.Loopback, port: 0)); - listener.Start(); + _listener = new TcpListener(new IPEndPoint(IPAddress.Loopback, port: 0)); + _listener.Start(); - int port = ((IPEndPoint)listener.Server.LocalEndPoint).Port; - using (var metaFileWriter = new StreamWriter(metaFile, Encoding.UTF8)) + int port = ((IPEndPoint)_listener.Server.LocalEndPoint).Port; + using (var metaFileWriter = new StreamWriter(_metaFile, Encoding.UTF8)) { metaFileWriter.WriteLine(port); metaFileWriter.WriteLine(editorExecutablePath); @@ -130,30 +132,30 @@ namespace GodotTools.Ides private async Task AcceptClient(TcpClient tcpClient) { - logger.LogDebug("Accept client..."); + _logger.LogDebug("Accept client..."); - using (var peer = new Peer(tcpClient, new ServerHandshake(), new ServerMessageHandler(), logger)) + using (var peer = new Peer(tcpClient, new ServerHandshake(), new ServerMessageHandler(), _logger)) { // ReSharper disable AccessToDisposedClosure peer.Connected += () => { - logger.LogInfo("Connection open with Ide Client"); + _logger.LogInfo("Connection open with Ide Client"); - if (clientConnectedAwaiters.TryGetValue(peer.RemoteIdentity, out var queue)) + if (_clientConnectedAwaiters.TryGetValue(peer.RemoteIdentity, out var queue)) { while (queue.Count > 0) queue.Dequeue().SetResult(true); - clientConnectedAwaiters.Remove(peer.RemoteIdentity); + _clientConnectedAwaiters.Remove(peer.RemoteIdentity); } }; peer.Disconnected += () => { - if (clientDisconnectedAwaiters.TryGetValue(peer.RemoteIdentity, out var queue)) + if (_clientDisconnectedAwaiters.TryGetValue(peer.RemoteIdentity, out var queue)) { while (queue.Count > 0) queue.Dequeue().SetResult(true); - clientDisconnectedAwaiters.Remove(peer.RemoteIdentity); + _clientDisconnectedAwaiters.Remove(peer.RemoteIdentity); } }; // ReSharper restore AccessToDisposedClosure @@ -162,17 +164,17 @@ namespace GodotTools.Ides { if (!await peer.DoHandshake("server")) { - logger.LogError("Handshake failed"); + _logger.LogError("Handshake failed"); return; } } catch (Exception e) { - logger.LogError("Handshake failed with unhandled exception: ", e); + _logger.LogError("Handshake failed with unhandled exception: ", e); return; } - using (await peersSem.UseAsync()) + using (await _peersSem.UseAsync()) Peers.Add(peer); try @@ -181,7 +183,7 @@ namespace GodotTools.Ides } finally { - using (await peersSem.UseAsync()) + using (await _peersSem.UseAsync()) Peers.Remove(peer); } } @@ -192,7 +194,7 @@ namespace GodotTools.Ides try { while (!IsDisposed) - _ = AcceptClient(await listener.AcceptTcpClientAsync()); + _ = AcceptClient(await _listener.AcceptTcpClientAsync()); } catch (Exception e) { @@ -204,11 +206,11 @@ namespace GodotTools.Ides public async void BroadcastRequest<TResponse>(string identity, Request request) where TResponse : Response, new() { - using (await peersSem.UseAsync()) + using (await _peersSem.UseAsync()) { if (!IsAnyConnected(identity)) { - logger.LogError("Cannot write request. No client connected to the Godot Ide Server."); + _logger.LogError("Cannot write request. No client connected to the Godot Ide Server."); return; } @@ -225,16 +227,19 @@ namespace GodotTools.Ides private class ServerHandshake : IHandshake { - private static readonly string ServerHandshakeBase = $"{Peer.ServerHandshakeName},Version={Peer.ProtocolVersionMajor}.{Peer.ProtocolVersionMinor}.{Peer.ProtocolVersionRevision}"; - private static readonly string ClientHandshakePattern = $@"{Regex.Escape(Peer.ClientHandshakeName)},Version=([0-9]+)\.([0-9]+)\.([0-9]+),([_a-zA-Z][_a-zA-Z0-9]{{0,63}})"; + private static readonly string _serverHandshakeBase = + $"{Peer.ServerHandshakeName},Version={Peer.ProtocolVersionMajor}.{Peer.ProtocolVersionMinor}.{Peer.ProtocolVersionRevision}"; - public string GetHandshakeLine(string identity) => $"{ServerHandshakeBase},{identity}"; + private static readonly string _clientHandshakePattern = + $@"{Regex.Escape(Peer.ClientHandshakeName)},Version=([0-9]+)\.([0-9]+)\.([0-9]+),([_a-zA-Z][_a-zA-Z0-9]{{0,63}})"; + + public string GetHandshakeLine(string identity) => $"{_serverHandshakeBase},{identity}"; public bool IsValidPeerHandshake(string handshake, out string identity, ILogger logger) { identity = null; - var match = Regex.Match(handshake, ClientHandshakePattern); + var match = Regex.Match(handshake, _clientHandshakePattern); if (!match.Success) return false; diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/MonoDevelop/Instance.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/MonoDevelop/Instance.cs index fd7bbd5578..3f1d5ac3ca 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Ides/MonoDevelop/Instance.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Ides/MonoDevelop/Instance.cs @@ -10,17 +10,17 @@ namespace GodotTools.Ides.MonoDevelop public class Instance : IDisposable { public DateTime LaunchTime { get; private set; } - private readonly string solutionFile; - private readonly EditorId editorId; + private readonly string _solutionFile; + private readonly EditorId _editorId; - private Process process; + private Process _process; - public bool IsRunning => process != null && !process.HasExited; + public bool IsRunning => _process != null && !_process.HasExited; public bool IsDisposed { get; private set; } public void Execute() { - bool newWindow = process == null || process.HasExited; + bool newWindow = _process == null || _process.HasExited; var args = new List<string>(); @@ -28,7 +28,7 @@ namespace GodotTools.Ides.MonoDevelop if (OS.IsMacOS) { - string bundleId = BundleIds[editorId]; + string bundleId = BundleIds[_editorId]; if (Internal.IsOsxAppBundleInstalled(bundleId)) { @@ -45,18 +45,18 @@ namespace GodotTools.Ides.MonoDevelop } else { - command = OS.PathWhich(ExecutableNames[editorId]); + command = OS.PathWhich(ExecutableNames[_editorId]); } } else { - command = OS.PathWhich(ExecutableNames[editorId]); + command = OS.PathWhich(ExecutableNames[_editorId]); } args.Add("--ipc-tcp"); if (newWindow) - args.Add("\"" + Path.GetFullPath(solutionFile) + "\""); + args.Add("\"" + Path.GetFullPath(_solutionFile) + "\""); if (command == null) throw new FileNotFoundException(); @@ -65,7 +65,7 @@ namespace GodotTools.Ides.MonoDevelop if (newWindow) { - process = Process.Start(new ProcessStartInfo + _process = Process.Start(new ProcessStartInfo { FileName = command, Arguments = string.Join(" ", args), @@ -88,14 +88,14 @@ namespace GodotTools.Ides.MonoDevelop if (editorId == EditorId.VisualStudioForMac && !OS.IsMacOS) throw new InvalidOperationException($"{nameof(EditorId.VisualStudioForMac)} not supported on this platform"); - this.solutionFile = solutionFile; - this.editorId = editorId; + _solutionFile = solutionFile; + _editorId = editorId; } public void Dispose() { IsDisposed = true; - process?.Dispose(); + _process?.Dispose(); } private static readonly IReadOnlyDictionary<EditorId, string> ExecutableNames; diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathLocator.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathLocator.cs index 821532f759..71055f0125 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathLocator.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathLocator.cs @@ -11,6 +11,7 @@ using Environment = System.Environment; using File = System.IO.File; using Path = System.IO.Path; using OS = GodotTools.Utils.OS; + // ReSharper disable UnassignedField.Local // ReSharper disable InconsistentNaming // ReSharper disable UnassignedField.Global @@ -53,10 +54,10 @@ namespace GodotTools.Ides.Rider private static RiderInfo[] CollectAllRiderPathsLinux() { var installInfos = new List<RiderInfo>(); - var home = Environment.GetEnvironmentVariable("HOME"); + string home = Environment.GetEnvironmentVariable("HOME"); if (!string.IsNullOrEmpty(home)) { - var toolboxRiderRootPath = GetToolboxBaseDir(); + string toolboxRiderRootPath = GetToolboxBaseDir(); installInfos.AddRange(CollectPathsFromToolbox(toolboxRiderRootPath, "bin", "rider.sh", false) .Select(a => new RiderInfo(a, true)).ToList()); @@ -65,12 +66,12 @@ namespace GodotTools.Ides.Rider if (shortcut.Exists) { - var lines = File.ReadAllLines(shortcut.FullName); - foreach (var line in lines) + string[] lines = File.ReadAllLines(shortcut.FullName); + foreach (string line in lines) { if (!line.StartsWith("Exec=\"")) continue; - var path = line.Split('"').Where((item, index) => index == 1).SingleOrDefault(); + string path = line.Split('"').Where((item, index) => index == 1).SingleOrDefault(); if (string.IsNullOrEmpty(path)) continue; @@ -82,7 +83,7 @@ namespace GodotTools.Ides.Rider } // snap install - var snapInstallPath = "/snap/rider/current/bin/rider.sh"; + string snapInstallPath = "/snap/rider/current/bin/rider.sh"; if (new FileInfo(snapInstallPath).Exists) installInfos.Add(new RiderInfo(snapInstallPath, false)); @@ -98,15 +99,15 @@ namespace GodotTools.Ides.Rider if (folder.Exists) { installInfos.AddRange(folder.GetDirectories("*Rider*.app") - .Select(a => new RiderInfo(Path.Combine(a.FullName, "Contents/MacOS/rider"), false)) - .ToList()); + .Select(a => new RiderInfo(Path.Combine(a.FullName, "Contents/MacOS/rider"), false)) + .ToList()); } // /Users/user/Library/Application Support/JetBrains/Toolbox/apps/Rider/ch-1/181.3870.267/Rider EAP.app // should be combined with "Contents/MacOS/rider" - var toolboxRiderRootPath = GetToolboxBaseDir(); + string toolboxRiderRootPath = GetToolboxBaseDir(); var paths = CollectPathsFromToolbox(toolboxRiderRootPath, "", "Rider*.app", true) - .Select(a => new RiderInfo(Path.Combine(a, "Contents/MacOS/rider"), true)); + .Select(a => new RiderInfo(Path.Combine(a, "Contents/MacOS/rider"), true)); installInfos.AddRange(paths); return installInfos.ToArray(); @@ -134,7 +135,7 @@ namespace GodotTools.Ides.Rider { if (OS.IsWindows) { - var localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); + string localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); return GetToolboxRiderRootPath(localAppData); } diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathManager.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathManager.cs index 60dd565ef2..ac29efb716 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathManager.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathManager.cs @@ -49,7 +49,7 @@ namespace GodotTools.Ides.Rider if (!paths.Any()) return; - var newPath = paths.Last().Path; + string newPath = paths.Last().Path; Globals.EditorDef(EditorPathSettingName, newPath); editorSettings.SetSetting(EditorPathSettingName, newPath); } @@ -57,7 +57,8 @@ namespace GodotTools.Ides.Rider public static bool IsExternalEditorSetToRider(EditorSettings editorSettings) { - return editorSettings.HasSetting(EditorPathSettingName) && IsRider((string)editorSettings.GetSetting(EditorPathSettingName)); + return editorSettings.HasSetting(EditorPathSettingName) && + IsRider((string)editorSettings.GetSetting(EditorPathSettingName)); } public static bool IsRider(string path) @@ -66,7 +67,7 @@ namespace GodotTools.Ides.Rider return false; var fileInfo = new FileInfo(path); - var filename = fileInfo.Name.ToLowerInvariant(); + string filename = fileInfo.Name.ToLowerInvariant(); return filename.StartsWith("rider", StringComparison.Ordinal); } @@ -83,7 +84,7 @@ namespace GodotTools.Ides.Rider if (!paths.Any()) return null; - var newPath = paths.Last().Path; + string newPath = paths.Last().Path; editorSettings.SetSetting(EditorPathSettingName, newPath); Globals.EditorDef(EditorPathSettingName, newPath); return newPath; @@ -96,8 +97,8 @@ namespace GodotTools.Ides.Rider public static void OpenFile(string slnPath, string scriptPath, int line) { - var pathFromSettings = GetRiderPathFromSettings(); - var path = CheckAndUpdatePath(pathFromSettings); + string pathFromSettings = GetRiderPathFromSettings(); + string path = CheckAndUpdatePath(pathFromSettings); var args = new List<string>(); args.Add(slnPath); diff --git a/modules/mono/editor/GodotTools/GodotTools/Internals/GodotSharpDirs.cs b/modules/mono/editor/GodotTools/GodotTools/Internals/GodotSharpDirs.cs index 6893bc1974..5e70c399b2 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Internals/GodotSharpDirs.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Internals/GodotSharpDirs.cs @@ -39,45 +39,57 @@ namespace GodotTools.Internals [MethodImpl(MethodImplOptions.InternalCall)] private static extern string internal_ResDataDir(); + [MethodImpl(MethodImplOptions.InternalCall)] private static extern string internal_ResMetadataDir(); + [MethodImpl(MethodImplOptions.InternalCall)] private static extern string internal_ResAssembliesBaseDir(); + [MethodImpl(MethodImplOptions.InternalCall)] private static extern string internal_ResAssembliesDir(); + [MethodImpl(MethodImplOptions.InternalCall)] private static extern string internal_ResConfigDir(); + [MethodImpl(MethodImplOptions.InternalCall)] private static extern string internal_ResTempDir(); + [MethodImpl(MethodImplOptions.InternalCall)] private static extern string internal_ResTempAssembliesBaseDir(); + [MethodImpl(MethodImplOptions.InternalCall)] private static extern string internal_ResTempAssembliesDir(); [MethodImpl(MethodImplOptions.InternalCall)] private static extern string internal_MonoUserDir(); + [MethodImpl(MethodImplOptions.InternalCall)] private static extern string internal_MonoLogsDir(); #region Tools-only [MethodImpl(MethodImplOptions.InternalCall)] private static extern string internal_MonoSolutionsDir(); + [MethodImpl(MethodImplOptions.InternalCall)] private static extern string internal_BuildLogsDirs(); [MethodImpl(MethodImplOptions.InternalCall)] private static extern string internal_ProjectSlnPath(); + [MethodImpl(MethodImplOptions.InternalCall)] private static extern string internal_ProjectCsProjPath(); [MethodImpl(MethodImplOptions.InternalCall)] private static extern string internal_DataEditorToolsDir(); + [MethodImpl(MethodImplOptions.InternalCall)] private static extern string internal_DataEditorPrebuiltApiDir(); #endregion [MethodImpl(MethodImplOptions.InternalCall)] private static extern string internal_DataMonoEtcDir(); + [MethodImpl(MethodImplOptions.InternalCall)] private static extern string internal_DataMonoLibDir(); diff --git a/modules/mono/editor/GodotTools/GodotTools/Utils/FsPathUtils.cs b/modules/mono/editor/GodotTools/GodotTools/Utils/FsPathUtils.cs index c6724ccaf7..05499339b1 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Utils/FsPathUtils.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Utils/FsPathUtils.cs @@ -8,7 +8,7 @@ namespace GodotTools.Utils { public static class FsPathUtils { - private static readonly string ResourcePath = ProjectSettings.GlobalizePath("res://"); + private static readonly string _resourcePath = ProjectSettings.GlobalizePath("res://"); private static bool PathStartsWithAlreadyNorm(this string childPath, string parentPath) { @@ -34,7 +34,7 @@ namespace GodotTools.Utils public static string LocalizePathWithCaseChecked(string path) { string pathNorm = path.NormalizePath() + Path.DirectorySeparatorChar; - string resourcePathNorm = ResourcePath.NormalizePath() + Path.DirectorySeparatorChar; + string resourcePathNorm = _resourcePath.NormalizePath() + Path.DirectorySeparatorChar; if (!pathNorm.PathStartsWithAlreadyNorm(resourcePathNorm)) return null; diff --git a/modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs b/modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs index 4624439665..93a1360cb6 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs @@ -13,10 +13,10 @@ namespace GodotTools.Utils public static class OS { [MethodImpl(MethodImplOptions.InternalCall)] - static extern string GetPlatformName(); + private static extern string GetPlatformName(); [MethodImpl(MethodImplOptions.InternalCall)] - static extern bool UnixFileHasExecutableAccess(string filePath); + private static extern bool UnixFileHasExecutableAccess(string filePath); public static class Names { @@ -106,7 +106,10 @@ namespace GodotTools.Utils public static string PathWhich([NotNull] string name) { - return IsWindows ? PathWhichWindows(name) : PathWhichUnix(name); + if (IsWindows) + return PathWhichWindows(name); + + return PathWhichUnix(name); } private static string PathWhichWindows([NotNull] string name) @@ -129,7 +132,8 @@ namespace GodotTools.Utils } string nameExt = Path.GetExtension(name); - bool hasPathExt = !string.IsNullOrEmpty(nameExt) && windowsExts.Contains(nameExt, StringComparer.OrdinalIgnoreCase); + bool hasPathExt = !string.IsNullOrEmpty(nameExt) && + windowsExts.Contains(nameExt, StringComparer.OrdinalIgnoreCase); searchDirs.Add(System.IO.Directory.GetCurrentDirectory()); // last in the list diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp index 7fdef8ff45..e03c5fd248 100644 --- a/modules/mono/editor/bindings_generator.cpp +++ b/modules/mono/editor/bindings_generator.cpp @@ -387,7 +387,7 @@ String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterf xml_output.append(link_target); xml_output.append("</c>"); } - } else if (link_tag == "const") { + } else if (link_tag == "constant") { if (!target_itype || !target_itype->is_object_type) { if (OS::get_singleton()->is_stdout_verbose()) { if (target_itype) { @@ -2610,7 +2610,7 @@ bool BindingsGenerator::_populate_object_type_interfaces() { Map<StringName, StringName> accessor_methods; for (const PropertyInfo &property : property_list) { - if (property.usage & PROPERTY_USAGE_GROUP || property.usage & PROPERTY_USAGE_SUBGROUP || property.usage & PROPERTY_USAGE_CATEGORY) { + if (property.usage & PROPERTY_USAGE_GROUP || property.usage & PROPERTY_USAGE_SUBGROUP || property.usage & PROPERTY_USAGE_CATEGORY || (property.type == Variant::NIL && property.usage & PROPERTY_USAGE_ARRAY)) { continue; } diff --git a/modules/mono/editor/bindings_generator.h b/modules/mono/editor/bindings_generator.h index 8a85a1acbd..51a27ee934 100644 --- a/modules/mono/editor/bindings_generator.h +++ b/modules/mono/editor/bindings_generator.h @@ -575,7 +575,7 @@ class BindingsGenerator { StaticCString::create(_STR(PackedByteArray)), StaticCString::create(_STR(PackedInt32Array)), - StaticCString::create(_STR(PackedInt64rray)), + StaticCString::create(_STR(PackedInt64Array)), StaticCString::create(_STR(PackedFloat32Array)), StaticCString::create(_STR(PackedFloat64Array)), StaticCString::create(_STR(PackedStringArray)), diff --git a/modules/mono/editor/code_completion.cpp b/modules/mono/editor/code_completion.cpp index d911f6461c..7433c865f5 100644 --- a/modules/mono/editor/code_completion.cpp +++ b/modules/mono/editor/code_completion.cpp @@ -123,8 +123,8 @@ PackedStringArray get_code_completion(CompletionKind p_kind, const String &p_scr // AutoLoads OrderedHashMap<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list(); - for (const KeyValue<StringName, ProjectSettings::AutoloadInfo> &E : autoloads) { - const ProjectSettings::AutoloadInfo &info = E.value; + for (OrderedHashMap<StringName, ProjectSettings::AutoloadInfo>::Element E = autoloads.front(); E; E = E.next()) { + const ProjectSettings::AutoloadInfo &info = E.value(); suggestions.push_back(quoted("/root/" + String(info.name))); } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/AABB.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/AABB.cs index 1a3b81487f..8b12537f7f 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/AABB.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/AABB.cs @@ -20,7 +20,7 @@ namespace Godot private Vector3 _size; /// <summary> - /// Beginning corner. Typically has values lower than End. + /// Beginning corner. Typically has values lower than <see cref="End"/>. /// </summary> /// <value>Directly uses a private field.</value> public Vector3 Position @@ -30,7 +30,7 @@ namespace Godot } /// <summary> - /// Size from Position to End. Typically all components are positive. + /// Size from <see cref="Position"/> to <see cref="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> @@ -44,7 +44,10 @@ namespace Godot /// 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> + /// <value> + /// Getting is equivalent to <paramref name="value"/> = <see cref="Position"/> + <see cref="Size"/>, + /// setting is equivalent to <see cref="Size"/> = <paramref name="value"/> - <see cref="Position"/> + /// </value> public Vector3 End { get { return _position + _size; } @@ -52,10 +55,10 @@ namespace Godot } /// <summary> - /// Returns an AABB with equivalent position and size, modified so that + /// Returns an <see cref="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> + /// <returns>The modified <see cref="AABB"/>.</returns> public AABB Abs() { Vector3 end = End; @@ -64,30 +67,32 @@ namespace Godot } /// <summary> - /// Returns true if this AABB completely encloses another one. + /// Returns <see langword="true"/> if this <see cref="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> + /// <param name="with">The other <see cref="AABB"/> that may be enclosed.</param> + /// <returns> + /// A <see langword="bool"/> for whether or not this <see cref="AABB"/> encloses <paramref name="with"/>. + /// </returns> public bool Encloses(AABB with) { - Vector3 src_min = _position; - Vector3 src_max = _position + _size; - Vector3 dst_min = with._position; - Vector3 dst_max = with._position + with._size; + Vector3 srcMin = _position; + Vector3 srcMax = _position + _size; + Vector3 dstMin = with._position; + Vector3 dstMax = with._position + with._size; - return src_min.x <= dst_min.x && - src_max.x > dst_max.x && - src_min.y <= dst_min.y && - src_max.y > dst_max.y && - src_min.z <= dst_min.z && - src_max.z > dst_max.z; + return srcMin.x <= dstMin.x && + srcMax.x > dstMax.x && + srcMin.y <= dstMin.y && + srcMax.y > dstMax.y && + srcMin.z <= dstMin.z && + srcMax.z > dstMax.z; } /// <summary> - /// Returns this AABB expanded to include a given point. + /// Returns this <see cref="AABB"/> expanded to include a given point. /// </summary> /// <param name="point">The point to include.</param> - /// <returns>The expanded AABB.</returns> + /// <returns>The expanded <see cref="AABB"/>.</returns> public AABB Expand(Vector3 point) { Vector3 begin = _position; @@ -123,7 +128,7 @@ namespace Godot } /// <summary> - /// Returns the area of the AABB. + /// Returns the area of the <see cref="AABB"/>. /// </summary> /// <returns>The area.</returns> public real_t GetArea() @@ -132,10 +137,10 @@ namespace Godot } /// <summary> - /// Gets the position of one of the 8 endpoints of the AABB. + /// Gets the position of one of the 8 endpoints of the <see cref="AABB"/>. /// </summary> /// <param name="idx">Which endpoint to get.</param> - /// <returns>An endpoint of the AABB.</returns> + /// <returns>An endpoint of the <see cref="AABB"/>.</returns> public Vector3 GetEndpoint(int idx) { switch (idx) @@ -157,26 +162,29 @@ namespace Godot case 7: return new Vector3(_position.x + _size.x, _position.y + _size.y, _position.z + _size.z); default: - throw new ArgumentOutOfRangeException(nameof(idx), String.Format("Index is {0}, but a value from 0 to 7 is expected.", idx)); + { + throw new ArgumentOutOfRangeException(nameof(idx), + $"Index is {idx}, but a value from 0 to 7 is expected."); + } } } /// <summary> - /// Returns the normalized longest axis of the AABB. + /// Returns the normalized longest axis of the <see cref="AABB"/>. /// </summary> - /// <returns>A vector representing the normalized longest axis of the AABB.</returns> + /// <returns>A vector representing the normalized longest axis of the <see cref="AABB"/>.</returns> public Vector3 GetLongestAxis() { var axis = new Vector3(1f, 0f, 0f); - real_t max_size = _size.x; + real_t maxSize = _size.x; - if (_size.y > max_size) + if (_size.y > maxSize) { axis = new Vector3(0f, 1f, 0f); - max_size = _size.y; + maxSize = _size.y; } - if (_size.z > max_size) + if (_size.z > maxSize) { axis = new Vector3(0f, 0f, 1f); } @@ -185,21 +193,21 @@ namespace Godot } /// <summary> - /// Returns the <see cref="Vector3.Axis"/> index of the longest axis of the AABB. + /// Returns the <see cref="Vector3.Axis"/> index of the longest axis of the <see cref="AABB"/>. /// </summary> /// <returns>A <see cref="Vector3.Axis"/> index for which axis is longest.</returns> public Vector3.Axis GetLongestAxisIndex() { var axis = Vector3.Axis.X; - real_t max_size = _size.x; + real_t maxSize = _size.x; - if (_size.y > max_size) + if (_size.y > maxSize) { axis = Vector3.Axis.Y; - max_size = _size.y; + maxSize = _size.y; } - if (_size.z > max_size) + if (_size.z > maxSize) { axis = Vector3.Axis.Z; } @@ -208,38 +216,38 @@ namespace Godot } /// <summary> - /// Returns the scalar length of the longest axis of the AABB. + /// Returns the scalar length of the longest axis of the <see cref="AABB"/>. /// </summary> - /// <returns>The scalar length of the longest axis of the AABB.</returns> + /// <returns>The scalar length of the longest axis of the <see cref="AABB"/>.</returns> public real_t GetLongestAxisSize() { - real_t max_size = _size.x; + real_t maxSize = _size.x; - if (_size.y > max_size) - max_size = _size.y; + if (_size.y > maxSize) + maxSize = _size.y; - if (_size.z > max_size) - max_size = _size.z; + if (_size.z > maxSize) + maxSize = _size.z; - return max_size; + return maxSize; } /// <summary> - /// Returns the normalized shortest axis of the AABB. + /// Returns the normalized shortest axis of the <see cref="AABB"/>. /// </summary> - /// <returns>A vector representing the normalized shortest axis of the AABB.</returns> + /// <returns>A vector representing the normalized shortest axis of the <see cref="AABB"/>.</returns> public Vector3 GetShortestAxis() { var axis = new Vector3(1f, 0f, 0f); - real_t max_size = _size.x; + real_t maxSize = _size.x; - if (_size.y < max_size) + if (_size.y < maxSize) { axis = new Vector3(0f, 1f, 0f); - max_size = _size.y; + maxSize = _size.y; } - if (_size.z < max_size) + if (_size.z < maxSize) { axis = new Vector3(0f, 0f, 1f); } @@ -248,21 +256,21 @@ namespace Godot } /// <summary> - /// Returns the <see cref="Vector3.Axis"/> index of the shortest axis of the AABB. + /// Returns the <see cref="Vector3.Axis"/> index of the shortest axis of the <see cref="AABB"/>. /// </summary> /// <returns>A <see cref="Vector3.Axis"/> index for which axis is shortest.</returns> public Vector3.Axis GetShortestAxisIndex() { var axis = Vector3.Axis.X; - real_t max_size = _size.x; + real_t maxSize = _size.x; - if (_size.y < max_size) + if (_size.y < maxSize) { axis = Vector3.Axis.Y; - max_size = _size.y; + maxSize = _size.y; } - if (_size.z < max_size) + if (_size.z < maxSize) { axis = Vector3.Axis.Z; } @@ -271,20 +279,20 @@ namespace Godot } /// <summary> - /// Returns the scalar length of the shortest axis of the AABB. + /// Returns the scalar length of the shortest axis of the <see cref="AABB"/>. /// </summary> - /// <returns>The scalar length of the shortest axis of the AABB.</returns> + /// <returns>The scalar length of the shortest axis of the <see cref="AABB"/>.</returns> public real_t GetShortestAxisSize() { - real_t max_size = _size.x; + real_t maxSize = _size.x; - if (_size.y < max_size) - max_size = _size.y; + if (_size.y < maxSize) + maxSize = _size.y; - if (_size.z < max_size) - max_size = _size.z; + if (_size.z < maxSize) + maxSize = _size.z; - return max_size; + return maxSize; } /// <summary> @@ -295,23 +303,23 @@ namespace Godot /// <returns>A vector representing the support.</returns> public Vector3 GetSupport(Vector3 dir) { - Vector3 half_extents = _size * 0.5f; - Vector3 ofs = _position + half_extents; + Vector3 halfExtents = _size * 0.5f; + Vector3 ofs = _position + halfExtents; return ofs + new Vector3( - dir.x > 0f ? -half_extents.x : half_extents.x, - dir.y > 0f ? -half_extents.y : half_extents.y, - dir.z > 0f ? -half_extents.z : half_extents.z); + dir.x > 0f ? -halfExtents.x : halfExtents.x, + dir.y > 0f ? -halfExtents.y : halfExtents.y, + dir.z > 0f ? -halfExtents.z : halfExtents.z); } /// <summary> - /// Returns a copy of the AABB grown a given amount of units towards all the sides. + /// Returns a copy of the <see cref="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> + /// <returns>The grown <see cref="AABB"/>.</returns> public AABB Grow(real_t by) { - var res = this; + AABB res = this; res._position.x -= by; res._position.y -= by; @@ -324,28 +332,37 @@ namespace Godot } /// <summary> - /// Returns true if the AABB is flat or empty, or false otherwise. + /// Returns <see langword="true"/> if the <see cref="AABB"/> is flat or empty, + /// or <see langword="false"/> otherwise. /// </summary> - /// <returns>A bool for whether or not the AABB has area.</returns> + /// <returns> + /// A <see langword="bool"/> for whether or not the <see cref="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. + /// Returns <see langword="true"/> if the <see cref="AABB"/> has no surface (no size), + /// or <see langword="false"/> otherwise. /// </summary> - /// <returns>A bool for whether or not the AABB has area.</returns> + /// <returns> + /// A <see langword="bool"/> for whether or not the <see cref="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. + /// Returns <see langword="true"/> if the <see cref="AABB"/> contains a point, + /// or <see langword="false"/> otherwise. /// </summary> /// <param name="point">The point to check.</param> - /// <returns>A bool for whether or not the AABB contains `point`.</returns> + /// <returns> + /// A <see langword="bool"/> for whether or not the <see cref="AABB"/> contains <paramref name="point"/>. + /// </returns> public bool HasPoint(Vector3 point) { if (point.x < _position.x) @@ -365,56 +382,59 @@ namespace Godot } /// <summary> - /// Returns the intersection of this AABB and `b`. + /// Returns the intersection of this <see cref="AABB"/> and <paramref name="with"/>. /// </summary> - /// <param name="with">The other AABB.</param> - /// <returns>The clipped AABB.</returns> + /// <param name="with">The other <see cref="AABB"/>.</param> + /// <returns>The clipped <see cref="AABB"/>.</returns> public AABB Intersection(AABB with) { - Vector3 src_min = _position; - Vector3 src_max = _position + _size; - Vector3 dst_min = with._position; - Vector3 dst_max = with._position + with._size; + Vector3 srcMin = _position; + Vector3 srcMax = _position + _size; + Vector3 dstMin = with._position; + Vector3 dstMax = with._position + with._size; Vector3 min, max; - if (src_min.x > dst_max.x || src_max.x < dst_min.x) + if (srcMin.x > dstMax.x || srcMax.x < dstMin.x) { return new AABB(); } - min.x = src_min.x > dst_min.x ? src_min.x : dst_min.x; - max.x = src_max.x < dst_max.x ? src_max.x : dst_max.x; + min.x = srcMin.x > dstMin.x ? srcMin.x : dstMin.x; + max.x = srcMax.x < dstMax.x ? srcMax.x : dstMax.x; - if (src_min.y > dst_max.y || src_max.y < dst_min.y) + if (srcMin.y > dstMax.y || srcMax.y < dstMin.y) { return new AABB(); } - min.y = src_min.y > dst_min.y ? src_min.y : dst_min.y; - max.y = src_max.y < dst_max.y ? src_max.y : dst_max.y; + min.y = srcMin.y > dstMin.y ? srcMin.y : dstMin.y; + max.y = srcMax.y < dstMax.y ? srcMax.y : dstMax.y; - if (src_min.z > dst_max.z || src_max.z < dst_min.z) + if (srcMin.z > dstMax.z || srcMax.z < dstMin.z) { return new AABB(); } - min.z = src_min.z > dst_min.z ? src_min.z : dst_min.z; - max.z = src_max.z < dst_max.z ? src_max.z : dst_max.z; + min.z = srcMin.z > dstMin.z ? srcMin.z : dstMin.z; + max.z = srcMax.z < dstMax.z ? srcMax.z : dstMax.z; return new AABB(min, max - min); } /// <summary> - /// Returns true if the AABB overlaps with `b` + /// Returns <see langword="true"/> if the <see cref="AABB"/> overlaps with <paramref name="with"/> /// (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. + /// If <paramref name="includeBorders"/> is <see langword="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="with">The other <see cref="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> + /// <returns> + /// A <see langword="bool"/> for whether or not they are intersecting. + /// </returns> public bool Intersects(AABB with, bool includeBorders = false) { if (includeBorders) @@ -452,10 +472,12 @@ namespace Godot } /// <summary> - /// Returns true if the AABB is on both sides of `plane`. + /// Returns <see langword="true"/> if the <see cref="AABB"/> is on both sides of <paramref name="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> + /// <param name="plane">The <see cref="Plane"/> to check for intersection.</param> + /// <returns> + /// A <see langword="bool"/> for whether or not the <see cref="AABB"/> intersects the <see cref="Plane"/>. + /// </returns> public bool IntersectsPlane(Plane plane) { Vector3[] points = @@ -489,11 +511,14 @@ namespace Godot } /// <summary> - /// Returns true if the AABB intersects the line segment between `from` and `to`. + /// Returns <see langword="true"/> if the <see cref="AABB"/> intersects + /// the line segment between <paramref name="from"/> and <paramref name="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> + /// <returns> + /// A <see langword="bool"/> for whether or not the <see cref="AABB"/> intersects the line segment. + /// </returns> public bool IntersectsSegment(Vector3 from, Vector3 to) { real_t min = 0f; @@ -549,10 +574,10 @@ namespace Godot } /// <summary> - /// Returns a larger AABB that contains this AABB and `b`. + /// Returns a larger <see cref="AABB"/> that contains this <see cref="AABB"/> and <paramref name="with"/>. /// </summary> - /// <param name="with">The other AABB.</param> - /// <returns>The merged AABB.</returns> + /// <param name="with">The other <see cref="AABB"/>.</param> + /// <returns>The merged <see cref="AABB"/>.</returns> public AABB Merge(AABB with) { Vector3 beg1 = _position; @@ -561,22 +586,22 @@ namespace Godot var end2 = new Vector3(with._size.x, with._size.y, with._size.z) + beg2; var min = new Vector3( - beg1.x < beg2.x ? beg1.x : beg2.x, - beg1.y < beg2.y ? beg1.y : beg2.y, - beg1.z < beg2.z ? beg1.z : beg2.z - ); + beg1.x < beg2.x ? beg1.x : beg2.x, + beg1.y < beg2.y ? beg1.y : beg2.y, + beg1.z < beg2.z ? beg1.z : beg2.z + ); var max = new Vector3( - end1.x > end2.x ? end1.x : end2.x, - end1.y > end2.y ? end1.y : end2.y, - end1.z > end2.z ? end1.z : end2.z - ); + end1.x > end2.x ? end1.x : end2.x, + end1.y > end2.y ? end1.y : end2.y, + end1.z > end2.z ? end1.z : end2.z + ); return new AABB(min, max - min); } /// <summary> - /// Constructs an AABB from a position and size. + /// Constructs an <see cref="AABB"/> from a position and size. /// </summary> /// <param name="position">The position.</param> /// <param name="size">The size, typically positive.</param> @@ -587,7 +612,8 @@ namespace Godot } /// <summary> - /// Constructs an AABB from a position, width, height, and depth. + /// Constructs an <see cref="AABB"/> from a <paramref name="position"/>, + /// <paramref name="width"/>, <paramref name="height"/>, and <paramref name="depth"/>. /// </summary> /// <param name="position">The position.</param> /// <param name="width">The width, typically positive.</param> @@ -600,7 +626,8 @@ namespace Godot } /// <summary> - /// Constructs an AABB from x, y, z, and size. + /// Constructs an <see cref="AABB"/> from <paramref name="x"/>, + /// <paramref name="y"/>, <paramref name="z"/>, and <paramref name="size"/>. /// </summary> /// <param name="x">The position's X coordinate.</param> /// <param name="y">The position's Y coordinate.</param> @@ -613,7 +640,9 @@ namespace Godot } /// <summary> - /// Constructs an AABB from x, y, z, width, height, and depth. + /// Constructs an <see cref="AABB"/> from <paramref name="x"/>, + /// <paramref name="y"/>, <paramref name="z"/>, <paramref name="width"/>, + /// <paramref name="height"/>, and <paramref name="depth"/>. /// </summary> /// <param name="x">The position's X coordinate.</param> /// <param name="y">The position's Y coordinate.</param> @@ -637,6 +666,11 @@ namespace Godot return !left.Equals(right); } + /// <summary> + /// Returns <see langword="true"/> if this AABB and <paramref name="obj"/> are equal. + /// </summary> + /// <param name="obj">The other object to compare.</param> + /// <returns>Whether or not the AABB structure and the other object are equal.</returns> public override bool Equals(object obj) { if (obj is AABB) @@ -647,32 +681,49 @@ namespace Godot return false; } + /// <summary> + /// Returns <see langword="true"/> if this AABB and <paramref name="other"/> are equal + /// </summary> + /// <param name="other">The other AABB to compare.</param> + /// <returns>Whether or not the AABBs are equal.</returns> public bool Equals(AABB other) { 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. + /// Returns <see langword="true"/> if this AABB and <paramref name="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> + /// <returns>Whether or not the AABBs structures are approximately equal.</returns> public bool IsEqualApprox(AABB other) { return _position.IsEqualApprox(other._position) && _size.IsEqualApprox(other._size); } + /// <summary> + /// Serves as the hash function for <see cref="AABB"/>. + /// </summary> + /// <returns>A hash code for this AABB.</returns> public override int GetHashCode() { return _position.GetHashCode() ^ _size.GetHashCode(); } + /// <summary> + /// Converts this <see cref="AABB"/> to a string. + /// </summary> + /// <returns>A string representation of this AABB.</returns> public override string ToString() { return $"{_position}, {_size}"; } + /// <summary> + /// Converts this <see cref="AABB"/> to a string with the given <paramref name="format"/>. + /// </summary> + /// <returns>A string representation of this AABB.</returns> public string ToString(string format) { return $"{_position.ToString(format)}, {_size.ToString(format)}"; diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs index f52a767018..a412047196 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs @@ -6,7 +6,7 @@ using System.Runtime.InteropServices; namespace Godot.Collections { - class ArraySafeHandle : SafeHandle + internal class ArraySafeHandle : SafeHandle { public ArraySafeHandle(IntPtr handle) : base(IntPtr.Zero, true) { @@ -33,15 +33,15 @@ namespace Godot.Collections /// </summary> public class Array : IList, IDisposable { - ArraySafeHandle safeHandle; - bool disposed = false; + private ArraySafeHandle _safeHandle; + private bool _disposed = false; /// <summary> /// Constructs a new empty <see cref="Array"/>. /// </summary> public Array() { - safeHandle = new ArraySafeHandle(godot_icall_Array_Ctor()); + _safeHandle = new ArraySafeHandle(godot_icall_Array_Ctor()); } /// <summary> @@ -69,31 +69,31 @@ namespace Godot.Collections { throw new NullReferenceException($"Parameter '{nameof(array)} cannot be null.'"); } - safeHandle = new ArraySafeHandle(godot_icall_Array_Ctor_MonoArray(array)); + _safeHandle = new ArraySafeHandle(godot_icall_Array_Ctor_MonoArray(array)); } internal Array(ArraySafeHandle handle) { - safeHandle = handle; + _safeHandle = handle; } internal Array(IntPtr handle) { - safeHandle = new ArraySafeHandle(handle); + _safeHandle = new ArraySafeHandle(handle); } internal IntPtr GetPtr() { - if (disposed) + if (_disposed) throw new ObjectDisposedException(GetType().FullName); - return safeHandle.DangerousGetHandle(); + return _safeHandle.DangerousGetHandle(); } /// <summary> /// Duplicates this <see cref="Array"/>. /// </summary> - /// <param name="deep">If true, performs a deep copy.</param> + /// <param name="deep">If <see langword="true"/>, performs a deep copy.</param> /// <returns>A new Godot Array.</returns> public Array Duplicate(bool deep = false) { @@ -136,16 +136,16 @@ namespace Godot.Collections /// </summary> public void Dispose() { - if (disposed) + if (_disposed) return; - if (safeHandle != null) + if (_safeHandle != null) { - safeHandle.Dispose(); - safeHandle = null; + _safeHandle.Dispose(); + _safeHandle = null; } - disposed = true; + _disposed = true; } // IList @@ -155,9 +155,9 @@ namespace Godot.Collections bool IList.IsFixedSize => false; /// <summary> - /// Returns the object at the given index. + /// Returns the object at the given <paramref name="index"/>. /// </summary> - /// <value>The object at the given index.</value> + /// <value>The object at the given <paramref name="index"/>.</value> public object this[int index] { get => godot_icall_Array_At(GetPtr(), index); @@ -166,7 +166,7 @@ namespace Godot.Collections /// <summary> /// Adds an object to the end of this <see cref="Array"/>. - /// This is the same as `append` or `push_back` in GDScript. + /// This is the same as <c>append</c> or <c>push_back</c> in GDScript. /// </summary> /// <param name="value">The object to add.</param> /// <returns>The new size after adding the object.</returns> @@ -203,7 +203,7 @@ namespace Godot.Collections public void Insert(int index, object value) => godot_icall_Array_Insert(GetPtr(), index, value); /// <summary> - /// Removes the first occurrence of the specified value + /// Removes the first occurrence of the specified <paramref name="value"/> /// from this <see cref="Array"/>. /// </summary> /// <param name="value">The value to remove.</param> @@ -272,67 +272,67 @@ namespace Godot.Collections } [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static IntPtr godot_icall_Array_Ctor(); + internal static extern IntPtr godot_icall_Array_Ctor(); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static IntPtr godot_icall_Array_Ctor_MonoArray(System.Array array); + internal static extern IntPtr godot_icall_Array_Ctor_MonoArray(System.Array array); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static void godot_icall_Array_Dtor(IntPtr ptr); + internal static extern void godot_icall_Array_Dtor(IntPtr ptr); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static object godot_icall_Array_At(IntPtr ptr, int index); + internal static extern object godot_icall_Array_At(IntPtr ptr, int index); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static object godot_icall_Array_At_Generic(IntPtr ptr, int index, int elemTypeEncoding, IntPtr elemTypeClass); + internal static extern object godot_icall_Array_At_Generic(IntPtr ptr, int index, int elemTypeEncoding, IntPtr elemTypeClass); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static void godot_icall_Array_SetAt(IntPtr ptr, int index, object value); + internal static extern void godot_icall_Array_SetAt(IntPtr ptr, int index, object value); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static int godot_icall_Array_Count(IntPtr ptr); + internal static extern int godot_icall_Array_Count(IntPtr ptr); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static int godot_icall_Array_Add(IntPtr ptr, object item); + internal static extern int godot_icall_Array_Add(IntPtr ptr, object item); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static void godot_icall_Array_Clear(IntPtr ptr); + internal static extern void godot_icall_Array_Clear(IntPtr ptr); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static IntPtr godot_icall_Array_Concatenate(IntPtr left, IntPtr right); + internal static extern IntPtr godot_icall_Array_Concatenate(IntPtr left, IntPtr right); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static bool godot_icall_Array_Contains(IntPtr ptr, object item); + internal static extern bool godot_icall_Array_Contains(IntPtr ptr, object item); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static void godot_icall_Array_CopyTo(IntPtr ptr, System.Array array, int arrayIndex); + internal static extern void godot_icall_Array_CopyTo(IntPtr ptr, System.Array array, int arrayIndex); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static IntPtr godot_icall_Array_Duplicate(IntPtr ptr, bool deep); + internal static extern IntPtr godot_icall_Array_Duplicate(IntPtr ptr, bool deep); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static int godot_icall_Array_IndexOf(IntPtr ptr, object item); + internal static extern int godot_icall_Array_IndexOf(IntPtr ptr, object item); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static void godot_icall_Array_Insert(IntPtr ptr, int index, object item); + internal static extern void godot_icall_Array_Insert(IntPtr ptr, int index, object item); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static bool godot_icall_Array_Remove(IntPtr ptr, object item); + internal static extern bool godot_icall_Array_Remove(IntPtr ptr, object item); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static void godot_icall_Array_RemoveAt(IntPtr ptr, int index); + internal static extern void godot_icall_Array_RemoveAt(IntPtr ptr, int index); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static Error godot_icall_Array_Resize(IntPtr ptr, int newSize); + internal static extern Error godot_icall_Array_Resize(IntPtr ptr, int newSize); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static Error godot_icall_Array_Shuffle(IntPtr ptr); + internal static extern Error godot_icall_Array_Shuffle(IntPtr ptr); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static void godot_icall_Array_Generic_GetElementTypeInfo(Type elemType, out int elemTypeEncoding, out IntPtr elemTypeClass); + internal static extern void godot_icall_Array_Generic_GetElementTypeInfo(Type elemType, out int elemTypeEncoding, out IntPtr elemTypeClass); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static string godot_icall_Array_ToString(IntPtr ptr); + internal static extern string godot_icall_Array_ToString(IntPtr ptr); } /// <summary> @@ -344,7 +344,7 @@ namespace Godot.Collections /// <typeparam name="T">The type of the array.</typeparam> public class Array<T> : IList<T>, ICollection<T>, IEnumerable<T> { - Array objectArray; + private Array _objectArray; internal static int elemTypeEncoding; internal static IntPtr elemTypeClass; @@ -359,7 +359,7 @@ namespace Godot.Collections /// </summary> public Array() { - objectArray = new Array(); + _objectArray = new Array(); } /// <summary> @@ -372,7 +372,7 @@ namespace Godot.Collections if (collection == null) throw new NullReferenceException($"Parameter '{nameof(collection)} cannot be null.'"); - objectArray = new Array(collection); + _objectArray = new Array(collection); } /// <summary> @@ -386,7 +386,7 @@ namespace Godot.Collections { throw new NullReferenceException($"Parameter '{nameof(array)} cannot be null.'"); } - objectArray = new Array(array); + _objectArray = new Array(array); } /// <summary> @@ -395,22 +395,22 @@ namespace Godot.Collections /// <param name="array">The untyped array to construct from.</param> public Array(Array array) { - objectArray = array; + _objectArray = array; } internal Array(IntPtr handle) { - objectArray = new Array(handle); + _objectArray = new Array(handle); } internal Array(ArraySafeHandle handle) { - objectArray = new Array(handle); + _objectArray = new Array(handle); } internal IntPtr GetPtr() { - return objectArray.GetPtr(); + return _objectArray.GetPtr(); } /// <summary> @@ -419,17 +419,17 @@ namespace Godot.Collections /// <param name="from">The typed array to convert.</param> public static explicit operator Array(Array<T> from) { - return from.objectArray; + return from._objectArray; } /// <summary> /// Duplicates this <see cref="Array{T}"/>. /// </summary> - /// <param name="deep">If true, performs a deep copy.</param> + /// <param name="deep">If <see langword="true"/>, performs a deep copy.</param> /// <returns>A new Godot Array.</returns> public Array<T> Duplicate(bool deep = false) { - return new Array<T>(objectArray.Duplicate(deep)); + return new Array<T>(_objectArray.Duplicate(deep)); } /// <summary> @@ -439,7 +439,7 @@ namespace Godot.Collections /// <returns><see cref="Error.Ok"/> if successful, or an error code.</returns> public Error Resize(int newSize) { - return objectArray.Resize(newSize); + return _objectArray.Resize(newSize); } /// <summary> @@ -447,7 +447,7 @@ namespace Godot.Collections /// </summary> public void Shuffle() { - objectArray.Shuffle(); + _objectArray.Shuffle(); } /// <summary> @@ -458,19 +458,19 @@ namespace Godot.Collections /// <returns>A new Godot Array with the contents of both arrays.</returns> public static Array<T> operator +(Array<T> left, Array<T> right) { - return new Array<T>(left.objectArray + right.objectArray); + return new Array<T>(left._objectArray + right._objectArray); } // IList<T> /// <summary> - /// Returns the value at the given index. + /// Returns the value at the given <paramref name="index"/>. /// </summary> - /// <value>The value at the given index.</value> + /// <value>The value at the given <paramref name="index"/>.</value> public T this[int index] { get { return (T)Array.godot_icall_Array_At_Generic(GetPtr(), index, elemTypeEncoding, elemTypeClass); } - set { objectArray[index] = value; } + set { _objectArray[index] = value; } } /// <summary> @@ -481,7 +481,7 @@ namespace Godot.Collections /// <returns>The index of the item, or -1 if not found.</returns> public int IndexOf(T item) { - return objectArray.IndexOf(item); + return _objectArray.IndexOf(item); } /// <summary> @@ -494,7 +494,7 @@ namespace Godot.Collections /// <param name="item">The item to insert.</param> public void Insert(int index, T item) { - objectArray.Insert(index, item); + _objectArray.Insert(index, item); } /// <summary> @@ -503,7 +503,7 @@ namespace Godot.Collections /// <param name="index">The index of the element to remove.</param> public void RemoveAt(int index) { - objectArray.RemoveAt(index); + _objectArray.RemoveAt(index); } // ICollection<T> @@ -515,20 +515,20 @@ namespace Godot.Collections /// <returns>The number of elements.</returns> public int Count { - get { return objectArray.Count; } + get { return _objectArray.Count; } } bool ICollection<T>.IsReadOnly => false; /// <summary> /// Adds an item to the end of this <see cref="Array{T}"/>. - /// This is the same as `append` or `push_back` in GDScript. + /// This is the same as <c>append</c> or <c>push_back</c> in GDScript. /// </summary> /// <param name="item">The item to add.</param> /// <returns>The new size after adding the item.</returns> public void Add(T item) { - objectArray.Add(item); + _objectArray.Add(item); } /// <summary> @@ -536,7 +536,7 @@ namespace Godot.Collections /// </summary> public void Clear() { - objectArray.Clear(); + _objectArray.Clear(); } /// <summary> @@ -546,7 +546,7 @@ namespace Godot.Collections /// <returns>Whether or not this array contains the given item.</returns> public bool Contains(T item) { - return objectArray.Contains(item); + return _objectArray.Contains(item); } /// <summary> @@ -566,7 +566,7 @@ namespace Godot.Collections // TODO This may be quite slow because every element access is an internal call. // It could be moved entirely to an internal call if we find out how to do the cast there. - int count = objectArray.Count; + int count = _objectArray.Count; if (array.Length < (arrayIndex + count)) throw new ArgumentException("Destination array was not long enough. Check destIndex and length, and the array's lower bounds."); @@ -583,7 +583,7 @@ namespace Godot.Collections /// from this <see cref="Array{T}"/>. /// </summary> /// <param name="item">The value to remove.</param> - /// <returns>A bool indicating success or failure.</returns> + /// <returns>A <see langword="bool"/> indicating success or failure.</returns> public bool Remove(T item) { return Array.godot_icall_Array_Remove(GetPtr(), item); @@ -597,7 +597,7 @@ namespace Godot.Collections /// <returns>An enumerator.</returns> public IEnumerator<T> GetEnumerator() { - int count = objectArray.Count; + int count = _objectArray.Count; for (int i = 0; i < count; i++) { @@ -614,6 +614,6 @@ namespace Godot.Collections /// Converts this <see cref="Array{T}"/> to a string. /// </summary> /// <returns>A string representation of this array.</returns> - public override string ToString() => objectArray.ToString(); + public override string ToString() => _objectArray.ToString(); } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/DisableGodotGeneratorsAttribute.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/DisableGodotGeneratorsAttribute.cs index ac6cffceb2..e93bc89811 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/DisableGodotGeneratorsAttribute.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/DisableGodotGeneratorsAttribute.cs @@ -3,7 +3,5 @@ using System; namespace Godot { [AttributeUsage(AttributeTargets.Class)] - public class DisableGodotGeneratorsAttribute : Attribute - { - } + public class DisableGodotGeneratorsAttribute : Attribute { } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/RPCAttributes.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/RPCAttributes.cs index 8aaa9e849d..2dedba2be3 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/RPCAttributes.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/RPCAttributes.cs @@ -3,8 +3,8 @@ using System; namespace Godot { [AttributeUsage(AttributeTargets.Method)] - public class RemoteAttribute : Attribute {} + public class RemoteAttribute : Attribute { } [AttributeUsage(AttributeTargets.Method)] - public class PuppetAttribute : Attribute {} + public class PuppetAttribute : Attribute { } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/SignalAttribute.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/SignalAttribute.cs index 39d5782db8..07a214f543 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/SignalAttribute.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/SignalAttribute.cs @@ -3,7 +3,5 @@ using System; namespace Godot { [AttributeUsage(AttributeTargets.Delegate | AttributeTargets.Event)] - public class SignalAttribute : Attribute - { - } + public class SignalAttribute : Attribute { } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/ToolAttribute.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/ToolAttribute.cs index d0437409af..d2344389f4 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/ToolAttribute.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/ToolAttribute.cs @@ -3,5 +3,5 @@ using System; namespace Godot { [AttributeUsage(AttributeTargets.Class)] - public class ToolAttribute : Attribute {} + public class ToolAttribute : Attribute { } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs index 968f853c2d..0fb1df6c2f 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs @@ -31,7 +31,7 @@ namespace Godot /// <summary> /// The basis matrix's X vector (column 0). /// </summary> - /// <value>Equivalent to <see cref="Column0"/> and array index `[0]`.</value> + /// <value>Equivalent to <see cref="Column0"/> and array index <c>[0]</c>.</value> public Vector3 x { get => Column0; @@ -41,7 +41,7 @@ namespace Godot /// <summary> /// The basis matrix's Y vector (column 1). /// </summary> - /// <value>Equivalent to <see cref="Column1"/> and array index `[1]`.</value> + /// <value>Equivalent to <see cref="Column1"/> and array index <c>[1]</c>.</value> public Vector3 y { get => Column1; @@ -51,7 +51,7 @@ namespace Godot /// <summary> /// The basis matrix's Z vector (column 2). /// </summary> - /// <value>Equivalent to <see cref="Column2"/> and array index `[2]`.</value> + /// <value>Equivalent to <see cref="Column2"/> and array index <c>[2]</c>.</value> public Vector3 z { get => Column2; @@ -82,45 +82,45 @@ namespace Godot /// <summary> /// Column 0 of the basis matrix (the X vector). /// </summary> - /// <value>Equivalent to <see cref="x"/> and array index `[0]`.</value> + /// <value>Equivalent to <see cref="x"/> and array index <c>[0]</c>.</value> public Vector3 Column0 { get => new Vector3(Row0.x, Row1.x, Row2.x); set { - this.Row0.x = value.x; - this.Row1.x = value.y; - this.Row2.x = value.z; + Row0.x = value.x; + Row1.x = value.y; + 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> + /// <value>Equivalent to <see cref="y"/> and array index <c>[1]</c>.</value> public Vector3 Column1 { get => new Vector3(Row0.y, Row1.y, Row2.y); set { - this.Row0.y = value.x; - this.Row1.y = value.y; - this.Row2.y = value.z; + Row0.y = value.x; + Row1.y = value.y; + 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> + /// <value>Equivalent to <see cref="z"/> and array index <c>[2]</c>.</value> public Vector3 Column2 { get => new Vector3(Row0.z, Row1.z, Row2.z); set { - this.Row0.z = value.x; - this.Row1.z = value.y; - this.Row2.z = value.z; + Row0.z = value.x; + Row1.z = value.y; + Row2.z = value.z; } } @@ -150,9 +150,10 @@ namespace Godot } /// <summary> - /// Access whole columns in the form of Vector3. + /// Access whole columns in the form of <see cref="Vector3"/>. /// </summary> /// <param name="column">Which column vector.</param> + /// <value>The basis column.</value> public Vector3 this[int column] { get @@ -193,6 +194,7 @@ namespace Godot /// </summary> /// <param name="column">Which column, the matrix horizontal position.</param> /// <param name="row">Which row, the matrix vertical position.</param> + /// <value>The matrix element.</value> public real_t this[int column, int row] { get @@ -207,6 +209,13 @@ namespace Godot } } + /// <summary> + /// Returns the <see cref="Basis"/>'s rotation in the form of a + /// <see cref="Quaternion"/>. See <see cref="GetEuler"/> if you + /// need Euler angles, but keep in mind quaternions should generally + /// be preferred to Euler angles. + /// </summary> + /// <returns>The basis rotation.</returns> public Quaternion GetRotationQuaternion() { Basis orthonormalizedBasis = Orthonormalized(); @@ -263,10 +272,10 @@ namespace Godot /// The returned vector contains the rotation angles in /// the format (X angle, Y angle, Z angle). /// - /// Consider using the <see cref="Basis.Quaternion()"/> method instead, which + /// Consider using the <see cref="Quaternion()"/> method instead, which /// returns a <see cref="Godot.Quaternion"/> quaternion instead of Euler angles. /// </summary> - /// <returns>A Vector3 representing the basis rotation in Euler angles.</returns> + /// <returns>A <see cref="Vector3"/> representing the basis rotation in Euler angles.</returns> public Vector3 GetEuler() { Basis m = Orthonormalized(); @@ -304,7 +313,10 @@ namespace Godot /// but are more efficient for some internal calculations. /// </summary> /// <param name="index">Which row.</param> - /// <returns>One of `Row0`, `Row1`, or `Row2`.</returns> + /// <exception cref="IndexOutOfRangeException"> + /// Thrown when the <paramref name="index"/> is not 0, 1 or 2. + /// </exception> + /// <returns>One of <c>Row0</c>, <c>Row1</c>, or <c>Row2</c>.</returns> public Vector3 GetRow(int index) { switch (index) @@ -326,6 +338,9 @@ namespace Godot /// </summary> /// <param name="index">Which row.</param> /// <param name="value">The vector to set the row to.</param> + /// <exception cref="IndexOutOfRangeException"> + /// Thrown when the <paramref name="index"/> is not 0, 1 or 2. + /// </exception> public void SetRow(int index, Vector3 value) { switch (index) @@ -452,8 +467,8 @@ namespace Godot } /// <summary> - /// Introduce an additional rotation around the given `axis` - /// by `phi` (in radians). The axis must be a normalized vector. + /// Introduce an additional rotation around the given <paramref name="axis"/> + /// by <paramref name="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> @@ -504,7 +519,7 @@ namespace Godot /// <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]; + return Row0[0] * with[0] + Row1[0] * with[1] + Row2[0] * with[2]; } /// <summary> @@ -514,7 +529,7 @@ namespace Godot /// <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]; + return Row0[1] * with[0] + Row1[1] * with[1] + Row2[1] * with[2]; } /// <summary> @@ -524,7 +539,7 @@ namespace Godot /// <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]; + return Row0[2] * with[0] + Row1[2] * with[1] + Row2[2] * with[2]; } /// <summary> @@ -533,7 +548,7 @@ namespace Godot /// <returns>The transposed basis matrix.</returns> public Basis Transposed() { - var tr = this; + Basis tr = this; real_t temp = tr.Row0[1]; tr.Row0[1] = tr.Row1[0]; @@ -553,15 +568,16 @@ namespace Godot /// <summary> /// Returns a vector transformed (multiplied) by the basis matrix. /// </summary> + /// <seealso cref="XformInv(Vector3)"/> /// <param name="v">A vector to transform.</param> /// <returns>The transformed vector.</returns> public Vector3 Xform(Vector3 v) { return new Vector3 ( - this.Row0.Dot(v), - this.Row1.Dot(v), - this.Row2.Dot(v) + Row0.Dot(v), + Row1.Dot(v), + Row2.Dot(v) ); } @@ -571,15 +587,16 @@ namespace Godot /// Note: This results in a multiplication by the inverse of the /// basis matrix only if it represents a rotation-reflection. /// </summary> + /// <seealso cref="Xform(Vector3)"/> /// <param name="v">A vector to inversely transform.</param> /// <returns>The inversely transformed vector.</returns> public Vector3 XformInv(Vector3 v) { return new Vector3 ( - this.Row0[0] * v.x + this.Row1[0] * v.y + this.Row2[0] * v.z, - this.Row0[1] * v.x + this.Row1[1] * v.y + this.Row2[1] * v.z, - this.Row0[2] * v.x + this.Row1[2] * v.y + this.Row2[2] * v.z + Row0[0] * v.x + Row1[0] * v.y + Row2[0] * v.z, + Row0[1] * v.x + Row1[1] * v.y + Row2[1] * v.z, + Row0[2] * v.x + Row1[2] * v.y + Row2[2] * v.z ); } @@ -675,25 +692,25 @@ namespace Godot /// <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. + /// This is used as a replacement for <c>Basis()</c> in GDScript. + /// Do not use <c>new Basis()</c> 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> + /// <value>Equivalent to <c>new Basis(Vector3.Right, Vector3.Up, Vector3.Back)</c>.</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> + /// <value>Equivalent to <c>new Basis(Vector3.Left, Vector3.Up, Vector3.Back)</c>.</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> + /// <value>Equivalent to <c>new Basis(Vector3.Right, Vector3.Down, Vector3.Back)</c>.</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> + /// <value>Equivalent to <c>new Basis(Vector3.Right, Vector3.Up, Vector3.Forward)</c>.</value> public static Basis FlipZ { get { return _flipZ; } } /// <summary> @@ -752,8 +769,8 @@ namespace Godot } /// <summary> - /// Constructs a pure rotation basis matrix, rotated around the given `axis` - /// by `phi` (in radians). The axis must be a normalized vector. + /// Constructs a pure rotation basis matrix, rotated around the given <paramref name="axis"/> + /// by <paramref name="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> @@ -830,6 +847,11 @@ namespace Godot return !left.Equals(right); } + /// <summary> + /// Returns <see langword="true"/> if this basis and <paramref name="obj"/> are equal. + /// </summary> + /// <param name="obj">The other object to compare.</param> + /// <returns>Whether or not the basis and the other object are equal.</returns> public override bool Equals(object obj) { if (obj is Basis) @@ -840,32 +862,49 @@ namespace Godot return false; } + /// <summary> + /// Returns <see langword="true"/> if this basis and <paramref name="other"/> are equal + /// </summary> + /// <param name="other">The other basis to compare.</param> + /// <returns>Whether or not the bases are equal.</returns> public bool Equals(Basis other) { 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. + /// Returns <see langword="true"/> if this basis and <paramref name="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> + /// <returns>Whether or not the bases are approximately equal.</returns> public bool IsEqualApprox(Basis other) { return Row0.IsEqualApprox(other.Row0) && Row1.IsEqualApprox(other.Row1) && Row2.IsEqualApprox(other.Row2); } + /// <summary> + /// Serves as the hash function for <see cref="Basis"/>. + /// </summary> + /// <returns>A hash code for this basis.</returns> public override int GetHashCode() { return Row0.GetHashCode() ^ Row1.GetHashCode() ^ Row2.GetHashCode(); } + /// <summary> + /// Converts this <see cref="Basis"/> to a string. + /// </summary> + /// <returns>A string representation of this basis.</returns> public override string ToString() { return $"[X: {x}, Y: {y}, Z: {z}]"; } + /// <summary> + /// Converts this <see cref="Basis"/> to a string with the given <paramref name="format"/>. + /// </summary> + /// <returns>A string representation of this basis.</returns> public string ToString(string format) { return $"[X: {x.ToString(format)}, Y: {y.ToString(format)}, Z: {z.ToString(format)}]"; diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.cs index c85cc1884c..a28a46896b 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.cs @@ -2,18 +2,58 @@ using System; namespace Godot { + /// <summary> + /// Callable is a first class object which can be held in variables and passed to functions. + /// It represents a given method in an Object, and is typically used for signal callbacks. + /// </summary> + /// <example> + /// <code> + /// public void PrintArgs(object ar1, object arg2, object arg3 = null) + /// { + /// GD.PrintS(arg1, arg2, arg3); + /// } + /// + /// public void Test() + /// { + /// // This Callable object will call the PrintArgs method defined above. + /// Callable callable = new Callable(this, nameof(PrintArgs)); + /// callable.Call("hello", "world"); // Prints "hello world null". + /// callable.Call(Vector2.Up, 42, callable); // Prints "(0, -1) 42 Node(Node.cs)::PrintArgs". + /// callable.Call("invalid"); // Invalid call, should have at least 2 arguments. + /// } + /// </code> + /// </example> public struct Callable { private readonly Object _target; private readonly StringName _method; private readonly Delegate _delegate; + /// <summary> + /// Object that contains the method. + /// </summary> public Object Target => _target; + /// <summary> + /// Name of the method that will be called. + /// </summary> public StringName Method => _method; + /// <summary> + /// Delegate of the method that will be called. + /// </summary> public Delegate Delegate => _delegate; + /// <summary> + /// Converts a <see cref="Delegate"/> to a <see cref="Callable"/>. + /// </summary> + /// <param name="delegate">The delegate to convert.</param> public static implicit operator Callable(Delegate @delegate) => new Callable(@delegate); + /// <summary> + /// Constructs a new <see cref="Callable"/> for the method called <paramref name="method"/> + /// in the specified <paramref name="target"/>. + /// </summary> + /// <param name="target">Object that contains the method.</param> + /// <param name="method">Name of the method that will be called.</param> public Callable(Object target, StringName method) { _target = target; @@ -21,6 +61,10 @@ namespace Godot _delegate = null; } + /// <summary> + /// Constructs a new <see cref="Callable"/> for the given <paramref name="delegate"/>. + /// </summary> + /// <param name="delegate">Delegate method that will be called.</param> public Callable(Delegate @delegate) { _target = null; diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Color.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Color.cs index b9a98ba9c7..2a869bc335 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Color.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Color.cs @@ -7,11 +7,11 @@ namespace Godot /// 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 + /// Some properties (such as <see cref="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. + /// <see cref="Color8"/> and the <c>r8</c>/<c>g8</c>/<c>b8</c>/<c>a8</c> properties. /// </summary> [Serializable] [StructLayout(LayoutKind.Sequential)] @@ -127,11 +127,11 @@ namespace Godot } else if (g == max) { - h = 2 + (b - r) / delta; // Between cyan & yellow + h = 2 + ((b - r) / delta); // Between cyan & yellow } else { - h = 4 + (r - g) / delta; // Between magenta & cyan + h = 4 + ((r - g) / delta); // Between magenta & cyan } h /= 6.0f; @@ -173,7 +173,7 @@ 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> + /// <value>Getting is equivalent to using <see cref="Math.Max(float, float)"/> on the RGB components. Setting uses <see cref="FromHSV"/>.</value> public float v { get @@ -189,7 +189,12 @@ namespace Godot /// <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> + /// <value> + /// <c>[0]</c> is equivalent to <see cref="r"/>, + /// <c>[1]</c> is equivalent to <see cref="g"/>, + /// <c>[2]</c> is equivalent to <see cref="b"/>, + /// <c>[3]</c> is equivalent to <see cref="a"/>. + /// </value> public float this[int index] { get @@ -236,30 +241,30 @@ namespace Godot /// 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> + /// <returns>This color blended over <paramref name="over"/>.</returns> public Color Blend(Color over) { Color res; float sa = 1.0f - over.a; - res.a = a * sa + over.a; + res.a = (a * sa) + over.a; if (res.a == 0) { return new Color(0, 0, 0, 0); } - res.r = (r * a * sa + over.r * over.a) / res.a; - res.g = (g * a * sa + over.g * over.a) / res.a; - res.b = (b * a * sa + over.b * over.a) / res.a; + res.r = ((r * a * sa) + (over.r * over.a)) / res.a; + res.g = ((g * a * sa) + (over.g * over.a)) / res.a; + res.b = ((b * a * sa) + (over.b * over.a)) / res.a; return res; } /// <summary> /// Returns a new color with all components clamped between the - /// components of `min` and `max` using - /// <see cref="Mathf.Clamp(float, float, float)"/>. + /// components of <paramref name="min"/> and <paramref name="max"/> + /// using <see cref="Mathf.Clamp(float, float, float)"/>. /// </summary> /// <param name="min">The color with minimum allowed values.</param> /// <param name="max">The color with maximum allowed values.</param> @@ -286,14 +291,14 @@ namespace Godot public Color Darkened(float amount) { Color res = this; - res.r = res.r * (1.0f - amount); - res.g = res.g * (1.0f - amount); - res.b = res.b * (1.0f - amount); + res.r *= 1.0f - amount; + res.g *= 1.0f - amount; + res.b *= 1.0f - amount; return res; } /// <summary> - /// Returns the inverted color: `(1 - r, 1 - g, 1 - b, a)`. + /// Returns the inverted color: <c>(1 - r, 1 - g, 1 - b, a)</c>. /// </summary> /// <returns>The inverted color.</returns> public Color Inverted() @@ -315,15 +320,15 @@ namespace Godot public Color Lightened(float amount) { Color res = this; - res.r = res.r + (1.0f - res.r) * amount; - res.g = res.g + (1.0f - res.g) * amount; - res.b = res.b + (1.0f - res.b) * amount; + res.r += (1.0f - res.r) * amount; + res.g += (1.0f - res.g) * amount; + res.b += (1.0f - res.b) * amount; return res; } /// <summary> /// Returns the result of the linear interpolation between - /// this color and `to` by amount `weight`. + /// this color and <paramref name="to"/> by amount <paramref name="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> @@ -341,7 +346,7 @@ namespace Godot /// <summary> /// Returns the result of the linear interpolation between - /// this color and `to` by color amount `weight`. + /// this color and <paramref name="to"/> by color amount <paramref name="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> @@ -362,7 +367,7 @@ namespace Godot /// format (each byte represents a color channel). /// ABGR is the reversed version of the default format. /// </summary> - /// <returns>A uint representing this color in ABGR32 format.</returns> + /// <returns>A <see langword="uint"/> representing this color in ABGR32 format.</returns> public uint ToAbgr32() { uint c = (byte)Math.Round(a * 255); @@ -381,7 +386,7 @@ namespace Godot /// format (each word represents a color channel). /// ABGR is the reversed version of the default format. /// </summary> - /// <returns>A ulong representing this color in ABGR64 format.</returns> + /// <returns>A <see langword="ulong"/> representing this color in ABGR64 format.</returns> public ulong ToAbgr64() { ulong c = (ushort)Math.Round(a * 65535); @@ -400,7 +405,7 @@ namespace Godot /// format (each byte represents a color channel). /// ARGB is more compatible with DirectX, but not used much in Godot. /// </summary> - /// <returns>A uint representing this color in ARGB32 format.</returns> + /// <returns>A <see langword="uint"/> representing this color in ARGB32 format.</returns> public uint ToArgb32() { uint c = (byte)Math.Round(a * 255); @@ -419,7 +424,7 @@ namespace Godot /// format (each word represents a color channel). /// ARGB is more compatible with DirectX, but not used much in Godot. /// </summary> - /// <returns>A ulong representing this color in ARGB64 format.</returns> + /// <returns>A <see langword="ulong"/> representing this color in ARGB64 format.</returns> public ulong ToArgb64() { ulong c = (ushort)Math.Round(a * 65535); @@ -438,7 +443,7 @@ namespace Godot /// format (each byte represents a color channel). /// RGBA is Godot's default and recommended format. /// </summary> - /// <returns>A uint representing this color in RGBA32 format.</returns> + /// <returns>A <see langword="uint"/> representing this color in RGBA32 format.</returns> public uint ToRgba32() { uint c = (byte)Math.Round(r * 255); @@ -457,7 +462,7 @@ namespace Godot /// format (each word represents a color channel). /// RGBA is Godot's default and recommended format. /// </summary> - /// <returns>A ulong representing this color in RGBA64 format.</returns> + /// <returns>A <see langword="ulong"/> representing this color in RGBA64 format.</returns> public ulong ToRgba64() { ulong c = (ushort)Math.Round(r * 65535); @@ -474,11 +479,13 @@ namespace Godot /// <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> + /// <param name="includeAlpha"> + /// Whether or not to include alpha. If <see langword="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; + string txt = string.Empty; txt += ToHex32(r); txt += ToHex32(g); @@ -493,7 +500,7 @@ namespace Godot } /// <summary> - /// Constructs a color from RGBA values, typically on the range of 0 to 1. + /// Constructs a <see cref="Color"/> from RGBA values, typically 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> @@ -508,7 +515,7 @@ namespace Godot } /// <summary> - /// Constructs a color from an existing color and an alpha value. + /// Constructs a <see cref="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> @@ -521,10 +528,10 @@ namespace Godot } /// <summary> - /// Constructs a color from an unsigned 32-bit integer in RGBA format + /// Constructs a <see cref="Color"/> from an unsigned 32-bit integer in RGBA format /// (each byte represents a color channel). /// </summary> - /// <param name="rgba">The uint representing the color.</param> + /// <param name="rgba">The <see langword="uint"/> representing the color.</param> public Color(uint rgba) { a = (rgba & 0xFF) / 255.0f; @@ -537,10 +544,10 @@ namespace Godot } /// <summary> - /// Constructs a color from an unsigned 64-bit integer in RGBA format + /// Constructs a <see cref="Color"/> from an unsigned 64-bit integer in RGBA format /// (each word represents a color channel). /// </summary> - /// <param name="rgba">The ulong representing the color.</param> + /// <param name="rgba">The <see langword="ulong"/> representing the color.</param> public Color(ulong rgba) { a = (rgba & 0xFFFF) / 65535.0f; @@ -553,9 +560,9 @@ namespace Godot } /// <summary> - /// Constructs a color either from an HTML color code or from a - /// standardized color name. Supported - /// color names are the same as the <see cref="Colors"/> constants. + /// Constructs a <see cref="Color"/> either from an HTML color code or from a + /// standardized color name. Supported color names are the same as the + /// <see cref="Colors"/> constants. /// </summary> /// <param name="code">The HTML color code or color name to construct from.</param> public Color(string code) @@ -571,8 +578,8 @@ namespace Godot } /// <summary> - /// Constructs a color either from an HTML color code or from a - /// standardized color name, with `alpha` on the range of 0 to 1. Supported + /// Constructs a <see cref="Color"/> either from an HTML color code or from a + /// standardized color name, with <paramref name="alpha"/> on the range of 0 to 1. Supported /// color names are the same as the <see cref="Colors"/> constants. /// </summary> /// <param name="code">The HTML color code or color name to construct from.</param> @@ -584,9 +591,12 @@ namespace Godot } /// <summary> - /// Constructs a color from the HTML hexadecimal color string in RGBA format. + /// Constructs a <see cref="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> + /// <exception name="ArgumentOutOfRangeException"> + /// Thrown when the given <paramref name="rgba"/> color code is invalid. + /// </exception> private static Color FromHTML(string rgba) { Color c; @@ -627,7 +637,8 @@ namespace Godot } else { - throw new ArgumentOutOfRangeException("Invalid color code. Length is " + rgba.Length + " but a length of 6 or 8 is expected: " + rgba); + throw new ArgumentOutOfRangeException( + $"Invalid color code. Length is {rgba.Length}, but a length of 6 or 8 is expected: {rgba}"); } c.a = 1.0f; @@ -697,11 +708,11 @@ namespace Godot /// <returns>The constructed color.</returns> private static Color Named(string name) { - 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.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.ToUpper(); if (!Colors.namedColors.ContainsKey(name)) @@ -715,7 +726,7 @@ namespace Godot /// <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. + /// the <c>h</c>/<c>s</c>/<c>v</c> 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> @@ -739,8 +750,8 @@ namespace Godot f = hue - i; p = value * (1 - saturation); - q = value * (1 - saturation * f); - t = value * (1 - saturation * (1 - f)); + q = value * (1 - (saturation * f)); + t = value * (1 - (saturation * (1 - f))); switch (i) { @@ -761,7 +772,7 @@ namespace Godot /// <summary> /// Converts a color to HSV values. This is equivalent to using each of - /// the `h`/`s`/`v` properties, but much more efficient. + /// the <c>h</c>/<c>s</c>/<c>v</c> 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> @@ -785,22 +796,24 @@ namespace Godot } else if (g == max) { - hue = 2 + (b - r) / delta; // Between cyan & yellow + hue = 2 + ((b - r) / delta); // Between cyan & yellow } else { - hue = 4 + (r - g) / delta; // Between magenta & cyan + 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; + if (max == 0) + saturation = 0; + else + saturation = 1 - (min / max); + value = max; } @@ -977,6 +990,11 @@ namespace Godot return left.r > right.r; } + /// <summary> + /// Returns <see langword="true"/> if this color and <paramref name="obj"/> are equal. + /// </summary> + /// <param name="obj">The other object to compare.</param> + /// <returns>Whether or not the color and the other object are equal.</returns> public override bool Equals(object obj) { if (obj is Color) @@ -987,14 +1005,19 @@ namespace Godot return false; } + /// <summary> + /// Returns <see langword="true"/> if this color and <paramref name="other"/> are equal + /// </summary> + /// <param name="other">The other color to compare.</param> + /// <returns>Whether or not the colors are equal.</returns> public bool Equals(Color other) { 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. + /// Returns <see langword="true"/> if this color and <paramref name="other"/> are approximately equal, + /// by running <see cref="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> @@ -1003,16 +1026,28 @@ namespace Godot return Mathf.IsEqualApprox(r, other.r) && Mathf.IsEqualApprox(g, other.g) && Mathf.IsEqualApprox(b, other.b) && Mathf.IsEqualApprox(a, other.a); } + /// <summary> + /// Serves as the hash function for <see cref="Color"/>. + /// </summary> + /// <returns>A hash code for this color.</returns> public override int GetHashCode() { return r.GetHashCode() ^ g.GetHashCode() ^ b.GetHashCode() ^ a.GetHashCode(); } + /// <summary> + /// Converts this <see cref="Color"/> to a string. + /// </summary> + /// <returns>A string representation of this color.</returns> public override string ToString() { return $"({r}, {g}, {b}, {a})"; } + /// <summary> + /// Converts this <see cref="Color"/> to a string with the given <paramref name="format"/>. + /// </summary> + /// <returns>A string representation of this color.</returns> public string ToString(string format) { return $"({r.ToString(format)}, {g.ToString(format)}, {b.ToString(format)}, {a.ToString(format)})"; diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs index 61a34bfc87..2dfe304aaa 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs @@ -7,7 +7,7 @@ using System.Diagnostics.CodeAnalysis; namespace Godot.Collections { - class DictionarySafeHandle : SafeHandle + internal class DictionarySafeHandle : SafeHandle { public DictionarySafeHandle(IntPtr handle) : base(IntPtr.Zero, true) { @@ -31,19 +31,17 @@ namespace Godot.Collections /// typed elements allocated in the engine in C++. Useful when /// interfacing with the engine. /// </summary> - public class Dictionary : - IDictionary, - IDisposable + public class Dictionary : IDictionary, IDisposable { - DictionarySafeHandle safeHandle; - bool disposed = false; + private DictionarySafeHandle _safeHandle; + private bool _disposed = false; /// <summary> /// Constructs a new empty <see cref="Dictionary"/>. /// </summary> public Dictionary() { - safeHandle = new DictionarySafeHandle(godot_icall_Dictionary_Ctor()); + _safeHandle = new DictionarySafeHandle(godot_icall_Dictionary_Ctor()); } /// <summary> @@ -62,20 +60,20 @@ namespace Godot.Collections internal Dictionary(DictionarySafeHandle handle) { - safeHandle = handle; + _safeHandle = handle; } internal Dictionary(IntPtr handle) { - safeHandle = new DictionarySafeHandle(handle); + _safeHandle = new DictionarySafeHandle(handle); } internal IntPtr GetPtr() { - if (disposed) + if (_disposed) throw new ObjectDisposedException(GetType().FullName); - return safeHandle.DangerousGetHandle(); + return _safeHandle.DangerousGetHandle(); } /// <summary> @@ -83,16 +81,16 @@ namespace Godot.Collections /// </summary> public void Dispose() { - if (disposed) + if (_disposed) return; - if (safeHandle != null) + if (_safeHandle != null) { - safeHandle.Dispose(); - safeHandle = null; + _safeHandle.Dispose(); + _safeHandle = null; } - disposed = true; + _disposed = true; } /// <summary> @@ -230,17 +228,17 @@ namespace Godot.Collections private class DictionaryEnumerator : IDictionaryEnumerator { - private readonly Dictionary dictionary; - private readonly int count; - private int index = -1; - private bool dirty = true; + private readonly Dictionary _dictionary; + private readonly int _count; + private int _index = -1; + private bool _dirty = true; - private DictionaryEntry entry; + private DictionaryEntry _entry; public DictionaryEnumerator(Dictionary dictionary) { - this.dictionary = dictionary; - count = dictionary.Count; + _dictionary = dictionary; + _count = dictionary.Count; } public object Current => Entry; @@ -249,19 +247,19 @@ namespace Godot.Collections { get { - if (dirty) + if (_dirty) { UpdateEntry(); } - return entry; + return _entry; } } private void UpdateEntry() { - dirty = false; - godot_icall_Dictionary_KeyValuePairAt(dictionary.GetPtr(), index, out object key, out object value); - entry = new DictionaryEntry(key, value); + _dirty = false; + godot_icall_Dictionary_KeyValuePairAt(_dictionary.GetPtr(), _index, out object key, out object value); + _entry = new DictionaryEntry(key, value); } public object Key => Entry.Key; @@ -270,15 +268,15 @@ namespace Godot.Collections public bool MoveNext() { - index++; - dirty = true; - return index < count; + _index++; + _dirty = true; + return _index < _count; } public void Reset() { - index = -1; - dirty = true; + _index = -1; + _dirty = true; } } @@ -292,28 +290,28 @@ namespace Godot.Collections } [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static IntPtr godot_icall_Dictionary_Ctor(); + internal static extern IntPtr godot_icall_Dictionary_Ctor(); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static void godot_icall_Dictionary_Dtor(IntPtr ptr); + internal static extern void godot_icall_Dictionary_Dtor(IntPtr ptr); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static object godot_icall_Dictionary_GetValue(IntPtr ptr, object key); + internal static extern object godot_icall_Dictionary_GetValue(IntPtr ptr, object key); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static object godot_icall_Dictionary_GetValue_Generic(IntPtr ptr, object key, int valTypeEncoding, IntPtr valTypeClass); + internal static extern object godot_icall_Dictionary_GetValue_Generic(IntPtr ptr, object key, int valTypeEncoding, IntPtr valTypeClass); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static void godot_icall_Dictionary_SetValue(IntPtr ptr, object key, object value); + internal static extern void godot_icall_Dictionary_SetValue(IntPtr ptr, object key, object value); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static IntPtr godot_icall_Dictionary_Keys(IntPtr ptr); + internal static extern IntPtr godot_icall_Dictionary_Keys(IntPtr ptr); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static IntPtr godot_icall_Dictionary_Values(IntPtr ptr); + internal static extern IntPtr godot_icall_Dictionary_Values(IntPtr ptr); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static int godot_icall_Dictionary_Count(IntPtr ptr); + internal static extern int godot_icall_Dictionary_Count(IntPtr ptr); [MethodImpl(MethodImplOptions.InternalCall)] internal extern static int godot_icall_Dictionary_KeyValuePairs(IntPtr ptr, out IntPtr keys, out IntPtr values); @@ -325,34 +323,34 @@ namespace Godot.Collections internal extern static void godot_icall_Dictionary_Add(IntPtr ptr, object key, object value); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static void godot_icall_Dictionary_Clear(IntPtr ptr); + internal static extern void godot_icall_Dictionary_Clear(IntPtr ptr); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static bool godot_icall_Dictionary_Contains(IntPtr ptr, object key, object value); + internal static extern bool godot_icall_Dictionary_Contains(IntPtr ptr, object key, object value); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static bool godot_icall_Dictionary_ContainsKey(IntPtr ptr, object key); + internal static extern bool godot_icall_Dictionary_ContainsKey(IntPtr ptr, object key); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static IntPtr godot_icall_Dictionary_Duplicate(IntPtr ptr, bool deep); + internal static extern IntPtr godot_icall_Dictionary_Duplicate(IntPtr ptr, bool deep); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static bool godot_icall_Dictionary_RemoveKey(IntPtr ptr, object key); + internal static extern bool godot_icall_Dictionary_RemoveKey(IntPtr ptr, object key); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static bool godot_icall_Dictionary_Remove(IntPtr ptr, object key, object value); + internal static extern bool godot_icall_Dictionary_Remove(IntPtr ptr, object key, object value); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static bool godot_icall_Dictionary_TryGetValue(IntPtr ptr, object key, out object value); + internal static extern bool godot_icall_Dictionary_TryGetValue(IntPtr ptr, object key, out object value); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static bool godot_icall_Dictionary_TryGetValue_Generic(IntPtr ptr, object key, out object value, int valTypeEncoding, IntPtr valTypeClass); + internal static extern bool godot_icall_Dictionary_TryGetValue_Generic(IntPtr ptr, object key, out object value, int valTypeEncoding, IntPtr valTypeClass); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static void godot_icall_Dictionary_Generic_GetValueTypeInfo(Type valueType, out int valTypeEncoding, out IntPtr valTypeClass); + internal static extern void godot_icall_Dictionary_Generic_GetValueTypeInfo(Type valueType, out int valTypeEncoding, out IntPtr valTypeClass); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static string godot_icall_Dictionary_ToString(IntPtr ptr); + internal static extern string godot_icall_Dictionary_ToString(IntPtr ptr); } /// <summary> @@ -363,10 +361,9 @@ namespace Godot.Collections /// </summary> /// <typeparam name="TKey">The type of the dictionary's keys.</typeparam> /// <typeparam name="TValue">The type of the dictionary's values.</typeparam> - public class Dictionary<TKey, TValue> : - IDictionary<TKey, TValue> + public class Dictionary<TKey, TValue> : IDictionary<TKey, TValue> { - private readonly Dictionary objectDict; + private readonly Dictionary _objectDict; internal static int valTypeEncoding; internal static IntPtr valTypeClass; @@ -381,7 +378,7 @@ namespace Godot.Collections /// </summary> public Dictionary() { - objectDict = new Dictionary(); + _objectDict = new Dictionary(); } /// <summary> @@ -391,7 +388,7 @@ namespace Godot.Collections /// <returns>A new Godot Dictionary.</returns> public Dictionary(IDictionary<TKey, TValue> dictionary) { - objectDict = new Dictionary(); + _objectDict = new Dictionary(); if (dictionary == null) throw new NullReferenceException($"Parameter '{nameof(dictionary)} cannot be null.'"); @@ -413,17 +410,17 @@ namespace Godot.Collections /// <returns>A new Godot Dictionary.</returns> public Dictionary(Dictionary dictionary) { - objectDict = dictionary; + _objectDict = dictionary; } internal Dictionary(IntPtr handle) { - objectDict = new Dictionary(handle); + _objectDict = new Dictionary(handle); } internal Dictionary(DictionarySafeHandle handle) { - objectDict = new Dictionary(handle); + _objectDict = new Dictionary(handle); } /// <summary> @@ -432,12 +429,12 @@ namespace Godot.Collections /// <param name="from">The typed dictionary to convert.</param> public static explicit operator Dictionary(Dictionary<TKey, TValue> from) { - return from.objectDict; + return from._objectDict; } internal IntPtr GetPtr() { - return objectDict.GetPtr(); + return _objectDict.GetPtr(); } /// <summary> @@ -447,7 +444,7 @@ namespace Godot.Collections /// <returns>A new Godot Dictionary.</returns> public Dictionary<TKey, TValue> Duplicate(bool deep = false) { - return new Dictionary<TKey, TValue>(objectDict.Duplicate(deep)); + return new Dictionary<TKey, TValue>(_objectDict.Duplicate(deep)); } // IDictionary<TKey, TValue> @@ -458,8 +455,8 @@ namespace Godot.Collections /// <value>The value at the given <paramref name="key"/>.</value> public TValue this[TKey key] { - get { return (TValue)Dictionary.godot_icall_Dictionary_GetValue_Generic(objectDict.GetPtr(), key, valTypeEncoding, valTypeClass); } - set { objectDict[key] = value; } + get { return (TValue)Dictionary.godot_icall_Dictionary_GetValue_Generic(_objectDict.GetPtr(), key, valTypeEncoding, valTypeClass); } + set { _objectDict[key] = value; } } /// <summary> @@ -469,7 +466,7 @@ namespace Godot.Collections { get { - IntPtr handle = Dictionary.godot_icall_Dictionary_Keys(objectDict.GetPtr()); + IntPtr handle = Dictionary.godot_icall_Dictionary_Keys(_objectDict.GetPtr()); return new Array<TKey>(new ArraySafeHandle(handle)); } } @@ -481,7 +478,7 @@ namespace Godot.Collections { get { - IntPtr handle = Dictionary.godot_icall_Dictionary_Values(objectDict.GetPtr()); + IntPtr handle = Dictionary.godot_icall_Dictionary_Values(_objectDict.GetPtr()); return new Array<TValue>(new ArraySafeHandle(handle)); } } @@ -500,7 +497,7 @@ namespace Godot.Collections /// <param name="value">The object to add.</param> public void Add(TKey key, TValue value) { - objectDict.Add(key, value); + _objectDict.Add(key, value); } /// <summary> @@ -510,7 +507,7 @@ namespace Godot.Collections /// <returns>Whether or not this dictionary contains the given key.</returns> public bool ContainsKey(TKey key) { - return objectDict.Contains(key); + return _objectDict.Contains(key); } /// <summary> @@ -544,14 +541,14 @@ namespace Godot.Collections /// <returns>The number of elements.</returns> public int Count { - get { return objectDict.Count; } + get { return _objectDict.Count; } } bool ICollection<KeyValuePair<TKey, TValue>>.IsReadOnly => false; void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> item) { - objectDict.Add(item.Key, item.Value); + _objectDict.Add(item.Key, item.Value); } /// <summary> @@ -559,12 +556,12 @@ namespace Godot.Collections /// </summary> public void Clear() { - objectDict.Clear(); + _objectDict.Clear(); } bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> item) { - return objectDict.Contains(new KeyValuePair<object, object>(item.Key, item.Value)); + return _objectDict.Contains(new KeyValuePair<object, object>(item.Key, item.Value)); } /// <summary> @@ -622,6 +619,6 @@ namespace Godot.Collections /// Converts this <see cref="Dictionary{TKey, TValue}"/> to a string. /// </summary> /// <returns>A string representation of this dictionary.</returns> - public override string ToString() => objectDict.ToString(); + public override string ToString() => _objectDict.ToString(); } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/DynamicObject.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/DynamicObject.cs index 0c21bcaa3f..26d5f9c796 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/DynamicObject.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/DynamicObject.cs @@ -7,20 +7,20 @@ using System.Runtime.CompilerServices; namespace Godot { /// <summary> - /// Represents an <see cref="Godot.Object"/> whose members can be dynamically accessed at runtime through the Variant API. + /// Represents an <see cref="Object"/> whose members can be dynamically accessed at runtime through the Variant API. /// </summary> /// <remarks> /// <para> - /// The <see cref="Godot.DynamicGodotObject"/> class enables access to the Variant - /// members of a <see cref="Godot.Object"/> instance at runtime. + /// The <see cref="DynamicGodotObject"/> class enables access to the Variant + /// members of a <see cref="Object"/> instance at runtime. /// </para> /// <para> /// This allows accessing the class members using their original names in the engine as well as the members from the - /// script attached to the <see cref="Godot.Object"/>, regardless of the scripting language it was written in. + /// script attached to the <see cref="Object"/>, regardless of the scripting language it was written in. /// </para> /// </remarks> /// <example> - /// This sample shows how to use <see cref="Godot.DynamicGodotObject"/> to dynamically access the engine members of a <see cref="Godot.Object"/>. + /// This sample shows how to use <see cref="DynamicGodotObject"/> to dynamically access the engine members of a <see cref="Object"/>. /// <code> /// dynamic sprite = GetNode("Sprite2D").DynamicGodotObject; /// sprite.add_child(this); @@ -30,7 +30,7 @@ namespace Godot /// </code> /// </example> /// <example> - /// This sample shows how to use <see cref="Godot.DynamicGodotObject"/> to dynamically access the members of the script attached to a <see cref="Godot.Object"/>. + /// This sample shows how to use <see cref="DynamicGodotObject"/> to dynamically access the members of the script attached to a <see cref="Object"/>. /// <code> /// dynamic childNode = GetNode("ChildNode").DynamicGodotObject; /// @@ -54,32 +54,34 @@ namespace Godot public class DynamicGodotObject : DynamicObject { /// <summary> - /// Gets the <see cref="Godot.Object"/> associated with this <see cref="Godot.DynamicGodotObject"/>. + /// Gets the <see cref="Object"/> associated with this <see cref="DynamicGodotObject"/>. /// </summary> public Object Value { get; } /// <summary> - /// Initializes a new instance of the <see cref="Godot.DynamicGodotObject"/> class. + /// Initializes a new instance of the <see cref="DynamicGodotObject"/> class. /// </summary> /// <param name="godotObject"> - /// The <see cref="Godot.Object"/> that will be associated with this <see cref="Godot.DynamicGodotObject"/>. + /// The <see cref="Object"/> that will be associated with this <see cref="DynamicGodotObject"/>. /// </param> - /// <exception cref="System.ArgumentNullException"> - /// Thrown when the <paramref name="godotObject"/> parameter is null. + /// <exception cref="ArgumentNullException"> + /// Thrown when the <paramref name="godotObject"/> parameter is <see langword="null"/>. /// </exception> public DynamicGodotObject(Object godotObject) { if (godotObject == null) throw new ArgumentNullException(nameof(godotObject)); - this.Value = godotObject; + Value = godotObject; } + /// <inheritdoc/> public override IEnumerable<string> GetDynamicMemberNames() { return godot_icall_DynamicGodotObject_SetMemberList(Object.GetPtr(Value)); } + /// <inheritdoc/> public override bool TryBinaryOperation(BinaryOperationBinder binder, object arg, out object result) { switch (binder.Operation) @@ -121,6 +123,7 @@ namespace Godot return base.TryBinaryOperation(binder, arg, out result); } + /// <inheritdoc/> public override bool TryConvert(ConvertBinder binder, out object result) { if (binder.Type == typeof(Object)) @@ -139,6 +142,7 @@ namespace Godot return base.TryConvert(binder, out result); } + /// <inheritdoc/> public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result) { if (indexes.Length == 1) @@ -152,16 +156,19 @@ namespace Godot return base.TryGetIndex(binder, indexes, out result); } + /// <inheritdoc/> public override bool TryGetMember(GetMemberBinder binder, out object result) { return godot_icall_DynamicGodotObject_GetMember(Object.GetPtr(Value), binder.Name, out result); } + /// <inheritdoc/> public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result) { return godot_icall_DynamicGodotObject_InvokeMember(Object.GetPtr(Value), binder.Name, args, out result); } + /// <inheritdoc/> public override bool TrySetIndex(SetIndexBinder binder, object[] indexes, object value) { if (indexes.Length == 1) @@ -175,22 +182,23 @@ namespace Godot return base.TrySetIndex(binder, indexes, value); } + /// <inheritdoc/> public override bool TrySetMember(SetMemberBinder binder, object value) { return godot_icall_DynamicGodotObject_SetMember(Object.GetPtr(Value), binder.Name, value); } [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static string[] godot_icall_DynamicGodotObject_SetMemberList(IntPtr godotObject); + internal static extern string[] godot_icall_DynamicGodotObject_SetMemberList(IntPtr godotObject); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static bool godot_icall_DynamicGodotObject_InvokeMember(IntPtr godotObject, string name, object[] args, out object result); + internal static extern bool godot_icall_DynamicGodotObject_InvokeMember(IntPtr godotObject, string name, object[] args, out object result); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static bool godot_icall_DynamicGodotObject_GetMember(IntPtr godotObject, string name, out object result); + internal static extern bool godot_icall_DynamicGodotObject_GetMember(IntPtr godotObject, string name, out object result); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static bool godot_icall_DynamicGodotObject_SetMember(IntPtr godotObject, string name, object value); + internal static extern bool godot_icall_DynamicGodotObject_SetMember(IntPtr godotObject, string name, object value); #region We don't override these methods diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/NodeExtensions.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/NodeExtensions.cs index 5d16260f5d..658582960f 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/NodeExtensions.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/NodeExtensions.cs @@ -1,42 +1,190 @@ +using System; + namespace Godot { public partial class Node { + /// <summary> + /// Fetches a node. The <see cref="NodePath"/> can be either a relative path (from + /// the current node) or an absolute path (in the scene tree) to a node. If the path + /// does not exist, a <see langword="null"/> instance is returned and an error + /// is logged. Attempts to access methods on the return value will result in an + /// "Attempt to call <method> on a null instance." error. + /// Note: Fetching absolute paths only works when the node is inside the scene tree + /// (see <see cref="IsInsideTree"/>). + /// </summary> + /// <example> + /// Example: Assume your current node is Character and the following tree: + /// <code> + /// /root + /// /root/Character + /// /root/Character/Sword + /// /root/Character/Backpack/Dagger + /// /root/MyGame + /// /root/Swamp/Alligator + /// /root/Swamp/Mosquito + /// /root/Swamp/Goblin + /// </code> + /// Possible paths are: + /// <code> + /// GetNode("Sword"); + /// GetNode("Backpack/Dagger"); + /// GetNode("../Swamp/Alligator"); + /// GetNode("/root/MyGame"); + /// </code> + /// </example> + /// <seealso cref="GetNodeOrNull{T}(NodePath)"/> + /// <param name="path">The path to the node to fetch.</param> + /// <exception cref="InvalidCastException"> + /// Thrown when the given the fetched node can't be casted to the given type <typeparamref name="T"/>. + /// </exception> + /// <typeparam name="T">The type to cast to. Should be a descendant of <see cref="Node"/>.</typeparam> + /// <returns> + /// The <see cref="Node"/> at the given <paramref name="path"/>. + /// </returns> public T GetNode<T>(NodePath path) where T : class { return (T)(object)GetNode(path); } + /// <summary> + /// Fetches a node. The <see cref="NodePath"/> can be either a relative path (from + /// the current node) or an absolute path (in the scene tree) to a node. If the path + /// does not exist, a <see langword="null"/> instance is returned and an error + /// is logged. Attempts to access methods on the return value will result in an + /// "Attempt to call <method> on a null instance." error. + /// Note: Fetching absolute paths only works when the node is inside the scene tree + /// (see <see cref="IsInsideTree"/>). + /// </summary> + /// <example> + /// Example: Assume your current node is Character and the following tree: + /// <code> + /// /root + /// /root/Character + /// /root/Character/Sword + /// /root/Character/Backpack/Dagger + /// /root/MyGame + /// /root/Swamp/Alligator + /// /root/Swamp/Mosquito + /// /root/Swamp/Goblin + /// </code> + /// Possible paths are: + /// <code> + /// GetNode("Sword"); + /// GetNode("Backpack/Dagger"); + /// GetNode("../Swamp/Alligator"); + /// GetNode("/root/MyGame"); + /// </code> + /// </example> + /// <seealso cref="GetNode{T}(NodePath)"/> + /// <param name="path">The path to the node to fetch.</param> + /// <typeparam name="T">The type to cast to. Should be a descendant of <see cref="Node"/>.</typeparam>/// ///////// <returns> + /// The <see cref="Node"/> at the given <paramref name="path"/>, or <see langword="null"/> if not found. + /// </returns> public T GetNodeOrNull<T>(NodePath path) where T : class { return GetNodeOrNull(path) as T; } + /// <summary> + /// Returns a child node by its index (see <see cref="GetChildCount"/>). + /// This method is often used for iterating all children of a node. + /// Negative indices access the children from the last one. + /// To access a child node via its name, use <see cref="GetNode"/>. + /// </summary> + /// <seealso cref="GetChildOrNull{T}(int)"/> + /// <param name="idx">Child index.</param> + /// <exception cref="InvalidCastException"> + /// Thrown when the given the fetched node can't be casted to the given type <typeparamref name="T"/>. + /// </exception> + /// <typeparam name="T">The type to cast to. Should be a descendant of <see cref="Node"/>.</typeparam>/// ///////// <returns> + /// The child <see cref="Node"/> at the given index <paramref name="idx"/>. + /// </returns> public T GetChild<T>(int idx) where T : class { return (T)(object)GetChild(idx); } + /// <summary> + /// Returns a child node by its index (see <see cref="GetChildCount"/>). + /// This method is often used for iterating all children of a node. + /// Negative indices access the children from the last one. + /// To access a child node via its name, use <see cref="GetNode"/>. + /// </summary> + /// <seealso cref="GetChild{T}(int)"/> + /// <param name="idx">Child index.</param> + /// <typeparam name="T">The type to cast to. Should be a descendant of <see cref="Node"/>.</typeparam> + /// <returns> + /// The child <see cref="Node"/> at the given index <paramref name="idx"/>, or <see langword="null"/> if not found. + /// </returns> public T GetChildOrNull<T>(int idx) where T : class { return GetChild(idx) as T; } + /// <summary> + /// The node owner. A node can have any other node as owner (as long as it is + /// a valid parent, grandparent, etc. ascending in the tree). When saving a + /// node (using <see cref="PackedScene"/>), all the nodes it owns will be saved + /// with it. This allows for the creation of complex <see cref="SceneTree"/>s, + /// with instancing and subinstancing. + /// </summary> + /// <seealso cref="GetOwnerOrNull{T}"/> + /// <exception cref="InvalidCastException"> + /// Thrown when the given the fetched node can't be casted to the given type <typeparamref name="T"/>. + /// </exception> + /// <typeparam name="T">The type to cast to. Should be a descendant of <see cref="Node"/>.</typeparam> + /// <returns> + /// The owner <see cref="Node"/>. + /// </returns> public T GetOwner<T>() where T : class { return (T)(object)Owner; } + /// <summary> + /// The node owner. A node can have any other node as owner (as long as it is + /// a valid parent, grandparent, etc. ascending in the tree). When saving a + /// node (using <see cref="PackedScene"/>), all the nodes it owns will be saved + /// with it. This allows for the creation of complex <see cref="SceneTree"/>s, + /// with instancing and subinstancing. + /// </summary> + /// <seealso cref="GetOwner{T}"/> + /// <typeparam name="T">The type to cast to. Should be a descendant of <see cref="Node"/>.</typeparam> + /// <returns> + /// The owner <see cref="Node"/>, or <see langword="null"/> if there is no owner. + /// </returns> public T GetOwnerOrNull<T>() where T : class { return Owner as T; } + /// <summary> + /// Returns the parent node of the current node, or a <see langword="null"/> instance + /// if the node lacks a parent. + /// </summary> + /// <seealso cref="GetParentOrNull{T}"/> + /// <exception cref="InvalidCastException"> + /// Thrown when the given the fetched node can't be casted to the given type <typeparamref name="T"/>. + /// </exception> + /// <typeparam name="T">The type to cast to. Should be a descendant of <see cref="Node"/>.</typeparam> + /// <returns> + /// The parent <see cref="Node"/>. + /// </returns> public T GetParent<T>() where T : class { return (T)(object)GetParent(); } + /// <summary> + /// Returns the parent node of the current node, or a <see langword="null"/> instance + /// if the node lacks a parent. + /// </summary> + /// <seealso cref="GetParent{T}"/> + /// <typeparam name="T">The type to cast to. Should be a descendant of <see cref="Node"/>.</typeparam> + /// <returns> + /// The parent <see cref="Node"/>, or <see langword="null"/> if the node has no parent. + /// </returns> public T GetParentOrNull<T>() where T : class { return GetParent() as T; diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/ObjectExtensions.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/ObjectExtensions.cs index 9ef0959750..691fd85964 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/ObjectExtensions.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/ObjectExtensions.cs @@ -5,17 +5,37 @@ namespace Godot { public partial class Object { + /// <summary> + /// Returns whether <paramref name="instance"/> is a valid object + /// (e.g. has not been deleted from memory). + /// </summary> + /// <param name="instance">The instance to check.</param> + /// <returns>If the instance is a valid object.</returns> public static bool IsInstanceValid(Object instance) { return instance != null && instance.NativeInstance != IntPtr.Zero; } + /// <summary> + /// Returns a weak reference to an object, or <see langword="null"/> + /// if the argument is invalid. + /// A weak reference to an object is not enough to keep the object alive: + /// when the only remaining references to a referent are weak references, + /// garbage collection is free to destroy the referent and reuse its memory + /// for something else. However, until the object is actually destroyed the + /// weak reference may return the object even if there are no strong references + /// to it. + /// </summary> + /// <param name="obj">The object.</param> + /// <returns> + /// The <see cref="WeakRef"/> reference to the object or <see langword="null"/>. + /// </returns> public static WeakRef WeakRef(Object obj) { - return godot_icall_Object_weakref(Object.GetPtr(obj)); + return godot_icall_Object_weakref(GetPtr(obj)); } [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static WeakRef godot_icall_Object_weakref(IntPtr obj); + internal static extern WeakRef godot_icall_Object_weakref(IntPtr obj); } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/PackedSceneExtensions.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/PackedSceneExtensions.cs index 214bbf5179..435b59d5f3 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/PackedSceneExtensions.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/PackedSceneExtensions.cs @@ -1,3 +1,5 @@ +using System; + namespace Godot { public partial class PackedScene @@ -5,20 +7,27 @@ namespace Godot /// <summary> /// Instantiates the scene's node hierarchy, erroring on failure. /// Triggers child scene instantiation(s). Triggers a - /// `Node.NotificationInstanced` notification on the root node. + /// <see cref="Node.NotificationInstanced"/> notification on the root node. /// </summary> - /// <typeparam name="T">The type to cast to. Should be a descendant of Node.</typeparam> + /// <seealso cref="InstantiateOrNull{T}(GenEditState)"/> + /// <exception cref="InvalidCastException"> + /// Thrown when the given the instantiated node can't be casted to the given type <typeparamref name="T"/>. + /// </exception> + /// <typeparam name="T">The type to cast to. Should be a descendant of <see cref="Node"/>.</typeparam> + /// <returns>The instantiated scene.</returns> public T Instantiate<T>(PackedScene.GenEditState editState = (PackedScene.GenEditState)0) where T : class { return (T)(object)Instantiate(editState); } /// <summary> - /// Instantiates the scene's node hierarchy, returning null on failure. + /// Instantiates the scene's node hierarchy, returning <see langword="null"/> on failure. /// Triggers child scene instantiation(s). Triggers a - /// `Node.NotificationInstanced` notification on the root node. + /// <see cref="Node.NotificationInstanced"/> notification on the root node. /// </summary> - /// <typeparam name="T">The type to cast to. Should be a descendant of Node.</typeparam> + /// <seealso cref="Instantiate{T}(GenEditState)"/> + /// <typeparam name="T">The type to cast to. Should be a descendant of <see cref="Node"/>.</typeparam> + /// <returns>The instantiated scene.</returns> public T InstantiateOrNull<T>(PackedScene.GenEditState editState = (PackedScene.GenEditState)0) where T : class { return Instantiate(editState) as T; diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/ResourceLoaderExtensions.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/ResourceLoaderExtensions.cs index 74fa05d1fd..25c11d5cf6 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/ResourceLoaderExtensions.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/ResourceLoaderExtensions.cs @@ -1,10 +1,30 @@ +using System; + namespace Godot { public static partial class ResourceLoader { - public static T Load<T>(string path, string typeHint = null, CacheMode noCache = CacheMode.Reuse) where T : class + /// <summary> + /// Loads a resource at the given <paramref name="path"/>, caching the result + /// for further access. + /// The registered <see cref="ResourceFormatLoader"/> instances are queried sequentially + /// to find the first one which can handle the file's extension, and then attempt + /// loading. If loading fails, the remaining ResourceFormatLoaders are also attempted. + /// An optional <paramref name="typeHint"/> can be used to further specify the + /// <see cref="Resource"/> type that should be handled by the <see cref="ResourceFormatLoader"/>. + /// Anything that inherits from <see cref="Resource"/> can be used as a type hint, + /// for example <see cref="Image"/>. + /// The <paramref name="cacheMode"/> property defines whether and how the cache should + /// be used or updated when loading the resource. See <see cref="CacheMode"/> for details. + /// Returns an empty resource if no <see cref="ResourceFormatLoader"/> could handle the file. + /// </summary> + /// <exception cref="InvalidCastException"> + /// Thrown when the given the loaded resource can't be casted to the given type <typeparamref name="T"/>. + /// </exception> + /// <typeparam name="T">The type to cast to. Should be a descendant of <see cref="Resource"/>.</typeparam> + public static T Load<T>(string path, string typeHint = null, CacheMode cacheMode = CacheMode.Reuse) where T : class { - return (T)(object)Load(path, typeHint, noCache); + return (T)(object)Load(path, typeHint, cacheMode); } } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/SceneTreeExtensions.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/SceneTreeExtensions.cs index 20b11a48dd..df130a5c77 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/SceneTreeExtensions.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/SceneTreeExtensions.cs @@ -6,12 +6,16 @@ namespace Godot { public partial class SceneTree { + /// <summary> + /// Returns a list of all nodes assigned to the given <paramref name="group"/>. + /// </summary> + /// <typeparam name="T">The type to cast to. Should be a descendant of <see cref="Node"/>.</typeparam> public Array<T> GetNodesInGroup<T>(StringName group) where T : class { - return new Array<T>(godot_icall_SceneTree_get_nodes_in_group_Generic(Object.GetPtr(this), StringName.GetPtr(group), typeof(T))); + return new Array<T>(godot_icall_SceneTree_get_nodes_in_group_Generic(GetPtr(this), StringName.GetPtr(group), typeof(T))); } [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static IntPtr godot_icall_SceneTree_get_nodes_in_group_Generic(IntPtr obj, IntPtr group, Type elemType); + internal static extern IntPtr godot_icall_SceneTree_get_nodes_in_group_Generic(IntPtr obj, IntPtr group, Type elemType); } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/GD.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/GD.cs index 71d0593916..53c02feaa2 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/GD.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/GD.cs @@ -7,22 +7,56 @@ using System; using System.Collections.Generic; using System.Runtime.CompilerServices; -// TODO: Add comments describing what this class does. It is not obvious. - namespace Godot { + /// <summary> + /// Godot's global functions. + /// </summary> public static partial class GD { + /// <summary> + /// Decodes a byte array back to a <c>Variant</c> value. + /// If <paramref name="allowObjects"/> is <see langword="true"/> decoding objects is allowed. + /// + /// WARNING: Deserialized object can contain code which gets executed. + /// Do not set <paramref name="allowObjects"/> to <see langword="true"/> + /// if the serialized object comes from untrusted sources to avoid + /// potential security threats (remote code execution). + /// </summary> + /// <param name="bytes">Byte array that will be decoded to a <c>Variant</c>.</param> + /// <param name="allowObjects">If objects should be decoded.</param> + /// <returns>The decoded <c>Variant</c>.</returns> public static object Bytes2Var(byte[] bytes, bool allowObjects = false) { return godot_icall_GD_bytes2var(bytes, allowObjects); } + /// <summary> + /// Converts from a <c>Variant</c> type to another in the best way possible. + /// The <paramref name="type"/> parameter uses the <see cref="Variant.Type"/> values. + /// </summary> + /// <example> + /// <code> + /// var a = new Vector2(1, 0); + /// // Prints 1 + /// GD.Print(a.Length()); + /// var b = GD.Convert(a, Variant.Type.String) + /// // Prints 6 as "(1, 0)" is 6 characters + /// GD.Print(b.Length); + /// </code> + /// </example> + /// <returns>The <c>Variant</c> converted to the given <paramref name="type"/>.</returns> public static object Convert(object what, Variant.Type type) { return godot_icall_GD_convert(what, type); } + /// <summary> + /// Converts from decibels to linear energy (audio). + /// </summary> + /// <seealso cref="Linear2Db(real_t)"/> + /// <param name="db">Decibels to convert.</param> + /// <returns>Audio volume as linear energy.</returns> public static real_t Db2Linear(real_t db) { return (real_t)Math.Exp(db * 0.11512925464970228420089957273422); @@ -38,111 +72,360 @@ namespace Godot return Array.ConvertAll(parameters, x => x?.ToString() ?? "null"); } + /// <summary> + /// Returns the integer hash of the variable passed. + /// </summary> + /// <example> + /// <code> + /// GD.Print(GD.Hash("a")); // Prints 177670 + /// </code> + /// </example> + /// <param name="var">Variable that will be hashed.</param> + /// <returns>Hash of the variable passed.</returns> public static int Hash(object var) { return godot_icall_GD_hash(var); } + /// <summary> + /// Returns the <see cref="Object"/> that corresponds to <paramref name="instanceId"/>. + /// All Objects have a unique instance ID. + /// </summary> + /// <example> + /// <code> + /// public class MyNode : Node + /// { + /// public string foo = "bar"; + /// + /// public override void _Ready() + /// { + /// ulong id = GetInstanceId(); + /// var inst = (MyNode)GD.InstanceFromId(Id); + /// GD.Print(inst.foo); // Prints bar + /// } + /// } + /// </code> + /// </example> + /// <param name="instanceId">Instance ID of the Object to retrieve.</param> + /// <returns>The <see cref="Object"/> instance.</returns> public static Object InstanceFromId(ulong instanceId) { return godot_icall_GD_instance_from_id(instanceId); } + /// <summary> + /// Converts from linear energy to decibels (audio). + /// This can be used to implement volume sliders that behave as expected (since volume isn't linear). + /// </summary> + /// <seealso cref="Db2Linear(real_t)"/> + /// <example> + /// <code> + /// // "slider" refers to a node that inherits Range such as HSlider or VSlider. + /// // Its range must be configured to go from 0 to 1. + /// // Change the bus name if you'd like to change the volume of a specific bus only. + /// AudioServer.SetBusVolumeDb(AudioServer.GetBusIndex("Master"), GD.Linear2Db(slider.value)); + /// </code> + /// </example> + /// <param name="linear">The linear energy to convert.</param> + /// <returns>Audio as decibels</returns> public static real_t Linear2Db(real_t linear) { return (real_t)(Math.Log(linear) * 8.6858896380650365530225783783321); } + /// <summary> + /// Loads a resource from the filesystem located at <paramref name="path"/>. + /// The resource is loaded on the method call (unless it's referenced already + /// elsewhere, e.g. in another script or in the scene), which might cause slight delay, + /// especially when loading scenes. To avoid unnecessary delays when loading something + /// multiple times, either store the resource in a variable. + /// + /// Note: Resource paths can be obtained by right-clicking on a resource in the FileSystem + /// dock and choosing "Copy Path" or by dragging the file from the FileSystem dock into the script. + /// + /// Important: The path must be absolute, a local path will just return <see langword="null"/>. + /// This method is a simplified version of <see cref="ResourceLoader.Load"/>, which can be used + /// for more advanced scenarios. + /// </summary> + /// <example> + /// <code> + /// // Load a scene called main located in the root of the project directory and cache it in a variable. + /// var main = GD.Load("res://main.tscn"); // main will contain a PackedScene resource. + /// </code> + /// </example> + /// <param name="path">Path of the <see cref="Resource"/> to load.</param> + /// <returns>The loaded <see cref="Resource"/>.</returns> public static Resource Load(string path) { return ResourceLoader.Load(path); } + /// <summary> + /// Loads a resource from the filesystem located at <paramref name="path"/>. + /// The resource is loaded on the method call (unless it's referenced already + /// elsewhere, e.g. in another script or in the scene), which might cause slight delay, + /// especially when loading scenes. To avoid unnecessary delays when loading something + /// multiple times, either store the resource in a variable. + /// + /// Note: Resource paths can be obtained by right-clicking on a resource in the FileSystem + /// dock and choosing "Copy Path" or by dragging the file from the FileSystem dock into the script. + /// + /// Important: The path must be absolute, a local path will just return <see langword="null"/>. + /// This method is a simplified version of <see cref="ResourceLoader.Load"/>, which can be used + /// for more advanced scenarios. + /// </summary> + /// <example> + /// <code> + /// // Load a scene called main located in the root of the project directory and cache it in a variable. + /// var main = GD.Load<PackedScene>("res://main.tscn"); // main will contain a PackedScene resource. + /// </code> + /// </example> + /// <param name="path">Path of the <see cref="Resource"/> to load.</param> + /// <typeparam name="T">The type to cast to. Should be a descendant of <see cref="Resource"/>.</typeparam> public static T Load<T>(string path) where T : class { return ResourceLoader.Load<T>(path); } + /// <summary> + /// Pushes an error message to Godot's built-in debugger and to the OS terminal. + /// + /// Note: Errors printed this way will not pause project execution. + /// To print an error message and pause project execution in debug builds, + /// use [code]assert(false, "test error")[/code] instead. + /// </summary> + /// <example> + /// <code> + /// GD.PushError("test_error"); // Prints "test error" to debugger and terminal as error call + /// </code> + /// </example> + /// <param name="message">Error message.</param> public static void PushError(string message) { godot_icall_GD_pusherror(message); } + /// <summary> + /// Pushes a warning message to Godot's built-in debugger and to the OS terminal. + /// </summary> + /// <example> + /// GD.PushWarning("test warning"); // Prints "test warning" to debugger and terminal as warning call + /// </example> + /// <param name="message">Warning message.</param> public static void PushWarning(string message) { godot_icall_GD_pushwarning(message); } + /// <summary> + /// Converts one or more arguments of any type to string in the best way possible + /// and prints them to the console. + /// + /// Note: Consider using <see cref="PushError(string)"/> and <see cref="PushWarning(string)"/> + /// to print error and warning messages instead of <see cref="Print(object[])"/>. + /// This distinguishes them from print messages used for debugging purposes, + /// while also displaying a stack trace when an error or warning is printed. + /// </summary> + /// <example> + /// <code> + /// var a = new int[] { 1, 2, 3 }; + /// GD.Print("a", "b", a); // Prints ab[1, 2, 3] + /// </code> + /// </example> + /// <param name="what">Arguments that will be printed.</param> public static void Print(params object[] what) { godot_icall_GD_print(GetPrintParams(what)); } + /// <summary> + /// Prints the current stack trace information to the console. + /// </summary> public static void PrintStack() { Print(System.Environment.StackTrace); } + /// <summary> + /// Prints one or more arguments to strings in the best way possible to standard error line. + /// </summary> + /// <example> + /// <code> + /// GD.PrintErr("prints to stderr"); + /// </code> + /// </example> + /// <param name="what">Arguments that will be printed.</param> public static void PrintErr(params object[] what) { godot_icall_GD_printerr(GetPrintParams(what)); } + /// <summary> + /// Prints one or more arguments to strings in the best way possible to console. + /// No newline is added at the end. + /// + /// Note: Due to limitations with Godot's built-in console, this only prints to the terminal. + /// If you need to print in the editor, use another method, such as <see cref="Print(object[])"/>. + /// </summary> + /// <example> + /// <code> + /// GD.PrintRaw("A"); + /// GD.PrintRaw("B"); + /// // Prints AB + /// </code> + /// </example> + /// <param name="what">Arguments that will be printed.</param> public static void PrintRaw(params object[] what) { godot_icall_GD_printraw(GetPrintParams(what)); } + /// <summary> + /// Prints one or more arguments to the console with a space between each argument. + /// </summary> + /// <example> + /// <code> + /// GD.PrintS("A", "B", "C"); // Prints A B C + /// </code> + /// </example> + /// <param name="what">Arguments that will be printed.</param> public static void PrintS(params object[] what) { godot_icall_GD_prints(GetPrintParams(what)); } + /// <summary> + /// Prints one or more arguments to the console with a tab between each argument. + /// </summary> + /// <example> + /// <code> + /// GD.PrintT("A", "B", "C"); // Prints A B C + /// </code> + /// </example> + /// <param name="what">Arguments that will be printed.</param> public static void PrintT(params object[] what) { godot_icall_GD_printt(GetPrintParams(what)); } + /// <summary> + /// Returns a random floating point value between <c>0.0</c> and <c>1.0</c> (inclusive). + /// </summary> + /// <example> + /// <code> + /// GD.Randf(); // Returns e.g. 0.375671 + /// </code> + /// </example> + /// <returns>A random <see langword="float"/> number.</returns> public static float Randf() { return godot_icall_GD_randf(); } + /// <summary> + /// Returns a random unsigned 32-bit integer. + /// Use remainder to obtain a random value in the interval <c>[0, N - 1]</c> (where N is smaller than 2^32). + /// </summary> + /// <example> + /// <code> + /// GD.Randi(); // Returns random integer between 0 and 2^32 - 1 + /// GD.Randi() % 20; // Returns random integer between 0 and 19 + /// GD.Randi() % 100; // Returns random integer between 0 and 99 + /// GD.Randi() % 100 + 1; // Returns random integer between 1 and 100 + /// </code> + /// </example> + /// <returns>A random <see langword="uint"/> number.</returns> public static uint Randi() { return godot_icall_GD_randi(); } + /// <summary> + /// Randomizes the seed (or the internal state) of the random number generator. + /// Current implementation reseeds using a number based on time. + /// + /// Note: This method is called automatically when the project is run. + /// If you need to fix the seed to have reproducible results, use <see cref="Seed(ulong)"/> + /// to initialize the random number generator. + /// </summary> public static void Randomize() { godot_icall_GD_randomize(); } + /// <summary> + /// Returns a random floating point value on the interval between <paramref name="from"/> + /// and <paramref name="to"/> (inclusive). + /// </summary> + /// <example> + /// <code> + /// GD.PrintS(GD.RandRange(-10.0, 10.0), GD.RandRange(-10.0, 10.0)); // Prints e.g. -3.844535 7.45315 + /// </code> + /// </example> + /// <returns>A random <see langword="double"/> number inside the given range.</returns> public static double RandRange(double from, double to) { return godot_icall_GD_randf_range(from, to); } + /// <summary> + /// Returns a random signed 32-bit integer between <paramref name="from"/> + /// and <paramref name="to"/> (inclusive). If <paramref name="to"/> is lesser than + /// <paramref name="from"/>, they are swapped. + /// </summary> + /// <example> + /// <code> + /// GD.Print(GD.RandRange(0, 1)); // Prints 0 or 1 + /// GD.Print(GD.RangeRange(-10, 1000)); // Prints any number from -10 to 1000 + /// </code> + /// </example> + /// <returns>A random <see langword="int"/> number inside the given range.</returns> public static int RandRange(int from, int to) { return godot_icall_GD_randi_range(from, to); } + /// <summary> + /// Returns a random unsigned 32-bit integer, using the given <paramref name="seed"/>. + /// </summary> + /// <param name="seed"> + /// Seed to use to generate the random number. + /// If a different seed is used, its value will be modfied. + /// </param> + /// <returns>A random <see langword="uint"/> number.</returns> public static uint RandFromSeed(ref ulong seed) { return godot_icall_GD_rand_seed(seed, out seed); } + /// <summary> + /// Returns a <see cref="IEnumerable{T}"/> that iterates from + /// <c>0</c> to <paramref name="end"/> in steps of <c>1</c>. + /// </summary> + /// <param name="end">The last index.</param> public static IEnumerable<int> Range(int end) { return Range(0, end, 1); } + /// <summary> + /// Returns a <see cref="IEnumerable{T}"/> that iterates from + /// <paramref name="start"/> to <paramref name="end"/> in steps of <c>1</c>. + /// </summary> + /// <param name="start">The first index.</param> + /// <param name="end">The last index.</param> public static IEnumerable<int> Range(int start, int end) { return Range(start, end, 1); } + /// <summary> + /// Returns a <see cref="IEnumerable{T}"/> that iterates from + /// <paramref name="start"/> to <paramref name="end"/> in steps of <paramref name="step"/>. + /// </summary> + /// <param name="start">The first index.</param> + /// <param name="end">The last index.</param> + /// <param name="step">The amount by which to increment the index on each iteration.</param> public static IEnumerable<int> Range(int start, int end, int step) { if (end < start && step > 0) @@ -163,109 +446,164 @@ namespace Godot } } + /// <summary> + /// Sets seed for the random number generator. + /// </summary> + /// <param name="seed">Seed that will be used.</param> public static void Seed(ulong seed) { godot_icall_GD_seed(seed); } + /// <summary> + /// Converts one or more arguments of any type to string in the best way possible. + /// </summary> + /// <param name="what">Arguments that will converted to string.</param> + /// <returns>The string formed by the given arguments.</returns> public static string Str(params object[] what) { return godot_icall_GD_str(what); } + /// <summary> + /// Converts a formatted string that was returned by <see cref="Var2Str(object)"/> to the original value. + /// </summary> + /// <example> + /// <code> + /// string a = "{\"a\": 1, \"b\": 2 }"; + /// var b = (Godot.Collections.Dictionary)GD.Str2Var(a); + /// GD.Print(b["a"]); // Prints 1 + /// </code> + /// </example> + /// <param name="str">String that will be converted to Variant.</param> + /// <returns>The decoded <c>Variant</c>.</returns> public static object Str2Var(string str) { return godot_icall_GD_str2var(str); } + /// <summary> + /// Returns whether the given class exists in <see cref="ClassDB"/>. + /// </summary> + /// <returns>If the class exists in <see cref="ClassDB"/>.</returns> public static bool TypeExists(StringName type) { return godot_icall_GD_type_exists(StringName.GetPtr(type)); } + /// <summary> + /// Encodes a <c>Variant</c> value to a byte array. + /// If <paramref name="fullObjects"/> is <see langword="true"/> encoding objects is allowed + /// (and can potentially include code). + /// Deserialization can be done with <see cref="Bytes2Var(byte[], bool)"/>. + /// </summary> + /// <param name="var">Variant that will be encoded.</param> + /// <param name="fullObjects">If objects should be serialized.</param> + /// <returns>The <c>Variant</c> encoded as an array of bytes.</returns> public static byte[] Var2Bytes(object var, bool fullObjects = false) { return godot_icall_GD_var2bytes(var, fullObjects); } + /// <summary> + /// Converts a <c>Variant</c> <paramref name="var"/> to a formatted string that + /// can later be parsed using <see cref="Str2Var(string)"/>. + /// </summary> + /// <example> + /// <code> + /// var a = new Godot.Collections.Dictionary { ["a"] = 1, ["b"] = 2 }; + /// GD.Print(GD.Var2Str(a)); + /// // Prints + /// // { + /// // "a": 1, + /// // "b": 2 + /// // } + /// </code> + /// </example> + /// <param name="var">Variant that will be converted to string.</param> + /// <returns>The <c>Variant</c> encoded as a string.</returns> public static string Var2Str(object var) { return godot_icall_GD_var2str(var); } + /// <summary> + /// Get the <see cref="Variant.Type"/> that corresponds for the given <see cref="Type"/>. + /// </summary> + /// <returns>The <see cref="Variant.Type"/> for the given <paramref name="type"/>.</returns> public static Variant.Type TypeToVariantType(Type type) { return godot_icall_TypeToVariantType(type); } [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static object godot_icall_GD_bytes2var(byte[] bytes, bool allowObjects); + internal static extern object godot_icall_GD_bytes2var(byte[] bytes, bool allowObjects); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static object godot_icall_GD_convert(object what, Variant.Type type); + internal static extern object godot_icall_GD_convert(object what, Variant.Type type); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static int godot_icall_GD_hash(object var); + internal static extern int godot_icall_GD_hash(object var); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static Object godot_icall_GD_instance_from_id(ulong instanceId); + internal static extern Object godot_icall_GD_instance_from_id(ulong instanceId); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static void godot_icall_GD_print(object[] what); + internal static extern void godot_icall_GD_print(object[] what); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static void godot_icall_GD_printerr(object[] what); + internal static extern void godot_icall_GD_printerr(object[] what); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static void godot_icall_GD_printraw(object[] what); + internal static extern void godot_icall_GD_printraw(object[] what); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static void godot_icall_GD_prints(object[] what); + internal static extern void godot_icall_GD_prints(object[] what); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static void godot_icall_GD_printt(object[] what); + internal static extern void godot_icall_GD_printt(object[] what); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static float godot_icall_GD_randf(); + internal static extern float godot_icall_GD_randf(); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static uint godot_icall_GD_randi(); + internal static extern uint godot_icall_GD_randi(); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static void godot_icall_GD_randomize(); + internal static extern void godot_icall_GD_randomize(); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static double godot_icall_GD_randf_range(double from, double to); + internal static extern double godot_icall_GD_randf_range(double from, double to); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static int godot_icall_GD_randi_range(int from, int to); + internal static extern int godot_icall_GD_randi_range(int from, int to); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static uint godot_icall_GD_rand_seed(ulong seed, out ulong newSeed); + internal static extern uint godot_icall_GD_rand_seed(ulong seed, out ulong newSeed); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static void godot_icall_GD_seed(ulong seed); + internal static extern void godot_icall_GD_seed(ulong seed); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static string godot_icall_GD_str(object[] what); + internal static extern string godot_icall_GD_str(object[] what); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static object godot_icall_GD_str2var(string str); + internal static extern object godot_icall_GD_str2var(string str); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static bool godot_icall_GD_type_exists(IntPtr type); + internal static extern bool godot_icall_GD_type_exists(IntPtr type); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static byte[] godot_icall_GD_var2bytes(object what, bool fullObjects); + internal static extern byte[] godot_icall_GD_var2bytes(object what, bool fullObjects); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static string godot_icall_GD_var2str(object var); + internal static extern string godot_icall_GD_var2str(object var); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static void godot_icall_GD_pusherror(string type); + internal static extern void godot_icall_GD_pusherror(string type); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static void godot_icall_GD_pushwarning(string type); + internal static extern void godot_icall_GD_pushwarning(string type); [MethodImpl(MethodImplOptions.InternalCall)] private static extern Variant.Type godot_icall_TypeToVariantType(Type type); diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotSynchronizationContext.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotSynchronizationContext.cs index 4b5e3f8761..c01c926e82 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotSynchronizationContext.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotSynchronizationContext.cs @@ -6,7 +6,8 @@ namespace Godot { public class GodotSynchronizationContext : SynchronizationContext { - private readonly BlockingCollection<KeyValuePair<SendOrPostCallback, object>> _queue = new BlockingCollection<KeyValuePair<SendOrPostCallback, object>>(); + private readonly BlockingCollection<KeyValuePair<SendOrPostCallback, object>> _queue = + new BlockingCollection<KeyValuePair<SendOrPostCallback, object>>(); public override void Post(SendOrPostCallback d, object state) { diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotTraceListener.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotTraceListener.cs index a566b53659..9ccac1faaf 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotTraceListener.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotTraceListener.cs @@ -24,7 +24,7 @@ namespace Godot try { - var stackTrace = new StackTrace(true).ToString(); + string stackTrace = new StackTrace(true).ToString(); GD.PrintErr(stackTrace); } catch diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/MarshalUtils.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/MarshalUtils.cs index f508211f68..3051bcedc7 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/MarshalUtils.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/MarshalUtils.cs @@ -3,60 +3,135 @@ using System.Collections.Generic; namespace Godot { - static class MarshalUtils + internal static class MarshalUtils { /// <summary> /// Returns <see langword="true"/> if the generic type definition of <paramref name="type"/> - /// is <see cref="Godot.Collections.Array{T}"/>; otherwise returns <see langword="false"/>. + /// is <see cref="Collections.Array{T}"/>; otherwise returns <see langword="false"/>. /// </summary> - /// <exception cref="System.InvalidOperationException"> - /// <paramref name="type"/> is not a generic type. That is, IsGenericType returns false. + /// <exception cref="InvalidOperationException"> + /// Thrown when the given <paramref name="type"/> is not a generic type. + /// That is, <see cref="Type.IsGenericType"/> returns <see langword="false"/>. /// </exception> - static bool TypeIsGenericArray(Type type) => - type.GetGenericTypeDefinition() == typeof(Godot.Collections.Array<>); + private static bool TypeIsGenericArray(Type type) => + type.GetGenericTypeDefinition() == typeof(Collections.Array<>); /// <summary> /// Returns <see langword="true"/> if the generic type definition of <paramref name="type"/> - /// is <see cref="Godot.Collections.Dictionary{TKey, TValue}"/>; otherwise returns <see langword="false"/>. + /// is <see cref="Collections.Dictionary{TKey, TValue}"/>; otherwise returns <see langword="false"/>. /// </summary> - /// <exception cref="System.InvalidOperationException"> - /// <paramref name="type"/> is not a generic type. That is, IsGenericType returns false. + /// <exception cref="InvalidOperationException"> + /// Thrown when the given <paramref name="type"/> is not a generic type. + /// That is, <see cref="Type.IsGenericType"/> returns <see langword="false"/>. /// </exception> - static bool TypeIsGenericDictionary(Type type) => - type.GetGenericTypeDefinition() == typeof(Godot.Collections.Dictionary<,>); + private static bool TypeIsGenericDictionary(Type type) => + type.GetGenericTypeDefinition() == typeof(Collections.Dictionary<,>); - static bool TypeIsSystemGenericList(Type type) => - type.GetGenericTypeDefinition() == typeof(System.Collections.Generic.List<>); + /// <summary> + /// Returns <see langword="true"/> if the generic type definition of <paramref name="type"/> + /// is <see cref="List{T}"/>; otherwise returns <see langword="false"/>. + /// </summary> + /// <exception cref="InvalidOperationException"> + /// Thrown when the given <paramref name="type"/> is not a generic type. + /// That is, <see cref="Type.IsGenericType"/> returns <see langword="false"/>. + /// </exception> + private static bool TypeIsSystemGenericList(Type type) => + type.GetGenericTypeDefinition() == typeof(List<>); - static bool TypeIsSystemGenericDictionary(Type type) => - type.GetGenericTypeDefinition() == typeof(System.Collections.Generic.Dictionary<,>); + /// <summary> + /// Returns <see langword="true"/> if the generic type definition of <paramref name="type"/> + /// is <see cref="Dictionary{TKey, TValue}"/>; otherwise returns <see langword="false"/>. + /// </summary> + /// <exception cref="InvalidOperationException"> + /// Thrown when the given <paramref name="type"/> is not a generic type. + /// That is, <see cref="Type.IsGenericType"/> returns <see langword="false"/>. + /// </exception> + private static bool TypeIsSystemGenericDictionary(Type type) => + type.GetGenericTypeDefinition() == typeof(Dictionary<,>); - static bool TypeIsGenericIEnumerable(Type type) => type.GetGenericTypeDefinition() == typeof(IEnumerable<>); + /// <summary> + /// Returns <see langword="true"/> if the generic type definition of <paramref name="type"/> + /// is <see cref="IEnumerable{T}"/>; otherwise returns <see langword="false"/>. + /// </summary> + /// <exception cref="InvalidOperationException"> + /// Thrown when the given <paramref name="type"/> is not a generic type. + /// That is, <see cref="Type.IsGenericType"/> returns <see langword="false"/>. + /// </exception> + private static bool TypeIsGenericIEnumerable(Type type) => type.GetGenericTypeDefinition() == typeof(IEnumerable<>); - static bool TypeIsGenericICollection(Type type) => type.GetGenericTypeDefinition() == typeof(ICollection<>); + /// <summary> + /// Returns <see langword="true"/> if the generic type definition of <paramref name="type"/> + /// is <see cref="ICollection{T}"/>; otherwise returns <see langword="false"/>. + /// </summary> + /// <exception cref="InvalidOperationException"> + /// Thrown when the given <paramref name="type"/> is not a generic type. + /// That is, <see cref="Type.IsGenericType"/> returns <see langword="false"/>. + /// </exception> + private static bool TypeIsGenericICollection(Type type) => type.GetGenericTypeDefinition() == typeof(ICollection<>); - static bool TypeIsGenericIDictionary(Type type) => type.GetGenericTypeDefinition() == typeof(IDictionary<,>); + /// <summary> + /// Returns <see langword="true"/> if the generic type definition of <paramref name="type"/> + /// is <see cref="IDictionary{TKey, TValue}"/>; otherwise returns <see langword="false"/>. + /// </summary> + /// <exception cref="InvalidOperationException"> + /// Thrown when the given <paramref name="type"/> is not a generic type. + /// That is, <see cref="Type.IsGenericType"/> returns <see langword="false"/>. + /// </exception> + private static bool TypeIsGenericIDictionary(Type type) => type.GetGenericTypeDefinition() == typeof(IDictionary<,>); - static void ArrayGetElementType(Type arrayType, out Type elementType) + /// <summary> + /// Gets the element type for the given <paramref name="arrayType"/>. + /// </summary> + /// <param name="arrayType">Type for the generic array.</param> + /// <param name="elementType">Element type for the generic array.</param> + /// <exception cref="InvalidOperationException"> + /// Thrown when the given <paramref name="arrayType"/> is not a generic type. + /// That is, <see cref="Type.IsGenericType"/> returns <see langword="false"/>. + /// </exception> + private static void ArrayGetElementType(Type arrayType, out Type elementType) { elementType = arrayType.GetGenericArguments()[0]; } - static void DictionaryGetKeyValueTypes(Type dictionaryType, out Type keyType, out Type valueType) + /// <summary> + /// Gets the key type and the value type for the given <paramref name="dictionaryType"/>. + /// </summary> + /// <param name="dictionaryType">The type for the generic dictionary.</param> + /// <param name="keyType">Key type for the generic dictionary.</param> + /// <param name="valueType">Value type for the generic dictionary.</param> + /// <exception cref="InvalidOperationException"> + /// Thrown when the given <paramref name="dictionaryType"/> is not a generic type. + /// That is, <see cref="Type.IsGenericType"/> returns <see langword="false"/>. + /// </exception> + private static void DictionaryGetKeyValueTypes(Type dictionaryType, out Type keyType, out Type valueType) { var genericArgs = dictionaryType.GetGenericArguments(); keyType = genericArgs[0]; valueType = genericArgs[1]; } - static Type MakeGenericArrayType(Type elemType) + /// <summary> + /// Constructs a new <see cref="Type"/> from <see cref="Collections.Array{T}"/> + /// where the generic type for the elements is <paramref name="elemType"/>. + /// </summary> + /// <param name="elemType">Element type for the array.</param> + /// <returns>The generic array type with the specified element type.</returns> + private static Type MakeGenericArrayType(Type elemType) { - return typeof(Godot.Collections.Array<>).MakeGenericType(elemType); + return typeof(Collections.Array<>).MakeGenericType(elemType); } - static Type MakeGenericDictionaryType(Type keyType, Type valueType) + /// <summary> + /// Constructs a new <see cref="Type"/> from <see cref="Collections.Dictionary{TKey, TValue}"/> + /// where the generic type for the keys is <paramref name="keyType"/> and + /// for the values is <paramref name="valueType"/>. + /// </summary> + /// <param name="keyType">Key type for the dictionary.</param> + /// <param name="valueType">Key type for the dictionary.</param> + /// <returns>The generic dictionary type with the specified key and value types.</returns> + private static Type MakeGenericDictionaryType(Type keyType, Type valueType) { - return typeof(Godot.Collections.Dictionary<,>).MakeGenericType(keyType, valueType); + return typeof(Collections.Dictionary<,>).MakeGenericType(keyType, valueType); } } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Mathf.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Mathf.cs index c8dd0ac9ce..6f7fac7429 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Mathf.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Mathf.cs @@ -7,6 +7,9 @@ using System; namespace Godot { + /// <summary> + /// Provides constants and static methods for common mathematical functions. + /// </summary> public static partial class Mathf { // Define constants with Decimal precision and cast down to double or float. @@ -19,108 +22,120 @@ namespace Godot /// <summary> /// Constant that represents how many times the diameter of a circle - /// fits around its perimeter. This is equivalent to `Mathf.Tau / 2`. + /// fits around its perimeter. This is equivalent to <c>Mathf.Tau / 2</c>. /// </summary> // 3.1415927f and 3.14159265358979 public const real_t Pi = (real_t)3.1415926535897932384626433833M; /// <summary> - /// Positive infinity. For negative infinity, use `-Mathf.Inf`. + /// Positive infinity. For negative infinity, use <c>-Mathf.Inf</c>. /// </summary> public const real_t Inf = real_t.PositiveInfinity; /// <summary> - /// "Not a Number", an invalid value. `NaN` has special properties, including + /// "Not a Number", an invalid value. <c>NaN</c> 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; // 0.0174532924f and 0.0174532925199433 - private const real_t Deg2RadConst = (real_t)0.0174532925199432957692369077M; + private const real_t _deg2RadConst = (real_t)0.0174532925199432957692369077M; // 57.29578f and 57.2957795130823 - private const real_t Rad2DegConst = (real_t)57.295779513082320876798154814M; + private const real_t _rad2DegConst = (real_t)57.295779513082320876798154814M; /// <summary> - /// Returns the absolute value of `s` (i.e. positive value). + /// Returns the absolute value of <paramref name="s"/> (i.e. positive value). /// </summary> /// <param name="s">The input number.</param> - /// <returns>The absolute value of `s`.</returns> + /// <returns>The absolute value of <paramref name="s"/>.</returns> public static int Abs(int s) { return Math.Abs(s); } /// <summary> - /// Returns the absolute value of `s` (i.e. positive value). + /// Returns the absolute value of <paramref name="s"/> (i.e. positive value). /// </summary> /// <param name="s">The input number.</param> - /// <returns>The absolute value of `s`.</returns> + /// <returns>The absolute value of <paramref name="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. + /// Returns the arc cosine of <paramref name="s"/> in radians. + /// Use to get the angle of cosine <paramref name="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> + /// <returns> + /// An angle that would result in the given cosine value. On the range <c>0</c> to <c>Tau/2</c>. + /// </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. + /// Returns the arc sine of <paramref name="s"/> in radians. + /// Use to get the angle of sine <paramref name="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> + /// <returns> + /// An angle that would result in the given sine value. On the range <c>-Tau/4</c> to <c>Tau/4</c>. + /// </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. + /// Returns the arc tangent of <paramref name="s"/> in radians. + /// Use to get the angle of tangent <paramref name="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`. + /// See <see cref="Atan2(real_t, real_t)"/> if you have both <c>y</c> and <c>x</c>. /// </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> + /// <returns> + /// An angle that would result in the given tangent value. On the range <c>-Tau/4</c> to <c>Tau/4</c>. + /// </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 + /// Returns the arc tangent of <paramref name="y"/> and <paramref name="x"/> in radians. + /// Use to get the angle of the tangent of <c>y/x</c>. 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> + /// <returns> + /// An angle that would result in the given tangent value. On the range <c>-Tau/2</c> to <c>Tau/2</c>. + /// </returns> public static real_t Atan2(real_t y, real_t x) { return (real_t)Math.Atan2(y, x); } /// <summary> - /// Rounds `s` upward (towards positive infinity). + /// Rounds <paramref name="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> + /// <returns>The smallest whole number that is not less than <paramref name="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`. + /// Clamps a <paramref name="value"/> so that it is not less than <paramref name="min"/> + /// and not more than <paramref name="max"/>. /// </summary> /// <param name="value">The value to clamp.</param> /// <param name="min">The minimum allowed value.</param> @@ -132,7 +147,8 @@ namespace Godot } /// <summary> - /// Clamps a `value` so that it is not less than `min` and not more than `max`. + /// Clamps a <paramref name="value"/> so that it is not less than <paramref name="min"/> + /// and not more than <paramref name="max"/>. /// </summary> /// <param name="value">The value to clamp.</param> /// <param name="min">The minimum allowed value.</param> @@ -144,7 +160,7 @@ namespace Godot } /// <summary> - /// Returns the cosine of angle `s` in radians. + /// Returns the cosine of angle <paramref name="s"/> in radians. /// </summary> /// <param name="s">The angle in radians.</param> /// <returns>The cosine of that angle.</returns> @@ -154,7 +170,7 @@ namespace Godot } /// <summary> - /// Returns the hyperbolic cosine of angle `s` in radians. + /// Returns the hyperbolic cosine of angle <paramref name="s"/> in radians. /// </summary> /// <param name="s">The angle in radians.</param> /// <returns>The hyperbolic cosine of that angle.</returns> @@ -170,16 +186,18 @@ namespace Godot /// <returns>The same angle expressed in radians.</returns> public static real_t Deg2Rad(real_t deg) { - return deg * Deg2RadConst; + 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. + /// Easing function, based on exponent. The <paramref name="curve"/> values are: + /// <c>0</c> is constant, <c>1</c> is linear, <c>0</c> to <c>1</c> is ease-in, <c>1</c> 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> + /// <param name="curve"> + /// <c>0</c> is constant, <c>1</c> is linear, <c>0</c> to <c>1</c> is ease-in, <c>1</c> or more is ease-out. + /// </param> /// <returns>The eased value.</returns> public static real_t Ease(real_t s, real_t curve) { @@ -209,7 +227,7 @@ namespace Godot return Pow(s * 2.0f, -curve) * 0.5f; } - return (1.0f - Pow(1.0f - (s - 0.5f) * 2.0f, -curve)) * 0.5f + 0.5f; + return ((1.0f - Pow(1.0f - ((s - 0.5f) * 2.0f), -curve)) * 0.5f) + 0.5f; } return 0f; @@ -217,20 +235,20 @@ namespace Godot /// <summary> /// The natural exponential function. It raises the mathematical - /// constant `e` to the power of `s` and returns it. + /// constant <c>e</c> to the power of <paramref name="s"/> and returns it. /// </summary> - /// <param name="s">The exponent to raise `e` to.</param> - /// <returns>`e` raised to the power of `s`.</returns> + /// <param name="s">The exponent to raise <c>e</c> to.</param> + /// <returns><c>e</c> raised to the power of <paramref name="s"/>.</returns> public static real_t Exp(real_t s) { return (real_t)Math.Exp(s); } /// <summary> - /// Rounds `s` downward (towards negative infinity). + /// Rounds <paramref name="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> + /// <returns>The largest whole number that is not more than <paramref name="s"/>.</returns> public static real_t Floor(real_t s) { return (real_t)Math.Floor(s); @@ -250,12 +268,13 @@ namespace Godot } /// <summary> - /// Returns true if `a` and `b` are approximately equal to each other. + /// Returns <see langword="true"/> if <paramref name="a"/> and <paramref name="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> + /// <returns>A <see langword="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. @@ -273,33 +292,33 @@ namespace Godot } /// <summary> - /// Returns whether `s` is an infinity value (either positive infinity or negative infinity). + /// Returns whether <paramref name="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> + /// <returns>A <see langword="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. + /// Returns whether <paramref name="s"/> is a <c>NaN</c> ("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> + /// <returns>A <see langword="bool"/> for whether or not the value is a <c>NaN</c> value.</returns> public static bool IsNaN(real_t s) { return real_t.IsNaN(s); } /// <summary> - /// Returns true if `s` is approximately zero. + /// Returns <see langword="true"/> if <paramref name="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> + /// <returns>A <see langword="bool"/> for whether or not the value is nearly zero.</returns> public static bool IsZeroApprox(real_t s) { return Abs(s) < Epsilon; @@ -315,7 +334,7 @@ namespace Godot /// <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; + return from + ((to - from) * weight); } /// <summary> @@ -332,7 +351,7 @@ namespace Godot { real_t difference = (to - from) % Mathf.Tau; real_t distance = ((2 * difference) % Mathf.Tau) - difference; - return from + distance * weight; + return from + (distance * weight); } /// <summary> @@ -341,7 +360,7 @@ namespace Godot /// 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> + /// <returns>The natural log of <paramref name="s"/>.</returns> public static real_t Log(real_t s) { return (real_t)Math.Log(s); @@ -392,9 +411,9 @@ namespace Godot } /// <summary> - /// Moves `from` toward `to` by the `delta` value. + /// Moves <paramref name="from"/> toward <paramref name="to"/> by the <paramref name="delta"/> value. /// - /// Use a negative delta value to move away. + /// Use a negative <paramref name="delta"/> value to move away. /// </summary> /// <param name="from">The start value.</param> /// <param name="to">The value to move towards.</param> @@ -402,11 +421,14 @@ namespace Godot /// <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; + if (Abs(to - from) <= delta) + return to; + + return from + (Sign(to - from) * delta); } /// <summary> - /// Returns the nearest larger power of 2 for the integer `value`. + /// Returns the nearest larger power of 2 for the integer <paramref name="value"/>. /// </summary> /// <param name="value">The input value.</param> /// <returns>The nearest larger power of 2.</returns> @@ -423,10 +445,10 @@ 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, <paramref name="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> + /// <param name="b">The divisor. The output is on the range [0, <paramref name="b"/>).</param> /// <returns>The resulting output.</returns> public static int PosMod(int a, int b) { @@ -439,10 +461,10 @@ 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, <paramref name="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> + /// <param name="b">The divisor. The output is on the range [0, <paramref name="b"/>).</param> /// <returns>The resulting output.</returns> public static real_t PosMod(real_t a, real_t b) { @@ -455,11 +477,11 @@ namespace Godot } /// <summary> - /// Returns the result of `x` raised to the power of `y`. + /// Returns the result of <paramref name="x"/> raised to the power of <paramref name="y"/>. /// </summary> /// <param name="x">The base.</param> /// <param name="y">The exponent.</param> - /// <returns>`x` raised to the power of `y`.</returns> + /// <returns><paramref name="x"/> raised to the power of <paramref name="y"/>.</returns> public static real_t Pow(real_t x, real_t y) { return (real_t)Math.Pow(x, y); @@ -472,11 +494,11 @@ namespace Godot /// <returns>The same angle expressed in degrees.</returns> public static real_t Rad2Deg(real_t rad) { - return rad * Rad2DegConst; + return rad * _rad2DegConst; } /// <summary> - /// Rounds `s` to the nearest whole number, + /// Rounds <paramref name="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> @@ -487,10 +509,11 @@ namespace Godot } /// <summary> - /// Returns the sign of `s`: `-1` or `1`. Returns `0` if `s` is `0`. + /// Returns the sign of <paramref name="s"/>: <c>-1</c> or <c>1</c>. + /// Returns <c>0</c> if <paramref name="s"/> is <c>0</c>. /// </summary> /// <param name="s">The input number.</param> - /// <returns>One of three possible values: `1`, `-1`, or `0`.</returns> + /// <returns>One of three possible values: <c>1</c>, <c>-1</c>, or <c>0</c>.</returns> public static int Sign(int s) { if (s == 0) @@ -499,10 +522,11 @@ namespace Godot } /// <summary> - /// Returns the sign of `s`: `-1` or `1`. Returns `0` if `s` is `0`. + /// Returns the sign of <paramref name="s"/>: <c>-1</c> or <c>1</c>. + /// Returns <c>0</c> if <paramref name="s"/> is <c>0</c>. /// </summary> /// <param name="s">The input number.</param> - /// <returns>One of three possible values: `1`, `-1`, or `0`.</returns> + /// <returns>One of three possible values: <c>1</c>, <c>-1</c>, or <c>0</c>.</returns> public static int Sign(real_t s) { if (s == 0) @@ -511,7 +535,7 @@ namespace Godot } /// <summary> - /// Returns the sine of angle `s` in radians. + /// Returns the sine of angle <paramref name="s"/> in radians. /// </summary> /// <param name="s">The angle in radians.</param> /// <returns>The sine of that angle.</returns> @@ -521,7 +545,7 @@ namespace Godot } /// <summary> - /// Returns the hyperbolic sine of angle `s` in radians. + /// Returns the hyperbolic sine of angle <paramref name="s"/> in radians. /// </summary> /// <param name="s">The angle in radians.</param> /// <returns>The hyperbolic sine of that angle.</returns> @@ -531,8 +555,8 @@ namespace Godot } /// <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)"/>, + /// Returns a number smoothly interpolated between <paramref name="from"/> and <paramref name="to"/>, + /// based on the <paramref name="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> @@ -546,16 +570,16 @@ namespace Godot return from; } real_t x = Clamp((weight - from) / (to - from), (real_t)0.0, (real_t)1.0); - return x * x * (3 - 2 * x); + return x * x * (3 - (2 * x)); } /// <summary> - /// Returns the square root of `s`, where `s` is a non-negative number. + /// Returns the square root of <paramref name="s"/>, where <paramref name="s"/> is a non-negative number. /// - /// If you need negative inputs, use `System.Numerics.Complex`. + /// If you need negative inputs, use <see cref="System.Numerics.Complex"/>. /// </summary> /// <param name="s">The input number. Must not be negative.</param> - /// <returns>The square root of `s`.</returns> + /// <returns>The square root of <paramref name="s"/>.</returns> public static real_t Sqrt(real_t s) { return (real_t)Math.Sqrt(s); @@ -570,7 +594,8 @@ namespace Godot /// <returns>The position of the first non-zero digit.</returns> public static int StepDecimals(real_t step) { - double[] sd = new double[] { + double[] sd = new double[] + { 0.9999, 0.09999, 0.009999, @@ -581,7 +606,7 @@ namespace Godot 0.00000009999, 0.000000009999, }; - double abs = Mathf.Abs(step); + double abs = Abs(step); double decs = abs - (int)abs; // Strip away integer part for (int i = 0; i < sd.Length; i++) { @@ -594,9 +619,8 @@ namespace Godot } /// <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. + /// Snaps float value <paramref name="s"/> to a given <paramref name="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 snap.</param> /// <param name="step">The step size to snap to.</param> @@ -605,14 +629,14 @@ namespace Godot { if (step != 0f) { - return Floor(s / step + 0.5f) * step; + return Floor((s / step) + 0.5f) * step; } return s; } /// <summary> - /// Returns the tangent of angle `s` in radians. + /// Returns the tangent of angle <paramref name="s"/> in radians. /// </summary> /// <param name="s">The angle in radians.</param> /// <returns>The tangent of that angle.</returns> @@ -622,7 +646,7 @@ namespace Godot } /// <summary> - /// Returns the hyperbolic tangent of angle `s` in radians. + /// Returns the hyperbolic tangent of angle <paramref name="s"/> in radians. /// </summary> /// <param name="s">The angle in radians.</param> /// <returns>The hyperbolic tangent of that angle.</returns> @@ -632,8 +656,9 @@ namespace Godot } /// <summary> - /// Wraps `value` between `min` and `max`. Usable for creating loop-alike - /// behavior or infinite surfaces. If `min` is `0`, this is equivalent + /// Wraps <paramref name="value"/> between <paramref name="min"/> and <paramref name="max"/>. + /// Usable for creating loop-alike behavior or infinite surfaces. + /// If <paramref name="min"/> is <c>0</c>, this is equivalent /// to <see cref="PosMod(int, int)"/>, so prefer using that instead. /// </summary> /// <param name="value">The value to wrap.</param> @@ -643,12 +668,16 @@ namespace Godot public static int Wrap(int value, int min, int max) { int range = max - min; - return range == 0 ? min : min + ((value - min) % range + range) % range; + if (range == 0) + return min; + + return 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 + /// Wraps <paramref name="value"/> between <paramref name="min"/> and <paramref name="max"/>. + /// Usable for creating loop-alike behavior or infinite surfaces. + /// If <paramref name="min"/> is <c>0</c>, 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> @@ -658,7 +687,11 @@ namespace Godot public static real_t Wrap(real_t value, real_t min, real_t max) { real_t range = max - min; - return IsZeroApprox(range) ? min : min + ((value - min) % range + range) % range; + if (IsZeroApprox(range)) + { + return min; + } + return min + ((((value - min) % range) + range) % range); } } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/MathfEx.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/MathfEx.cs index 0888e33090..9bb73ce7dd 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/MathfEx.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/MathfEx.cs @@ -12,7 +12,7 @@ namespace Godot // Define constants with Decimal precision and cast down to double or float. /// <summary> - /// The natural number `e`. + /// The natural number <c>e</c>. /// </summary> public const real_t E = (real_t)2.7182818284590452353602874714M; // 2.7182817f and 2.718281828459045 @@ -23,7 +23,7 @@ namespace Godot /// <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`. + /// 1e-06 with single-precision floats, but 1e-14 if <c>REAL_T_IS_DOUBLE</c>. /// </summary> #if REAL_T_IS_DOUBLE public const real_t Epsilon = 1e-14; // Epsilon size should depend on the precision used. @@ -44,7 +44,7 @@ namespace Godot /// <summary> /// Returns the amount of digits after the decimal place. /// </summary> - /// <param name="s">The input <see cref="System.Decimal"/> value.</param> + /// <param name="s">The input <see cref="decimal"/> value.</param> /// <returns>The amount of digits.</returns> public static int DecimalCount(decimal s) { @@ -52,48 +52,51 @@ namespace Godot } /// <summary> - /// Rounds `s` upward (towards positive infinity). + /// Rounds <paramref name="s"/> upward (towards positive infinity). /// - /// This is the same as <see cref="Ceil(real_t)"/>, but returns an `int`. + /// This is the same as <see cref="Ceil(real_t)"/>, but returns an <c>int</c>. /// </summary> /// <param name="s">The number to ceil.</param> - /// <returns>The smallest whole number that is not less than `s`.</returns> + /// <returns>The smallest whole number that is not less than <paramref name="s"/>.</returns> public static int CeilToInt(real_t s) { return (int)Math.Ceiling(s); } /// <summary> - /// Rounds `s` downward (towards negative infinity). + /// Rounds <paramref name="s"/> downward (towards negative infinity). /// - /// This is the same as <see cref="Floor(real_t)"/>, but returns an `int`. + /// This is the same as <see cref="Floor(real_t)"/>, but returns an <c>int</c>. /// </summary> /// <param name="s">The number to floor.</param> - /// <returns>The largest whole number that is not more than `s`.</returns> + /// <returns>The largest whole number that is not more than <paramref name="s"/>.</returns> public static int FloorToInt(real_t s) { return (int)Math.Floor(s); } /// <summary> + /// Rounds <paramref name="s"/> to the nearest whole number. /// + /// This is the same as <see cref="Round(real_t)"/>, but returns an <c>int</c>. /// </summary> - /// <param name="s"></param> - /// <returns></returns> + /// <param name="s">The number to round.</param> + /// <returns>The rounded number.</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. + /// Returns <see langword="true"/> if <paramref name="a"/> and <paramref name="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> + /// <returns>A <see langword="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/NodePath.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NodePath.cs index 4ecc55f94e..f53b5dc904 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NodePath.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NodePath.cs @@ -3,9 +3,45 @@ using System.Runtime.CompilerServices; namespace Godot { + /// <summary> + /// A pre-parsed relative or absolute path in a scene tree, + /// for use with <see cref="Node.GetNode(NodePath)"/> and similar functions. + /// It can reference a node, a resource within a node, or a property + /// of a node or resource. + /// For instance, <c>"Path2D/PathFollow2D/Sprite2D:texture:size"</c> + /// would refer to the <c>size</c> property of the <c>texture</c> + /// resource on the node named <c>"Sprite2D"</c> which is a child of + /// the other named nodes in the path. + /// You will usually just pass a string to <see cref="Node.GetNode(NodePath)"/> + /// and it will be automatically converted, but you may occasionally + /// want to parse a path ahead of time with NodePath. + /// Exporting a NodePath variable will give you a node selection widget + /// in the properties panel of the editor, which can often be useful. + /// A NodePath is composed of a list of slash-separated node names + /// (like a filesystem path) and an optional colon-separated list of + /// "subnames" which can be resources or properties. + /// + /// Note: In the editor, NodePath properties are automatically updated when moving, + /// renaming or deleting a node in the scene tree, but they are never updated at runtime. + /// </summary> + /// <example> + /// Some examples of NodePaths include the following: + /// <code> + /// // No leading slash means it is relative to the current node. + /// new NodePath("A"); // Immediate child A. + /// new NodePath("A/B"); // A's child B. + /// new NodePath("."); // The current node. + /// new NodePath(".."); // The parent node. + /// new NodePath("../C"); // A sibling node C. + /// // A leading slash means it is absolute from the SceneTree. + /// new NodePath("/root"); // Equivalent to GetTree().Root + /// new NodePath("/root/Main"); // If your main scene's root node were named "Main". + /// new NodePath("/root/MyAutoload"); // If you have an autoloaded node or scene. + /// </code> + /// </example> public sealed partial class NodePath : IDisposable { - private bool disposed = false; + private bool _disposed = false; private IntPtr ptr; @@ -14,7 +50,7 @@ namespace Godot if (instance == null) throw new NullReferenceException($"The instance of type {nameof(NodePath)} is null."); - if (instance.disposed) + if (instance._disposed) throw new ObjectDisposedException(instance.GetType().FullName); return instance.ptr; @@ -25,6 +61,9 @@ namespace Godot Dispose(false); } + /// <summary> + /// Disposes of this <see cref="NodePath"/>. + /// </summary> public void Dispose() { Dispose(true); @@ -33,7 +72,7 @@ namespace Godot private void Dispose(bool disposing) { - if (disposed) + if (_disposed) return; if (ptr != IntPtr.Zero) @@ -42,7 +81,7 @@ namespace Godot ptr = IntPtr.Zero; } - disposed = true; + _disposed = true; } internal NodePath(IntPtr ptr) @@ -50,57 +89,168 @@ namespace Godot this.ptr = ptr; } - public NodePath() : this(string.Empty) {} - + /// <summary> + /// Constructs an empty <see cref="NodePath"/>. + /// </summary> + public NodePath() : this(string.Empty) { } + + /// <summary> + /// Constructs a <see cref="NodePath"/> from a string <paramref name="path"/>, + /// e.g.: <c>"Path2D/PathFollow2D/Sprite2D:texture:size"</c>. + /// A path is absolute if it starts with a slash. Absolute paths + /// are only valid in the global scene tree, not within individual + /// scenes. In a relative path, <c>"."</c> and <c>".."</c> indicate + /// the current node and its parent. + /// The "subnames" optionally included after the path to the target + /// node can point to resources or properties, and can also be nested. + /// </summary> + /// <example> + /// Examples of valid NodePaths (assuming that those nodes exist and + /// have the referenced resources or properties): + /// <code> + /// // Points to the Sprite2D node. + /// "Path2D/PathFollow2D/Sprite2D" + /// // Points to the Sprite2D node and its "texture" resource. + /// // GetNode() would retrieve "Sprite2D", while GetNodeAndResource() + /// // would retrieve both the Sprite2D node and the "texture" resource. + /// "Path2D/PathFollow2D/Sprite2D:texture" + /// // Points to the Sprite2D node and its "position" property. + /// "Path2D/PathFollow2D/Sprite2D:position" + /// // Points to the Sprite2D node and the "x" component of its "position" property. + /// "Path2D/PathFollow2D/Sprite2D:position:x" + /// // Absolute path (from "root") + /// "/root/Level/Path2D" + /// </code> + /// </example> + /// <param name="path"></param> public NodePath(string path) { ptr = godot_icall_NodePath_Ctor(path); } + /// <summary> + /// Converts a string to a <see cref="NodePath"/>. + /// </summary> + /// <param name="from">The string to convert.</param> public static implicit operator NodePath(string from) => new NodePath(from); + /// <summary> + /// Converts this <see cref="NodePath"/> to a string. + /// </summary> + /// <param name="from">The <see cref="NodePath"/> to convert.</param> public static implicit operator string(NodePath from) => from.ToString(); + /// <summary> + /// Converts this <see cref="NodePath"/> to a string. + /// </summary> + /// <returns>A string representation of this <see cref="NodePath"/>.</returns> public override string ToString() { return godot_icall_NodePath_operator_String(GetPtr(this)); } + /// <summary> + /// Returns a node path with a colon character (<c>:</c>) prepended, + /// transforming it to a pure property path with no node name (defaults + /// to resolving from the current node). + /// </summary> + /// <example> + /// <code> + /// // This will be parsed as a node path to the "x" property in the "position" node. + /// var nodePath = new NodePath("position:x"); + /// // This will be parsed as a node path to the "x" component of the "position" property in the current node. + /// NodePath propertyPath = nodePath.GetAsPropertyPath(); + /// GD.Print(propertyPath); // :position:x + /// </code> + /// </example> + /// <returns>The <see cref="NodePath"/> as a pure property path.</returns> public NodePath GetAsPropertyPath() { return new NodePath(godot_icall_NodePath_get_as_property_path(GetPtr(this))); } + /// <summary> + /// Returns all subnames concatenated with a colon character (<c>:</c>) + /// as separator, i.e. the right side of the first colon in a node path. + /// </summary> + /// <example> + /// <code> + /// var nodepath = new NodePath("Path2D/PathFollow2D/Sprite2D:texture:load_path"); + /// GD.Print(nodepath.GetConcatenatedSubnames()); // texture:load_path + /// </code> + /// </example> + /// <returns>The subnames concatenated with <c>:</c>.</returns> public string GetConcatenatedSubnames() { return godot_icall_NodePath_get_concatenated_subnames(GetPtr(this)); } + /// <summary> + /// Gets the node name indicated by <paramref name="idx"/> (0 to <see cref="GetNameCount"/>). + /// </summary> + /// <example> + /// <code> + /// var nodePath = new NodePath("Path2D/PathFollow2D/Sprite2D"); + /// GD.Print(nodePath.GetName(0)); // Path2D + /// GD.Print(nodePath.GetName(1)); // PathFollow2D + /// GD.Print(nodePath.GetName(2)); // Sprite + /// </code> + /// </example> + /// <param name="idx">The name index.</param> + /// <returns>The name at the given index <paramref name="idx"/>.</returns> public string GetName(int idx) { return godot_icall_NodePath_get_name(GetPtr(this), idx); } + /// <summary> + /// Gets the number of node names which make up the path. + /// Subnames (see <see cref="GetSubnameCount"/>) are not included. + /// For example, <c>"Path2D/PathFollow2D/Sprite2D"</c> has 3 names. + /// </summary> + /// <returns>The number of node names which make up the path.</returns> public int GetNameCount() { return godot_icall_NodePath_get_name_count(GetPtr(this)); } + /// <summary> + /// Gets the resource or property name indicated by <paramref name="idx"/> (0 to <see cref="GetSubnameCount"/>). + /// </summary> + /// <param name="idx">The subname index.</param> + /// <returns>The subname at the given index <paramref name="idx"/>.</returns> public string GetSubname(int idx) { return godot_icall_NodePath_get_subname(GetPtr(this), idx); } + /// <summary> + /// Gets the number of resource or property names ("subnames") in the path. + /// Each subname is listed after a colon character (<c>:</c>) in the node path. + /// For example, <c>"Path2D/PathFollow2D/Sprite2D:texture:load_path"</c> has 2 subnames. + /// </summary> + /// <returns>The number of subnames in the path.</returns> public int GetSubnameCount() { return godot_icall_NodePath_get_subname_count(GetPtr(this)); } + /// <summary> + /// Returns <see langword="true"/> if the node path is absolute (as opposed to relative), + /// which means that it starts with a slash character (<c>/</c>). Absolute node paths can + /// be used to access the root node (<c>"/root"</c>) or autoloads (e.g. <c>"/global"</c> + /// if a "global" autoload was registered). + /// </summary> + /// <returns>If the <see cref="NodePath"/> is an absolute path.</returns> public bool IsAbsolute() { return godot_icall_NodePath_is_absolute(GetPtr(this)); } + /// <summary> + /// Returns <see langword="true"/> if the node path is empty. + /// </summary> + /// <returns>If the <see cref="NodePath"/> is empty.</returns> public bool IsEmpty() { return godot_icall_NodePath_is_empty(GetPtr(this)); diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Object.base.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Object.base.cs index d486d79557..8fe08e7e1d 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Object.base.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Object.base.cs @@ -5,13 +5,16 @@ namespace Godot { public partial class Object : IDisposable { - private bool disposed = false; + private bool _disposed = false; private static StringName nativeName = "Object"; internal IntPtr ptr; internal bool memoryOwn; + /// <summary> + /// Constructs a new <see cref="Object"/>. + /// </summary> public Object() : this(false) { if (ptr == IntPtr.Zero) @@ -29,6 +32,9 @@ namespace Godot this.memoryOwn = memoryOwn; } + /// <summary> + /// The pointer to the native instance of this <see cref="Object"/>. + /// </summary> public IntPtr NativeInstance { get { return ptr; } @@ -39,7 +45,7 @@ namespace Godot if (instance == null) return IntPtr.Zero; - if (instance.disposed) + if (instance._disposed) throw new ObjectDisposedException(instance.GetType().FullName); return instance.ptr; @@ -50,15 +56,21 @@ namespace Godot Dispose(false); } + /// <summary> + /// Disposes of this <see cref="Object"/>. + /// </summary> public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } + /// <summary> + /// Disposes implementation of this <see cref="Object"/>. + /// </summary> protected virtual void Dispose(bool disposing) { - if (disposed) + if (_disposed) return; if (ptr != IntPtr.Zero) @@ -73,19 +85,23 @@ namespace Godot godot_icall_Object_Disposed(this, ptr); } - this.ptr = IntPtr.Zero; + ptr = IntPtr.Zero; } - disposed = true; + _disposed = true; } + /// <summary> + /// Converts this <see cref="Object"/> to a string. + /// </summary> + /// <returns>A string representation of this object.</returns> public override string ToString() { return godot_icall_Object_ToString(GetPtr(this)); } /// <summary> - /// Returns a new <see cref="Godot.SignalAwaiter"/> awaiter configured to complete when the instance + /// Returns a new <see cref="SignalAwaiter"/> awaiter configured to complete when the instance /// <paramref name="source"/> emits the signal specified by the <paramref name="signal"/> parameter. /// </summary> /// <param name="source"> @@ -107,13 +123,17 @@ namespace Godot /// } /// </code> /// </example> + /// <returns> + /// A <see cref="SignalAwaiter"/> that completes when + /// <paramref name="source"/> emits the <paramref name="signal"/>. + /// </returns> public SignalAwaiter ToSignal(Object source, StringName signal) { return new SignalAwaiter(source, signal, this); } /// <summary> - /// Gets a new <see cref="Godot.DynamicGodotObject"/> associated with this instance. + /// Gets a new <see cref="DynamicGodotObject"/> associated with this instance. /// </summary> public dynamic DynamicObject => new DynamicGodotObject(this); diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Plane.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Plane.cs index 6972102730..66f7b745f7 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Plane.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Plane.cs @@ -21,10 +21,10 @@ namespace Godot /// <summary> /// The normal of the plane, which must be normalized. - /// In the scalar equation of the plane `ax + by + cz = d`, this is - /// the vector `(a, b, c)`, where `d` is the <see cref="D"/> property. + /// In the scalar equation of the plane <c>ax + by + cz = d</c>, this is + /// the vector <c>(a, b, c)</c>, where <c>d</c> is the <see cref="D"/> property. /// </summary> - /// <value>Equivalent to `x`, `y`, and `z`.</value> + /// <value>Equivalent to <see cref="x"/>, <see cref="y"/>, and <see cref="z"/>.</value> public Vector3 Normal { get { return _normal; } @@ -82,8 +82,8 @@ namespace Godot /// <summary> /// The distance from the origin to the plane (in the direction of /// <see cref="Normal"/>). This value is typically non-negative. - /// In the scalar equation of the plane `ax + by + cz = d`, - /// this is `d`, while the `(a, b, c)` coordinates are represented + /// In the scalar equation of the plane <c>ax + by + cz = d</c>, + /// this is <c>d</c>, while the <c>(a, b, c)</c> coordinates are represented /// by the <see cref="Normal"/> property. /// </summary> /// <value>The plane's distance from the origin.</value> @@ -92,7 +92,7 @@ namespace Godot /// <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> + /// <value>Equivalent to <see cref="Normal"/> multiplied by <see cref="D"/>.</value> public Vector3 Center { get @@ -107,7 +107,7 @@ namespace Godot } /// <summary> - /// Returns the shortest distance from this plane to the position `point`. + /// Returns the shortest distance from this plane to the position <paramref name="point"/>. /// </summary> /// <param name="point">The position to use for the calculation.</param> /// <returns>The shortest distance.</returns> @@ -117,12 +117,12 @@ namespace Godot } /// <summary> - /// Returns true if point is inside the plane. + /// Returns <see langword="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> + /// <returns>A <see langword="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; @@ -130,12 +130,12 @@ namespace Godot } /// <summary> - /// Returns the intersection point of the three planes: `b`, `c`, - /// and this plane. If no intersection is found, `null` is returned. + /// Returns the intersection point of the three planes: <paramref name="b"/>, <paramref name="c"/>, + /// and this plane. If no intersection is found, <see langword="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> + /// <returns>The intersection, or <see langword="null"/> if none is found.</returns> public Vector3? Intersect3(Plane b, Plane c) { real_t denom = _normal.Cross(b._normal).Dot(c._normal); @@ -145,21 +145,21 @@ namespace Godot return null; } - Vector3 result = b._normal.Cross(c._normal) * D + - c._normal.Cross(_normal) * b.D + - _normal.Cross(b._normal) * c.D; + Vector3 result = (b._normal.Cross(c._normal) * D) + + (c._normal.Cross(_normal) * b.D) + + (_normal.Cross(b._normal) * c.D); 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. + /// Returns the intersection point of a ray consisting of the position <paramref name="from"/> + /// and the direction normal <paramref name="dir"/> with this plane. + /// If no intersection is found, <see langword="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> + /// <returns>The intersection, or <see langword="null"/> if none is found.</returns> public Vector3? IntersectRay(Vector3 from, Vector3 dir) { real_t den = _normal.Dot(dir); @@ -182,12 +182,12 @@ namespace Godot /// <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. + /// position <paramref name="begin"/> to position <paramref name="end"/> with this plane. + /// If no intersection is found, <see langword="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> + /// <returns>The intersection, or <see langword="null"/> if none is found.</returns> public Vector3? IntersectSegment(Vector3 begin, Vector3 end) { Vector3 segment = begin - end; @@ -210,10 +210,10 @@ namespace Godot } /// <summary> - /// Returns true if `point` is located above the plane. + /// Returns <see langword="true"/> if <paramref name="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> + /// <returns>A <see langword="bool"/> for whether or not the point is above the plane.</returns> public bool IsPointOver(Vector3 point) { return _normal.Dot(point) > D; @@ -236,13 +236,13 @@ namespace Godot } /// <summary> - /// Returns the orthogonal projection of `point` into the plane. + /// Returns the orthogonal projection of <paramref name="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); + return point - (_normal * DistanceTo(point)); } // Constants @@ -251,27 +251,28 @@ namespace Godot 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). + /// A <see cref="Plane"/> that extends in the Y and Z axes (normal vector points +X). /// </summary> - /// <value>Equivalent to `new Plane(1, 0, 0, 0)`.</value> + /// <value>Equivalent to <c>new Plane(1, 0, 0, 0)</c>.</value> public static Plane PlaneYZ { get { return _planeYZ; } } /// <summary> - /// A plane that extends in the X and Z axes (normal vector points +Y). + /// A <see cref="Plane"/> that extends in the X and Z axes (normal vector points +Y). /// </summary> - /// <value>Equivalent to `new Plane(0, 1, 0, 0)`.</value> + /// <value>Equivalent to <c>new Plane(0, 1, 0, 0)</c>.</value> public static Plane PlaneXZ { get { return _planeXZ; } } /// <summary> - /// A plane that extends in the X and Y axes (normal vector points +Z). + /// A <see cref="Plane"/> that extends in the X and Y axes (normal vector points +Z). /// </summary> - /// <value>Equivalent to `new Plane(0, 0, 1, 0)`.</value> + /// <value>Equivalent to <c>new Plane(0, 0, 1, 0)</c>.</value> public static Plane PlaneXY { get { return _planeXY; } } /// <summary> - /// Constructs a plane from four values. `a`, `b` and `c` become the + /// Constructs a <see cref="Plane"/> from four values. + /// <paramref name="a"/>, <paramref name="b"/> and <paramref name="c"/> become the /// components of the resulting plane's <see cref="Normal"/> vector. - /// `d` becomes the plane's distance from the origin. + /// <paramref name="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> @@ -280,22 +281,23 @@ namespace Godot public Plane(real_t a, real_t b, real_t c, real_t d) { _normal = new Vector3(a, b, c); - this.D = d; + D = d; } /// <summary> - /// Constructs a plane from a normal vector and the plane's distance to the origin. + /// Constructs a <see cref="Plane"/> from a <paramref name="normal"/> vector and + /// the plane's distance to the origin <paramref name="d"/>. /// </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; + _normal = normal; + D = d; } /// <summary> - /// Constructs a plane from the three points, given in clockwise order. + /// Constructs a <see cref="Plane"/> from the three points, given in clockwise order. /// </summary> /// <param name="v1">The first point.</param> /// <param name="v2">The second point.</param> @@ -322,6 +324,11 @@ namespace Godot return !left.Equals(right); } + /// <summary> + /// Returns <see langword="true"/> if this plane and <paramref name="obj"/> are equal. + /// </summary> + /// <param name="obj">The other object to compare.</param> + /// <returns>Whether or not the plane and the other object are equal.</returns> public override bool Equals(object obj) { if (obj is Plane) @@ -332,14 +339,19 @@ namespace Godot return false; } + /// <summary> + /// Returns <see langword="true"/> if this plane and <paramref name="other"/> are equal. + /// </summary> + /// <param name="other">The other plane to compare.</param> + /// <returns>Whether or not the planes are equal.</returns> public bool Equals(Plane other) { 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. + /// Returns <see langword="true"/> if this plane and <paramref name="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> @@ -348,16 +360,28 @@ namespace Godot return _normal.IsEqualApprox(other._normal) && Mathf.IsEqualApprox(D, other.D); } + /// <summary> + /// Serves as the hash function for <see cref="Plane"/>. + /// </summary> + /// <returns>A hash code for this plane.</returns> public override int GetHashCode() { return _normal.GetHashCode() ^ D.GetHashCode(); } + /// <summary> + /// Converts this <see cref="Plane"/> to a string. + /// </summary> + /// <returns>A string representation of this plane.</returns> public override string ToString() { return $"{_normal}, {D}"; } + /// <summary> + /// Converts this <see cref="Plane"/> to a string with the given <paramref name="format"/>. + /// </summary> + /// <returns>A string representation of this plane.</returns> public string ToString(string format) { return $"{_normal.ToString(format)}, {D.ToString(format)}"; diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Quaternion.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Quaternion.cs index 0fed55cc30..1694ac0320 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Quaternion.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Quaternion.cs @@ -12,10 +12,10 @@ namespace Godot /// 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 Quaternion only stores rotation. + /// It is similar to <see cref="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 Quaternion 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) @@ -26,19 +26,19 @@ namespace Godot public struct Quaternion : IEquatable<Quaternion> { /// <summary> - /// X component of the quaternion (imaginary `i` axis part). + /// X component of the quaternion (imaginary <c>i</c> 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). + /// Y component of the quaternion (imaginary <c>j</c> 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). + /// Z component of the quaternion (imaginary <c>k</c> axis part). /// Quaternion components should usually not be manipulated directly. /// </summary> public real_t z; @@ -52,7 +52,12 @@ namespace Godot /// <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> + /// <value> + /// <c>[0]</c> is equivalent to <see cref="x"/>, + /// <c>[1]</c> is equivalent to <see cref="y"/>, + /// <c>[2]</c> is equivalent to <see cref="z"/>, + /// <c>[3]</c> is equivalent to <see cref="w"/>. + /// </value> public real_t this[int index] { get @@ -96,7 +101,8 @@ namespace Godot /// <summary> /// Returns the length (magnitude) of the quaternion. /// </summary> - /// <value>Equivalent to `Mathf.Sqrt(LengthSquared)`.</value> + /// <seealso cref="LengthSquared"/> + /// <value>Equivalent to <c>Mathf.Sqrt(LengthSquared)</c>.</value> public real_t Length { get { return Mathf.Sqrt(LengthSquared); } @@ -107,14 +113,14 @@ namespace Godot /// 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> + /// <value>Equivalent to <c>Dot(this)</c>.</value> public real_t LengthSquared { get { return Dot(this); } } /// <summary> - /// Returns the angle between this quaternion and `to`. + /// Returns the angle between this quaternion and <paramref name="to"/>. /// This is the magnitude of the angle you would need to rotate /// by to get from one to the other. /// @@ -131,12 +137,12 @@ namespace Godot } /// <summary> - /// Performs a cubic spherical interpolation between quaternions `preA`, - /// this vector, `b`, and `postB`, by the given amount `t`. + /// Performs a cubic spherical interpolation between quaternions <paramref name="preA"/>, this quaternion, + /// <paramref name="b"/>, and <paramref name="postB"/>, by the given amount <paramref name="weight"/>. /// </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="postB">A quaternion after <paramref name="b"/>.</param> /// <param name="weight">A value on the range of 0.0 to 1.0, representing the amount of interpolation.</param> /// <returns>The interpolated quaternion.</returns> public Quaternion CubicSlerp(Quaternion b, Quaternion preA, Quaternion postB, real_t weight) @@ -154,7 +160,7 @@ namespace Godot /// <returns>The dot product.</returns> public real_t Dot(Quaternion b) { - return x * b.x + y * b.y + z * b.z + w * b.w; + return (x * b.x) + (y * b.y) + (z * b.z) + (w * b.w); } /// <summary> @@ -194,7 +200,7 @@ namespace Godot /// <summary> /// Returns whether the quaternion is normalized or not. /// </summary> - /// <returns>A bool for whether the quaternion is normalized or not.</returns> + /// <returns>A <see langword="bool"/> for whether the quaternion is normalized or not.</returns> public bool IsNormalized() { return Mathf.Abs(LengthSquared - 1) <= Mathf.Epsilon; @@ -211,7 +217,7 @@ namespace Godot /// <summary> /// Returns the result of the spherical linear interpolation between - /// this quaternion and `to` by amount `weight`. + /// this quaternion and <paramref name="to"/> by amount <paramref name="weight"/>. /// /// Note: Both quaternions must be normalized. /// </summary> @@ -274,16 +280,16 @@ namespace Godot // Calculate final values. return new Quaternion ( - scale0 * x + scale1 * to1.x, - scale0 * y + scale1 * to1.y, - scale0 * z + scale1 * to1.z, - scale0 * w + scale1 * to1.w + (scale0 * x) + (scale1 * to1.x), + (scale0 * y) + (scale1 * to1.y), + (scale0 * z) + (scale1 * to1.z), + (scale0 * w) + (scale1 * to1.w) ); } /// <summary> /// Returns the result of the spherical linear interpolation between - /// this quaternion and `to` by amount `weight`, but without + /// this quaternion and <paramref name="to"/> by amount <paramref name="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> @@ -305,10 +311,10 @@ namespace Godot return new Quaternion ( - invFactor * x + newFactor * to.x, - invFactor * y + newFactor * to.y, - invFactor * z + newFactor * to.z, - invFactor * w + newFactor * to.w + (invFactor * x) + (newFactor * to.x), + (invFactor * y) + (newFactor * to.y), + (invFactor * z) + (newFactor * to.z), + (invFactor * w) + (newFactor * to.w) ); } @@ -327,7 +333,7 @@ namespace Godot #endif var u = new Vector3(x, y, z); Vector3 uv = u.Cross(v); - return v + ((uv * w) + u.Cross(uv)) * 2; + return v + (((uv * w) + u.Cross(uv)) * 2); } // Constants @@ -338,15 +344,15 @@ namespace Godot /// 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 Quaternion(0, 0, 0, 1)`.</value> + /// <value>Equivalent to <c>new Quaternion(0, 0, 0, 1)</c>.</value> public static Quaternion Identity { get { return _identity; } } /// <summary> - /// Constructs a quaternion defined by the given values. + /// Constructs a <see cref="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="x">X component of the quaternion (imaginary <c>i</c> axis part).</param> + /// <param name="y">Y component of the quaternion (imaginary <c>j</c> axis part).</param> + /// <param name="z">Z component of the quaternion (imaginary <c>k</c> axis part).</param> /// <param name="w">W component of the quaternion (real part).</param> public Quaternion(real_t x, real_t y, real_t z, real_t w) { @@ -357,7 +363,7 @@ namespace Godot } /// <summary> - /// Constructs a quaternion from the given quaternion. + /// Constructs a <see cref="Quaternion"/> from the given <see cref="Quaternion"/>. /// </summary> /// <param name="q">The existing quaternion.</param> public Quaternion(Quaternion q) @@ -366,46 +372,45 @@ namespace Godot } /// <summary> - /// Constructs a quaternion from the given <see cref="Basis"/>. + /// Constructs a <see cref="Quaternion"/> from the given <see cref="Basis"/>. /// </summary> - /// <param name="basis">The basis to construct from.</param> + /// <param name="basis">The <see cref="Basis"/> to construct from.</param> public Quaternion(Basis basis) { this = basis.Quaternion(); } /// <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), + /// Constructs a <see cref="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> + /// <param name="eulerYXZ">Euler angles that the quaternion will be rotated by.</param> public Quaternion(Vector3 eulerYXZ) { - real_t half_a1 = eulerYXZ.y * 0.5f; - real_t half_a2 = eulerYXZ.x * 0.5f; - real_t half_a3 = eulerYXZ.z * 0.5f; + real_t halfA1 = eulerYXZ.y * 0.5f; + real_t halfA2 = eulerYXZ.x * 0.5f; + real_t halfA3 = eulerYXZ.z * 0.5f; // R = Y(a1).X(a2).Z(a3) convention for Euler angles. // Conversion to quaternion as listed in https://ntrs.nasa.gov/archive/nasa/casi.ntrs.nasa.gov/19770024290.pdf (page A-6) // a3 is the angle of the first rotation, following the notation in this reference. - real_t cos_a1 = Mathf.Cos(half_a1); - real_t sin_a1 = Mathf.Sin(half_a1); - real_t cos_a2 = Mathf.Cos(half_a2); - real_t sin_a2 = Mathf.Sin(half_a2); - real_t cos_a3 = Mathf.Cos(half_a3); - real_t sin_a3 = Mathf.Sin(half_a3); + real_t cosA1 = Mathf.Cos(halfA1); + real_t sinA1 = Mathf.Sin(halfA1); + real_t cosA2 = Mathf.Cos(halfA2); + real_t sinA2 = Mathf.Sin(halfA2); + real_t cosA3 = Mathf.Cos(halfA3); + real_t sinA3 = Mathf.Sin(halfA3); - x = sin_a1 * cos_a2 * sin_a3 + cos_a1 * sin_a2 * cos_a3; - y = sin_a1 * cos_a2 * cos_a3 - cos_a1 * sin_a2 * sin_a3; - z = cos_a1 * cos_a2 * sin_a3 - sin_a1 * sin_a2 * cos_a3; - w = sin_a1 * sin_a2 * sin_a3 + cos_a1 * cos_a2 * cos_a3; + x = (sinA1 * cosA2 * sinA3) + (cosA1 * sinA2 * cosA3); + y = (sinA1 * cosA2 * cosA3) - (cosA1 * sinA2 * sinA3); + z = (cosA1 * cosA2 * sinA3) - (sinA1 * sinA2 * cosA3); + w = (sinA1 * sinA2 * sinA3) + (cosA1 * cosA2 * cosA3); } /// <summary> - /// Constructs a quaternion that will rotate around the given axis + /// Constructs a <see cref="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> @@ -445,10 +450,10 @@ namespace Godot { return new Quaternion ( - left.w * right.x + left.x * right.w + left.y * right.z - left.z * right.y, - left.w * right.y + left.y * right.w + left.z * right.x - left.x * right.z, - left.w * right.z + left.z * right.w + left.x * right.y - left.y * right.x, - left.w * right.w - left.x * right.x - left.y * right.y - left.z * right.z + (left.w * right.x) + (left.x * right.w) + (left.y * right.z) - (left.z * right.y), + (left.w * right.y) + (left.y * right.w) + (left.z * right.x) - (left.x * right.z), + (left.w * right.z) + (left.z * right.w) + (left.x * right.y) - (left.y * right.x), + (left.w * right.w) - (left.x * right.x) - (left.y * right.y) - (left.z * right.z) ); } @@ -471,10 +476,10 @@ namespace Godot { return new Quaternion ( - left.w * right.x + left.y * right.z - left.z * right.y, - left.w * right.y + left.z * right.x - left.x * right.z, - left.w * right.z + left.x * right.y - left.y * right.x, - -left.x * right.x - left.y * right.y - left.z * right.z + (left.w * right.x) + (left.y * right.z) - (left.z * right.y), + (left.w * right.y) + (left.z * right.x) - (left.x * right.z), + (left.w * right.z) + (left.x * right.y) - (left.y * right.x), + -(left.x * right.x) - (left.y * right.y) - (left.z * right.z) ); } @@ -482,10 +487,10 @@ namespace Godot { return new Quaternion ( - right.w * left.x + right.y * left.z - right.z * left.y, - right.w * left.y + right.z * left.x - right.x * left.z, - right.w * left.z + right.x * left.y - right.y * left.x, - -right.x * left.x - right.y * left.y - right.z * left.z + (right.w * left.x) + (right.y * left.z) - (right.z * left.y), + (right.w * left.y) + (right.z * left.x) - (right.x * left.z), + (right.w * left.z) + (right.x * left.y) - (right.y * left.x), + -(right.x * left.x) - (right.y * left.y) - (right.z * left.z) ); } @@ -514,6 +519,11 @@ namespace Godot return !left.Equals(right); } + /// <summary> + /// Returns <see langword="true"/> if this quaternion and <paramref name="obj"/> are equal. + /// </summary> + /// <param name="obj">The other object to compare.</param> + /// <returns>Whether or not the quaternion and the other object are equal.</returns> public override bool Equals(object obj) { if (obj is Quaternion) @@ -524,14 +534,19 @@ namespace Godot return false; } + /// <summary> + /// Returns <see langword="true"/> if this quaternion and <paramref name="other"/> are equal. + /// </summary> + /// <param name="other">The other quaternion to compare.</param> + /// <returns>Whether or not the quaternions are equal.</returns> public bool Equals(Quaternion other) { 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. + /// Returns <see langword="true"/> if this quaternion and <paramref name="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> @@ -540,16 +555,28 @@ namespace Godot return Mathf.IsEqualApprox(x, other.x) && Mathf.IsEqualApprox(y, other.y) && Mathf.IsEqualApprox(z, other.z) && Mathf.IsEqualApprox(w, other.w); } + /// <summary> + /// Serves as the hash function for <see cref="Quaternion"/>. + /// </summary> + /// <returns>A hash code for this quaternion.</returns> public override int GetHashCode() { return y.GetHashCode() ^ x.GetHashCode() ^ z.GetHashCode() ^ w.GetHashCode(); } + /// <summary> + /// Converts this <see cref="Quaternion"/> to a string. + /// </summary> + /// <returns>A string representation of this quaternion.</returns> public override string ToString() { return $"({x}, {y}, {z}, {w})"; } + /// <summary> + /// Converts this <see cref="Quaternion"/> to a string with the given <paramref name="format"/>. + /// </summary> + /// <returns>A string representation of this quaternion.</returns> public string ToString(string format) { return $"({x.ToString(format)}, {y.ToString(format)}, {z.ToString(format)}, {w.ToString(format)})"; diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/RID.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/RID.cs index 94761531b1..1588869ec0 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/RID.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/RID.cs @@ -3,9 +3,15 @@ using System.Runtime.CompilerServices; namespace Godot { + /// <summary> + /// The RID type is used to access the unique integer ID of a resource. + /// They are opaque, which means they do not grant access to the associated + /// resource by themselves. They are used by and with the low-level Server + /// classes such as <see cref="RenderingServer"/>. + /// </summary> public sealed partial class RID : IDisposable { - private bool disposed = false; + private bool _disposed = false; internal IntPtr ptr; @@ -14,7 +20,7 @@ namespace Godot if (instance == null) throw new NullReferenceException($"The instance of type {nameof(RID)} is null."); - if (instance.disposed) + if (instance._disposed) throw new ObjectDisposedException(instance.GetType().FullName); return instance.ptr; @@ -25,6 +31,9 @@ namespace Godot Dispose(false); } + /// <summary> + /// Disposes of this <see cref="RID"/>. + /// </summary> public void Dispose() { Dispose(true); @@ -33,7 +42,7 @@ namespace Godot private void Dispose(bool disposing) { - if (disposed) + if (_disposed) return; if (ptr != IntPtr.Zero) @@ -42,7 +51,7 @@ namespace Godot ptr = IntPtr.Zero; } - disposed = true; + _disposed = true; } internal RID(IntPtr ptr) @@ -50,6 +59,9 @@ namespace Godot this.ptr = ptr; } + /// <summary> + /// The pointer to the native instance of this <see cref="RID"/>. + /// </summary> public IntPtr NativeInstance { get { return ptr; } @@ -60,25 +72,36 @@ namespace Godot this.ptr = IntPtr.Zero; } + /// <summary> + /// Constructs a new <see cref="RID"/> for the given <see cref="Object"/> <paramref name="from"/>. + /// </summary> public RID(Object from) { this.ptr = godot_icall_RID_Ctor(Object.GetPtr(from)); } + /// <summary> + /// Returns the ID of the referenced resource. + /// </summary> + /// <returns>The ID of the referenced resource.</returns> public int GetId() { - return godot_icall_RID_get_id(RID.GetPtr(this)); + return godot_icall_RID_get_id(GetPtr(this)); } + /// <summary> + /// Converts this <see cref="RID"/> to a string. + /// </summary> + /// <returns>A string representation of this RID.</returns> public override string ToString() => "[RID]"; [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static IntPtr godot_icall_RID_Ctor(IntPtr from); + internal static extern IntPtr godot_icall_RID_Ctor(IntPtr from); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static void godot_icall_RID_Dtor(IntPtr ptr); + internal static extern void godot_icall_RID_Dtor(IntPtr ptr); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static int godot_icall_RID_get_id(IntPtr ptr); + internal static extern int godot_icall_RID_get_id(IntPtr ptr); } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs index dec69c7f94..1d001fba2d 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs @@ -20,7 +20,7 @@ namespace Godot private Vector2 _size; /// <summary> - /// Beginning corner. Typically has values lower than End. + /// Beginning corner. Typically has values lower than <see cref="End"/>. /// </summary> /// <value>Directly uses a private field.</value> public Vector2 Position @@ -30,7 +30,7 @@ namespace Godot } /// <summary> - /// Size from Position to End. Typically all components are positive. + /// Size from <see cref="Position"/> to <see cref="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> @@ -41,10 +41,13 @@ namespace Godot } /// <summary> - /// Ending corner. This is calculated as <see cref="Position"/> plus - /// <see cref="Size"/>. Setting this value will change the size. + /// 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> + /// <value> + /// Getting is equivalent to <paramref name="value"/> = <see cref="Position"/> + <see cref="Size"/>, + /// setting is equivalent to <see cref="Size"/> = <paramref name="value"/> - <see cref="Position"/> + /// </value> public Vector2 End { get { return _position + _size; } @@ -52,7 +55,7 @@ namespace Godot } /// <summary> - /// The area of this Rect2. + /// The area of this <see cref="Rect2"/>. /// </summary> /// <value>Equivalent to <see cref="GetArea()"/>.</value> public real_t Area @@ -61,10 +64,10 @@ namespace Godot } /// <summary> - /// Returns a Rect2 with equivalent position and size, modified so that + /// Returns a <see cref="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 Rect2.</returns> + /// <returns>The modified <see cref="Rect2"/>.</returns> public Rect2 Abs() { Vector2 end = End; @@ -73,14 +76,17 @@ namespace Godot } /// <summary> - /// Returns the intersection of this Rect2 and `b`. - /// If the rectangles do not intersect, an empty Rect2 is returned. + /// Returns the intersection of this <see cref="Rect2"/> and <paramref name="b"/>. + /// If the rectangles do not intersect, an empty <see cref="Rect2"/> is returned. /// </summary> - /// <param name="b">The other Rect2.</param> - /// <returns>The intersection of this Rect2 and `b`, or an empty Rect2 if they do not intersect.</returns> + /// <param name="b">The other <see cref="Rect2"/>.</param> + /// <returns> + /// The intersection of this <see cref="Rect2"/> and <paramref name="b"/>, + /// or an empty <see cref="Rect2"/> if they do not intersect. + /// </returns> public Rect2 Intersection(Rect2 b) { - var newRect = b; + Rect2 newRect = b; if (!Intersects(newRect)) { @@ -100,10 +106,12 @@ namespace Godot } /// <summary> - /// Returns true if this Rect2 completely encloses another one. + /// Returns <see langword="true"/> if this <see cref="Rect2"/> completely encloses another one. /// </summary> - /// <param name="b">The other Rect2 that may be enclosed.</param> - /// <returns>A bool for whether or not this Rect2 encloses `b`.</returns> + /// <param name="b">The other <see cref="Rect2"/> that may be enclosed.</param> + /// <returns> + /// A <see langword="bool"/> for whether or not this <see cref="Rect2"/> encloses <paramref name="b"/>. + /// </returns> public bool Encloses(Rect2 b) { return b._position.x >= _position.x && b._position.y >= _position.y && @@ -112,13 +120,13 @@ namespace Godot } /// <summary> - /// Returns this Rect2 expanded to include a given point. + /// Returns this <see cref="Rect2"/> expanded to include a given point. /// </summary> /// <param name="to">The point to include.</param> - /// <returns>The expanded Rect2.</returns> + /// <returns>The expanded <see cref="Rect2"/>.</returns> public Rect2 Expand(Vector2 to) { - var expanded = this; + Rect2 expanded = this; Vector2 begin = expanded._position; Vector2 end = expanded._position + expanded._size; @@ -148,7 +156,7 @@ namespace Godot } /// <summary> - /// Returns the area of the Rect2. + /// Returns the area of the <see cref="Rect2"/>. /// </summary> /// <returns>The area.</returns> public real_t GetArea() @@ -157,13 +165,16 @@ namespace Godot } /// <summary> - /// Returns a copy of the Rect2 grown by the specified amount on all sides. + /// Returns a copy of the <see cref="Rect2"/> grown by the specified amount + /// on all sides. /// </summary> + /// <seealso cref="GrowIndividual(real_t, real_t, real_t, real_t)"/> + /// <seealso cref="GrowSide(Side, real_t)"/> /// <param name="by">The amount to grow by.</param> - /// <returns>The grown Rect2.</returns> + /// <returns>The grown <see cref="Rect2"/>.</returns> public Rect2 Grow(real_t by) { - var g = this; + Rect2 g = this; g._position.x -= by; g._position.y -= by; @@ -174,16 +185,19 @@ namespace Godot } /// <summary> - /// Returns a copy of the Rect2 grown by the specified amount on each side individually. + /// Returns a copy of the <see cref="Rect2"/> grown by the specified amount + /// on each side individually. /// </summary> + /// <seealso cref="Grow(real_t)"/> + /// <seealso cref="GrowSide(Side, real_t)"/> /// <param name="left">The amount to grow by on the left side.</param> /// <param name="top">The amount to grow by on the top side.</param> /// <param name="right">The amount to grow by on the right side.</param> /// <param name="bottom">The amount to grow by on the bottom side.</param> - /// <returns>The grown Rect2.</returns> + /// <returns>The grown <see cref="Rect2"/>.</returns> public Rect2 GrowIndividual(real_t left, real_t top, real_t right, real_t bottom) { - var g = this; + Rect2 g = this; g._position.x -= left; g._position.y -= top; @@ -194,14 +208,17 @@ namespace Godot } /// <summary> - /// Returns a copy of the Rect2 grown by the specified amount on the specified Side. + /// Returns a copy of the <see cref="Rect2"/> grown by the specified amount + /// on the specified <see cref="Side"/>. /// </summary> + /// <seealso cref="Grow(real_t)"/> + /// <seealso cref="GrowIndividual(real_t, real_t, real_t, real_t)"/> /// <param name="side">The side to grow.</param> /// <param name="by">The amount to grow by.</param> - /// <returns>The grown Rect2.</returns> + /// <returns>The grown <see cref="Rect2"/>.</returns> public Rect2 GrowSide(Side side, real_t by) { - var g = this; + Rect2 g = this; g = g.GrowIndividual(Side.Left == side ? by : 0, Side.Top == side ? by : 0, @@ -212,19 +229,25 @@ namespace Godot } /// <summary> - /// Returns true if the Rect2 is flat or empty, or false otherwise. + /// Returns <see langword="true"/> if the <see cref="Rect2"/> is flat or empty, + /// or <see langword="false"/> otherwise. /// </summary> - /// <returns>A bool for whether or not the Rect2 has area.</returns> + /// <returns> + /// A <see langword="bool"/> for whether or not the <see cref="Rect2"/> 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. + /// Returns <see langword="true"/> if the <see cref="Rect2"/> contains a point, + /// or <see langword="false"/> otherwise. /// </summary> /// <param name="point">The point to check.</param> - /// <returns>A bool for whether or not the Rect2 contains `point`.</returns> + /// <returns> + /// A <see langword="bool"/> for whether or not the <see cref="Rect2"/> contains <paramref name="point"/>. + /// </returns> public bool HasPoint(Vector2 point) { if (point.x < _position.x) @@ -241,15 +264,16 @@ namespace Godot } /// <summary> - /// Returns true if the Rect2 overlaps with `b` + /// Returns <see langword="true"/> if the <see cref="Rect2"/> overlaps with <paramref name="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. + /// If <paramref name="includeBorders"/> is <see langword="true"/>, + /// they will also be considered overlapping if their borders touch, + /// even without intersection. /// </summary> - /// <param name="b">The other Rect2 to check for intersections with.</param> + /// <param name="b">The other <see cref="Rect2"/> 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> + /// <returns>A <see langword="bool"/> for whether or not they are intersecting.</returns> public bool Intersects(Rect2 b, bool includeBorders = false) { if (includeBorders) @@ -295,10 +319,10 @@ namespace Godot } /// <summary> - /// Returns a larger Rect2 that contains this Rect2 and `b`. + /// Returns a larger <see cref="Rect2"/> that contains this <see cref="Rect2"/> and <paramref name="b"/>. /// </summary> - /// <param name="b">The other Rect2.</param> - /// <returns>The merged Rect2.</returns> + /// <param name="b">The other <see cref="Rect2"/>.</param> + /// <returns>The merged <see cref="Rect2"/>.</returns> public Rect2 Merge(Rect2 b) { Rect2 newRect; @@ -315,7 +339,7 @@ namespace Godot } /// <summary> - /// Constructs a Rect2 from a position and size. + /// Constructs a <see cref="Rect2"/> from a position and size. /// </summary> /// <param name="position">The position.</param> /// <param name="size">The size.</param> @@ -326,7 +350,7 @@ namespace Godot } /// <summary> - /// Constructs a Rect2 from a position, width, and height. + /// Constructs a <see cref="Rect2"/> from a position, width, and height. /// </summary> /// <param name="position">The position.</param> /// <param name="width">The width.</param> @@ -338,7 +362,7 @@ namespace Godot } /// <summary> - /// Constructs a Rect2 from x, y, and size. + /// Constructs a <see cref="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> @@ -350,7 +374,7 @@ namespace Godot } /// <summary> - /// Constructs a Rect2 from x, y, width, and height. + /// Constructs a <see cref="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> @@ -372,6 +396,11 @@ namespace Godot return !left.Equals(right); } + /// <summary> + /// Returns <see langword="true"/> if this rect and <paramref name="obj"/> are equal. + /// </summary> + /// <param name="obj">The other object to compare.</param> + /// <returns>Whether or not the rect and the other object are equal.</returns> public override bool Equals(object obj) { if (obj is Rect2) @@ -382,32 +411,49 @@ namespace Godot return false; } + /// <summary> + /// Returns <see langword="true"/> if this rect and <paramref name="other"/> are equal. + /// </summary> + /// <param name="other">The other rect to compare.</param> + /// <returns>Whether or not the rects are equal.</returns> public bool Equals(Rect2 other) { return _position.Equals(other._position) && _size.Equals(other._size); } /// <summary> - /// Returns true if this Rect2 and `other` are approximately equal, by running - /// <see cref="Vector2.IsEqualApprox(Vector2)"/> on each component. + /// Returns <see langword="true"/> if this rect and <paramref name="other"/> are approximately equal, + /// by running <see cref="Vector2.IsEqualApprox(Vector2)"/> on each component. /// </summary> - /// <param name="other">The other Rect2 to compare.</param> - /// <returns>Whether or not the Rect2s are approximately equal.</returns> + /// <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); } + /// <summary> + /// Serves as the hash function for <see cref="Rect2"/>. + /// </summary> + /// <returns>A hash code for this rect.</returns> public override int GetHashCode() { return _position.GetHashCode() ^ _size.GetHashCode(); } + /// <summary> + /// Converts this <see cref="Rect2"/> to a string. + /// </summary> + /// <returns>A string representation of this rect.</returns> public override string ToString() { return $"{_position}, {_size}"; } + /// <summary> + /// Converts this <see cref="Rect2"/> to a string with the given <paramref name="format"/>. + /// </summary> + /// <returns>A string representation of this rect.</returns> public string ToString(string format) { return $"{_position.ToString(format)}, {_size.ToString(format)}"; diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2i.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2i.cs index 7fb6614d2c..250b0d0baf 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2i.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2i.cs @@ -15,7 +15,7 @@ namespace Godot private Vector2i _size; /// <summary> - /// Beginning corner. Typically has values lower than End. + /// Beginning corner. Typically has values lower than <see cref="End"/>. /// </summary> /// <value>Directly uses a private field.</value> public Vector2i Position @@ -25,7 +25,7 @@ namespace Godot } /// <summary> - /// Size from Position to End. Typically all components are positive. + /// Size from <see cref="Position"/> to <see cref="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> @@ -36,10 +36,13 @@ namespace Godot } /// <summary> - /// Ending corner. This is calculated as <see cref="Position"/> plus - /// <see cref="Size"/>. Setting this value will change the size. + /// 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> + /// <value> + /// Getting is equivalent to <paramref name="value"/> = <see cref="Position"/> + <see cref="Size"/>, + /// setting is equivalent to <see cref="Size"/> = <paramref name="value"/> - <see cref="Position"/> + /// </value> public Vector2i End { get { return _position + _size; } @@ -47,7 +50,7 @@ namespace Godot } /// <summary> - /// The area of this Rect2i. + /// The area of this <see cref="Rect2i"/>. /// </summary> /// <value>Equivalent to <see cref="GetArea()"/>.</value> public int Area @@ -56,10 +59,10 @@ namespace Godot } /// <summary> - /// Returns a Rect2i with equivalent position and size, modified so that + /// Returns a <see cref="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 Rect2i.</returns> + /// <returns>The modified <see cref="Rect2i"/>.</returns> public Rect2i Abs() { Vector2i end = End; @@ -68,14 +71,17 @@ namespace Godot } /// <summary> - /// Returns the intersection of this Rect2i and `b`. - /// If the rectangles do not intersect, an empty Rect2i is returned. + /// Returns the intersection of this <see cref="Rect2i"/> and <paramref name="b"/>. + /// If the rectangles do not intersect, an empty <see cref="Rect2i"/> is returned. /// </summary> - /// <param name="b">The other Rect2i.</param> - /// <returns>The intersection of this Rect2i and `b`, or an empty Rect2i if they do not intersect.</returns> + /// <param name="b">The other <see cref="Rect2i"/>.</param> + /// <returns> + /// The intersection of this <see cref="Rect2i"/> and <paramref name="b"/>, + /// or an empty <see cref="Rect2i"/> if they do not intersect. + /// </returns> public Rect2i Intersection(Rect2i b) { - var newRect = b; + Rect2i newRect = b; if (!Intersects(newRect)) { @@ -95,10 +101,12 @@ namespace Godot } /// <summary> - /// Returns true if this Rect2i completely encloses another one. + /// Returns <see langword="true"/> if this <see cref="Rect2i"/> completely encloses another one. /// </summary> - /// <param name="b">The other Rect2i that may be enclosed.</param> - /// <returns>A bool for whether or not this Rect2i encloses `b`.</returns> + /// <param name="b">The other <see cref="Rect2i"/> that may be enclosed.</param> + /// <returns> + /// A <see langword="bool"/> for whether or not this <see cref="Rect2i"/> encloses <paramref name="b"/>. + /// </returns> public bool Encloses(Rect2i b) { return b._position.x >= _position.x && b._position.y >= _position.y && @@ -107,13 +115,13 @@ namespace Godot } /// <summary> - /// Returns this Rect2i expanded to include a given point. + /// Returns this <see cref="Rect2i"/> expanded to include a given point. /// </summary> /// <param name="to">The point to include.</param> - /// <returns>The expanded Rect2i.</returns> + /// <returns>The expanded <see cref="Rect2i"/>.</returns> public Rect2i Expand(Vector2i to) { - var expanded = this; + Rect2i expanded = this; Vector2i begin = expanded._position; Vector2i end = expanded._position + expanded._size; @@ -143,7 +151,7 @@ namespace Godot } /// <summary> - /// Returns the area of the Rect2. + /// Returns the area of the <see cref="Rect2"/>. /// </summary> /// <returns>The area.</returns> public int GetArea() @@ -152,13 +160,16 @@ namespace Godot } /// <summary> - /// Returns a copy of the Rect2i grown by the specified amount on all sides. + /// Returns a copy of the <see cref="Rect2i"/> grown by the specified amount + /// on all sides. /// </summary> + /// <seealso cref="GrowIndividual(int, int, int, int)"/> + /// <seealso cref="GrowSide(Side, int)"/> /// <param name="by">The amount to grow by.</param> - /// <returns>The grown Rect2i.</returns> + /// <returns>The grown <see cref="Rect2i"/>.</returns> public Rect2i Grow(int by) { - var g = this; + Rect2i g = this; g._position.x -= by; g._position.y -= by; @@ -169,16 +180,19 @@ namespace Godot } /// <summary> - /// Returns a copy of the Rect2i grown by the specified amount on each side individually. + /// Returns a copy of the <see cref="Rect2i"/> grown by the specified amount + /// on each side individually. /// </summary> + /// <seealso cref="Grow(int)"/> + /// <seealso cref="GrowSide(Side, int)"/> /// <param name="left">The amount to grow by on the left side.</param> /// <param name="top">The amount to grow by on the top side.</param> /// <param name="right">The amount to grow by on the right side.</param> /// <param name="bottom">The amount to grow by on the bottom side.</param> - /// <returns>The grown Rect2i.</returns> + /// <returns>The grown <see cref="Rect2i"/>.</returns> public Rect2i GrowIndividual(int left, int top, int right, int bottom) { - var g = this; + Rect2i g = this; g._position.x -= left; g._position.y -= top; @@ -189,14 +203,17 @@ namespace Godot } /// <summary> - /// Returns a copy of the Rect2i grown by the specified amount on the specified Side. + /// Returns a copy of the <see cref="Rect2i"/> grown by the specified amount + /// on the specified <see cref="Side"/>. /// </summary> + /// <seealso cref="Grow(int)"/> + /// <seealso cref="GrowIndividual(int, int, int, int)"/> /// <param name="side">The side to grow.</param> /// <param name="by">The amount to grow by.</param> - /// <returns>The grown Rect2i.</returns> + /// <returns>The grown <see cref="Rect2i"/>.</returns> public Rect2i GrowSide(Side side, int by) { - var g = this; + Rect2i g = this; g = g.GrowIndividual(Side.Left == side ? by : 0, Side.Top == side ? by : 0, @@ -207,19 +224,25 @@ namespace Godot } /// <summary> - /// Returns true if the Rect2i is flat or empty, or false otherwise. + /// Returns <see langword="true"/> if the <see cref="Rect2i"/> is flat or empty, + /// or <see langword="false"/> otherwise. /// </summary> - /// <returns>A bool for whether or not the Rect2i has area.</returns> + /// <returns> + /// A <see langword="bool"/> for whether or not the <see cref="Rect2i"/> has area. + /// </returns> public bool HasNoArea() { return _size.x <= 0 || _size.y <= 0; } /// <summary> - /// Returns true if the Rect2i contains a point, or false otherwise. + /// Returns <see langword="true"/> if the <see cref="Rect2i"/> contains a point, + /// or <see langword="false"/> otherwise. /// </summary> /// <param name="point">The point to check.</param> - /// <returns>A bool for whether or not the Rect2i contains `point`.</returns> + /// <returns> + /// A <see langword="bool"/> for whether or not the <see cref="Rect2i"/> contains <paramref name="point"/>. + /// </returns> public bool HasPoint(Vector2i point) { if (point.x < _position.x) @@ -236,15 +259,16 @@ namespace Godot } /// <summary> - /// Returns true if the Rect2i overlaps with `b` + /// Returns <see langword="true"/> if the <see cref="Rect2i"/> overlaps with <paramref name="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. + /// If <paramref name="includeBorders"/> is <see langword="true"/>, + /// they will also be considered overlapping if their borders touch, + /// even without intersection. /// </summary> - /// <param name="b">The other Rect2i to check for intersections with.</param> + /// <param name="b">The other <see cref="Rect2i"/> 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> + /// <returns>A <see langword="bool"/> for whether or not they are intersecting.</returns> public bool Intersects(Rect2i b, bool includeBorders = false) { if (includeBorders) @@ -274,10 +298,10 @@ namespace Godot } /// <summary> - /// Returns a larger Rect2i that contains this Rect2i and `b`. + /// Returns a larger <see cref="Rect2i"/> that contains this <see cref="Rect2i"/> and <paramref name="b"/>. /// </summary> - /// <param name="b">The other Rect2i.</param> - /// <returns>The merged Rect2i.</returns> + /// <param name="b">The other <see cref="Rect2i"/>.</param> + /// <returns>The merged <see cref="Rect2i"/>.</returns> public Rect2i Merge(Rect2i b) { Rect2i newRect; @@ -294,7 +318,7 @@ namespace Godot } /// <summary> - /// Constructs a Rect2i from a position and size. + /// Constructs a <see cref="Rect2i"/> from a position and size. /// </summary> /// <param name="position">The position.</param> /// <param name="size">The size.</param> @@ -305,7 +329,7 @@ namespace Godot } /// <summary> - /// Constructs a Rect2i from a position, width, and height. + /// Constructs a <see cref="Rect2i"/> from a position, width, and height. /// </summary> /// <param name="position">The position.</param> /// <param name="width">The width.</param> @@ -317,7 +341,7 @@ namespace Godot } /// <summary> - /// Constructs a Rect2i from x, y, and size. + /// Constructs a <see cref="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> @@ -329,7 +353,7 @@ namespace Godot } /// <summary> - /// Constructs a Rect2i from x, y, width, and height. + /// Constructs a <see cref="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> @@ -351,16 +375,29 @@ namespace Godot return !left.Equals(right); } + /// <summary> + /// Converts this <see cref="Rect2i"/> to a <see cref="Rect2"/>. + /// </summary> + /// <param name="value">The rect to convert.</param> public static implicit operator Rect2(Rect2i value) { return new Rect2(value._position, value._size); } + /// <summary> + /// Converts a <see cref="Rect2"/> to a <see cref="Rect2i"/>. + /// </summary> + /// <param name="value">The rect to convert.</param> public static explicit operator Rect2i(Rect2 value) { return new Rect2i((Vector2i)value.Position, (Vector2i)value.Size); } + /// <summary> + /// Returns <see langword="true"/> if this rect and <paramref name="obj"/> are equal. + /// </summary> + /// <param name="obj">The other object to compare.</param> + /// <returns>Whether or not the rect and the other object are equal.</returns> public override bool Equals(object obj) { if (obj is Rect2i) @@ -371,21 +408,38 @@ namespace Godot return false; } + /// <summary> + /// Returns <see langword="true"/> if this rect and <paramref name="other"/> are equal. + /// </summary> + /// <param name="other">The other rect to compare.</param> + /// <returns>Whether or not the rects are equal.</returns> public bool Equals(Rect2i other) { return _position.Equals(other._position) && _size.Equals(other._size); } + /// <summary> + /// Serves as the hash function for <see cref="Rect2i"/>. + /// </summary> + /// <returns>A hash code for this rect.</returns> public override int GetHashCode() { return _position.GetHashCode() ^ _size.GetHashCode(); } + /// <summary> + /// Converts this <see cref="Rect2i"/> to a string. + /// </summary> + /// <returns>A string representation of this rect.</returns> public override string ToString() { return $"{_position}, {_size}"; } + /// <summary> + /// Converts this <see cref="Rect2i"/> to a string with the given <paramref name="format"/>. + /// </summary> + /// <returns>A string representation of this rect.</returns> public string ToString(string format) { return $"{_position.ToString(format)}, {_size.ToString(format)}"; diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/SignalAwaiter.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/SignalAwaiter.cs index 4dc630238b..2ba0493002 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/SignalAwaiter.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/SignalAwaiter.cs @@ -5,9 +5,9 @@ namespace Godot { public class SignalAwaiter : IAwaiter<object[]>, IAwaitable<object[]> { - private bool completed; - private object[] result; - private Action action; + private bool _completed; + private object[] _result; + private Action _action; public SignalAwaiter(Object source, StringName signal, Object target) { @@ -15,24 +15,24 @@ namespace Godot } [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static Error godot_icall_SignalAwaiter_connect(IntPtr source, IntPtr signal, IntPtr target, SignalAwaiter awaiter); + internal static extern Error godot_icall_SignalAwaiter_connect(IntPtr source, IntPtr signal, IntPtr target, SignalAwaiter awaiter); public bool IsCompleted { get { - return completed; + return _completed; } } public void OnCompleted(Action action) { - this.action = action; + this._action = action; } public object[] GetResult() { - return result; + return _result; } public IAwaiter<object[]> GetAwaiter() @@ -42,13 +42,9 @@ namespace Godot internal void SignalCallback(object[] args) { - completed = true; - result = args; - - if (action != null) - { - action(); - } + _completed = true; + _result = args; + _action?.Invoke(); } } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/SignalInfo.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/SignalInfo.cs index dc92de7a61..5680c9d55a 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/SignalInfo.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/SignalInfo.cs @@ -1,13 +1,28 @@ namespace Godot { + /// <summary> + /// Represents a signal defined in an object. + /// </summary> public struct SignalInfo { private readonly Object _owner; private readonly StringName _signalName; + /// <summary> + /// Object that contains the signal. + /// </summary> public Object Owner => _owner; + /// <summary> + /// Name of the signal. + /// </summary> public StringName Name => _signalName; + /// <summary> + /// Creates a new <see cref="Signal"/> with the name <paramref name="name"/> + /// in the specified <paramref name="owner"/>. + /// </summary> + /// <param name="owner">Object that contains the signal.</param> + /// <param name="name">Name of the signal.</param> public SignalInfo(Object owner, StringName name) { _owner = owner; diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs index bc598248bc..6b3eb09581 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs @@ -8,6 +8,9 @@ using System.Text.RegularExpressions; namespace Godot { + /// <summary> + /// Extension methods to manipulate strings. + /// </summary> public static class StringExtensions { private static int GetSliceCount(this string instance, string splitter) @@ -64,6 +67,11 @@ namespace Godot /// <summary> /// If the string is a path to a file, return the path to the file without the extension. /// </summary> + /// <seealso cref="GetExtension(string)"/> + /// <seealso cref="GetBaseDir(string)"/> + /// <seealso cref="GetFile(string)"/> + /// <param name="instance">The path to a file.</param> + /// <returns>The path to the file without the extension.</returns> public static string GetBaseName(this string instance) { int index = instance.LastIndexOf('.'); @@ -75,19 +83,25 @@ namespace Godot } /// <summary> - /// Return <see langword="true"/> if the strings begins with the given string. + /// Returns <see langword="true"/> if the strings begins + /// with the given string <paramref name="text"/>. /// </summary> + /// <param name="instance">The string to check.</param> + /// <param name="text">The beginning string.</param> + /// <returns>If the string begins with the given string.</returns> public static bool BeginsWith(this string instance, string text) { return instance.StartsWith(text); } /// <summary> - /// Return the bigrams (pairs of consecutive letters) of this string. + /// Returns the bigrams (pairs of consecutive letters) of this string. /// </summary> + /// <param name="instance">The string that will be used.</param> + /// <returns>The bigrams of this string.</returns> public static string[] Bigrams(this string instance) { - var b = new string[instance.Length - 1]; + string[] b = new string[instance.Length - 1]; for (int i = 0; i < b.Length; i++) { @@ -99,8 +113,8 @@ namespace Godot /// <summary> /// Converts a string containing a binary number into an integer. - /// Binary strings can either be prefixed with `0b` or not, - /// and they can also start with a `-` before the optional prefix. + /// Binary strings can either be prefixed with <c>0b</c> or not, + /// and they can also start with a <c>-</c> before the optional prefix. /// </summary> /// <param name="instance">The string to convert.</param> /// <returns>The converted string.</returns> @@ -128,8 +142,14 @@ namespace Godot } /// <summary> - /// Return the amount of substrings in string. + /// Returns the amount of substrings <paramref name="what"/> in the string. /// </summary> + /// <param name="instance">The string where the substring will be searched.</param> + /// <param name="what">The substring that will be counted.</param> + /// <param name="caseSensitive">If the search is case sensitive.</param> + /// <param name="from">Index to start searching from.</param> + /// <param name="to">Index to stop searching at.</param> + /// <returns>Amount of substrings in the string.</returns> public static int Count(this string instance, string what, bool caseSensitive = true, int from = 0, int to = 0) { if (what.Length == 0) @@ -188,8 +208,10 @@ namespace Godot } /// <summary> - /// Return a copy of the string with special characters escaped using the C language standard. + /// Returns a copy of the string with special characters escaped using the C language standard. /// </summary> + /// <param name="instance">The string to escape.</param> + /// <returns>The escaped string.</returns> public static string CEscape(this string instance) { var sb = new StringBuilder(string.Copy(instance)); @@ -210,9 +232,11 @@ namespace Godot } /// <summary> - /// Return a copy of the string with escaped characters replaced by their meanings + /// Returns a copy of the string with escaped characters replaced by their meanings /// according to the C language standard. /// </summary> + /// <param name="instance">The string to unescape.</param> + /// <returns>The unescaped string.</returns> public static string CUnescape(this string instance) { var sb = new StringBuilder(string.Copy(instance)); @@ -233,15 +257,17 @@ namespace Godot } /// <summary> - /// Change the case of some letters. Replace underscores with spaces, convert all letters + /// Changes the case of some letters. Replace underscores with spaces, convert all letters /// to lowercase then capitalize first and every letter following the space character. /// For <c>capitalize camelCase mixed_with_underscores</c> it will return /// <c>Capitalize Camelcase Mixed With Underscores</c>. /// </summary> + /// <param name="instance">The string to capitalize.</param> + /// <returns>The capitalized string.</returns> public static string Capitalize(this string instance) { string aux = instance.Replace("_", " ").ToLower(); - var cap = string.Empty; + string cap = string.Empty; for (int i = 0; i < aux.GetSliceCount(" "); i++) { @@ -259,16 +285,27 @@ namespace Godot } /// <summary> - /// Perform a case-sensitive comparison to another string, return -1 if less, 0 if equal and +1 if greater. + /// Performs a case-sensitive comparison to another string, return -1 if less, 0 if equal and +1 if greater. /// </summary> + /// <seealso cref="NocasecmpTo(string, string)"/> + /// <seealso cref="CompareTo(string, string, bool)"/> + /// <param name="instance">The string to compare.</param> + /// <param name="to">The other string to compare.</param> + /// <returns>-1 if less, 0 if equal and +1 if greater.</returns> public static int CasecmpTo(this string instance, string to) { return instance.CompareTo(to, caseSensitive: true); } /// <summary> - /// Perform a comparison to another string, return -1 if less, 0 if equal and +1 if greater. + /// Performs a comparison to another string, return -1 if less, 0 if equal and +1 if greater. /// </summary> + /// <param name="instance">The string to compare.</param> + /// <param name="to">The other string to compare.</param> + /// <param name="caseSensitive"> + /// If <see langword="true"/>, the comparison will be case sensitive. + /// </param> + /// <returns>-1 if less, 0 if equal and +1 if greater.</returns> public static int CompareTo(this string instance, string to, bool caseSensitive = true) { if (string.IsNullOrEmpty(instance)) @@ -321,8 +358,12 @@ namespace Godot } /// <summary> - /// Return <see langword="true"/> if the strings ends with the given string. + /// Returns <see langword="true"/> if the strings ends + /// with the given string <paramref name="text"/>. /// </summary> + /// <param name="instance">The string to check.</param> + /// <param name="text">The ending string.</param> + /// <returns>If the string ends with the given string.</returns> public static bool EndsWith(this string instance, string text) { return instance.EndsWith(text); @@ -331,14 +372,36 @@ namespace Godot /// <summary> /// Erase <paramref name="chars"/> characters from the string starting from <paramref name="pos"/>. /// </summary> + /// <param name="instance">The string to modify.</param> + /// <param name="pos">Starting position from which to erase.</param> + /// <param name="chars">Amount of characters to erase.</param> public static void Erase(this StringBuilder instance, int pos, int chars) { instance.Remove(pos, chars); } /// <summary> - /// If the string is a path to a file, return the extension. - /// </summary> + /// Returns the extension without the leading period character (<c>.</c>) + /// if the string is a valid file name or path. If the string does not contain + /// an extension, returns an empty string instead. + /// </summary> + /// <example> + /// <code> + /// GD.Print("/path/to/file.txt".GetExtension()) // "txt" + /// GD.Print("file.txt".GetExtension()) // "txt" + /// GD.Print("file.sample.txt".GetExtension()) // "txt" + /// GD.Print(".txt".GetExtension()) // "txt" + /// GD.Print("file.txt.".GetExtension()) // "" (empty string) + /// GD.Print("file.txt..".GetExtension()) // "" (empty string) + /// GD.Print("txt".GetExtension()) // "" (empty string) + /// GD.Print("".GetExtension()) // "" (empty string) + /// </code> + /// </example> + /// <seealso cref="GetBaseName(string)"/> + /// <seealso cref="GetBaseDir(string)"/> + /// <seealso cref="GetFile(string)"/> + /// <param name="instance">The path to a file.</param> + /// <returns>The extension of the file or an empty string.</returns> public static string GetExtension(this string instance) { int pos = instance.FindLast("."); @@ -352,6 +415,10 @@ namespace Godot /// <summary> /// Find the first occurrence of a substring. Optionally, the search starting position can be passed. /// </summary> + /// <param name="instance">The string that will be searched.</param> + /// <param name="what">The substring to find.</param> + /// <param name="from">The search starting position.</param> + /// <param name="caseSensitive">If <see langword="true"/>, the search is case sensitive.</param> /// <returns>The starting position of the substring, or -1 if not found.</returns> public static int Find(this string instance, string what, int from = 0, bool caseSensitive = true) { @@ -361,6 +428,14 @@ namespace Godot /// <summary> /// Find the first occurrence of a char. Optionally, the search starting position can be passed. /// </summary> + /// <seealso cref="Find(string, string, int, bool)"/> + /// <seealso cref="FindLast(string, string, bool)"/> + /// <seealso cref="FindLast(string, string, int, bool)"/> + /// <seealso cref="FindN(string, string, int)"/> + /// <param name="instance">The string that will be searched.</param> + /// <param name="what">The substring to find.</param> + /// <param name="from">The search starting position.</param> + /// <param name="caseSensitive">If <see langword="true"/>, the search is case sensitive.</param> /// <returns>The first instance of the char, or -1 if not found.</returns> public static int Find(this string instance, char what, int from = 0, bool caseSensitive = true) { @@ -370,6 +445,13 @@ namespace Godot } /// <summary>Find the last occurrence of a substring.</summary> + /// <seealso cref="Find(string, string, int, bool)"/> + /// <seealso cref="Find(string, char, int, bool)"/> + /// <seealso cref="FindLast(string, string, int, bool)"/> + /// <seealso cref="FindN(string, string, int)"/> + /// <param name="instance">The string that will be searched.</param> + /// <param name="what">The substring to find.</param> + /// <param name="caseSensitive">If <see langword="true"/>, the search is case sensitive.</param> /// <returns>The starting position of the substring, or -1 if not found.</returns> public static int FindLast(this string instance, string what, bool caseSensitive = true) { @@ -377,6 +459,14 @@ namespace Godot } /// <summary>Find the last occurrence of a substring specifying the search starting position.</summary> + /// <seealso cref="Find(string, string, int, bool)"/> + /// <seealso cref="Find(string, char, int, bool)"/> + /// <seealso cref="FindLast(string, string, bool)"/> + /// <seealso cref="FindN(string, string, int)"/> + /// <param name="instance">The string that will be searched.</param> + /// <param name="what">The substring to find.</param> + /// <param name="from">The search starting position.</param> + /// <param name="caseSensitive">If <see langword="true"/>, the search is case sensitive.</param> /// <returns>The starting position of the substring, or -1 if not found.</returns> public static int FindLast(this string instance, string what, int from, bool caseSensitive = true) { @@ -387,6 +477,13 @@ namespace Godot /// Find the first occurrence of a substring but search as case-insensitive. /// Optionally, the search starting position can be passed. /// </summary> + /// <seealso cref="Find(string, string, int, bool)"/> + /// <seealso cref="Find(string, char, int, bool)"/> + /// <seealso cref="FindLast(string, string, bool)"/> + /// <seealso cref="FindLast(string, string, int, bool)"/> + /// <param name="instance">The string that will be searched.</param> + /// <param name="what">The substring to find.</param> + /// <param name="from">The search starting position.</param> /// <returns>The starting position of the substring, or -1 if not found.</returns> public static int FindN(this string instance, string what, int from = 0) { @@ -396,25 +493,30 @@ namespace Godot /// <summary> /// If the string is a path to a file, return the base directory. /// </summary> + /// <seealso cref="GetBaseName(string)"/> + /// <seealso cref="GetExtension(string)"/> + /// <seealso cref="GetFile(string)"/> + /// <param name="instance">The path to a file.</param> + /// <returns>The base directory.</returns> public static string GetBaseDir(this string instance) { int basepos = instance.Find("://"); string rs; - var @base = string.Empty; + string directory = string.Empty; if (basepos != -1) { - var end = basepos + 3; + int end = basepos + 3; rs = instance.Substring(end); - @base = instance.Substring(0, end); + directory = instance.Substring(0, end); } else { if (instance.BeginsWith("/")) { rs = instance.Substring(1); - @base = "/"; + directory = "/"; } else { @@ -425,14 +527,19 @@ namespace Godot int sep = Mathf.Max(rs.FindLast("/"), rs.FindLast("\\")); if (sep == -1) - return @base; + return directory; - return @base + rs.Substr(0, sep); + return directory + rs.Substr(0, sep); } /// <summary> /// If the string is a path to a file, return the file and ignore the base directory. /// </summary> + /// <seealso cref="GetBaseName(string)"/> + /// <seealso cref="GetExtension(string)"/> + /// <seealso cref="GetBaseDir(string)"/> + /// <param name="instance">The path to a file.</param> + /// <returns>The file name.</returns> public static string GetFile(this string instance) { int sep = Mathf.Max(instance.FindLast("/"), instance.FindLast("\\")); @@ -475,6 +582,8 @@ namespace Godot /// <summary> /// Hash the string and return a 32 bits unsigned integer. /// </summary> + /// <param name="instance">The string to hash.</param> + /// <returns>The calculated hash of the string.</returns> public static uint Hash(this string instance) { uint hash = 5381; @@ -494,7 +603,7 @@ namespace Godot /// <returns>The hexadecimal representation of this byte.</returns> internal static string HexEncode(this byte b) { - var ret = string.Empty; + string ret = string.Empty; for (int i = 0; i < 2; i++) { @@ -524,7 +633,7 @@ namespace Godot /// <returns>The hexadecimal representation of this byte array.</returns> public static string HexEncode(this byte[] bytes) { - var ret = string.Empty; + string ret = string.Empty; foreach (byte b in bytes) { @@ -536,8 +645,8 @@ namespace Godot /// <summary> /// Converts a string containing a hexadecimal number into an integer. - /// Hexadecimal strings can either be prefixed with `0x` or not, - /// and they can also start with a `-` before the optional prefix. + /// Hexadecimal strings can either be prefixed with <c>0x</c> or not, + /// and they can also start with a <c>-</c> before the optional prefix. /// </summary> /// <param name="instance">The string to convert.</param> /// <returns>The converted string.</returns> @@ -565,16 +674,28 @@ namespace Godot } /// <summary> - /// Insert a substring at a given position. + /// Inserts a substring at a given position. /// </summary> + /// <param name="instance">The string to modify.</param> + /// <param name="pos">Position at which to insert the substring.</param> + /// <param name="what">Substring to insert.</param> + /// <returns> + /// The string with <paramref name="what"/> inserted at the given + /// position <paramref name="pos"/>. + /// </returns> public static string Insert(this string instance, int pos, string what) { return instance.Insert(pos, what); } /// <summary> - /// If the string is a path to a file or directory, return <see langword="true"/> if the path is absolute. + /// Returns <see langword="true"/> if the string is a path to a file or + /// directory and its startign point is explicitly defined. This includes + /// <c>res://</c>, <c>user://</c>, <c>C:\</c>, <c>/</c>, etc. /// </summary> + /// <seealso cref="IsRelativePath(string)"/> + /// <param name="instance">The string to check.</param> + /// <returns>If the string is an absolute path.</returns> public static bool IsAbsolutePath(this string instance) { if (string.IsNullOrEmpty(instance)) @@ -586,8 +707,14 @@ namespace Godot } /// <summary> - /// If the string is a path to a file or directory, return <see langword="true"/> if the path is relative. + /// Returns <see langword="true"/> if the string is a path to a file or + /// directory and its starting point is implicitly defined within the + /// context it is being used. The starting point may refer to the current + /// directory (<c>./</c>), or the current <see cref="Node"/>. /// </summary> + /// <seealso cref="IsAbsolutePath(string)"/> + /// <param name="instance">The string to check.</param> + /// <returns>If the string is a relative path.</returns> public static bool IsRelativePath(this string instance) { return !IsAbsolutePath(instance); @@ -596,6 +723,11 @@ namespace Godot /// <summary> /// Check whether this string is a subsequence of the given string. /// </summary> + /// <seealso cref="IsSubsequenceOfI(string, string)"/> + /// <param name="instance">The subsequence to search.</param> + /// <param name="text">The string that contains the subsequence.</param> + /// <param name="caseSensitive">If <see langword="true"/>, the check is case sensitive.</param> + /// <returns>If the string is a subsequence of the given string.</returns> public static bool IsSubsequenceOf(this string instance, string text, bool caseSensitive = true) { int len = instance.Length; @@ -639,6 +771,10 @@ namespace Godot /// <summary> /// Check whether this string is a subsequence of the given string, ignoring case differences. /// </summary> + /// <seealso cref="IsSubsequenceOf(string, string, bool)"/> + /// <param name="instance">The subsequence to search.</param> + /// <param name="text">The string that contains the subsequence.</param> + /// <returns>If the string is a subsequence of the given string.</returns> public static bool IsSubsequenceOfI(this string instance, string text) { return instance.IsSubsequenceOf(text, caseSensitive: false); @@ -647,6 +783,8 @@ namespace Godot /// <summary> /// Check whether the string contains a valid <see langword="float"/>. /// </summary> + /// <param name="instance">The string to check.</param> + /// <returns>If the string contains a valid floating point number.</returns> public static bool IsValidFloat(this string instance) { float f; @@ -656,6 +794,8 @@ namespace Godot /// <summary> /// Check whether the string contains a valid color in HTML notation. /// </summary> + /// <param name="instance">The string to check.</param> + /// <returns>If the string contains a valid HTML color.</returns> public static bool IsValidHtmlColor(this string instance) { return Color.HtmlIsValid(instance); @@ -666,6 +806,8 @@ namespace Godot /// programming languages, a valid identifier may contain only letters, /// digits and underscores (_) and the first character may not be a digit. /// </summary> + /// <param name="instance">The string to check.</param> + /// <returns>If the string contains a valid identifier.</returns> public static bool IsValidIdentifier(this string instance) { int len = instance.Length; @@ -673,18 +815,15 @@ namespace Godot if (len == 0) return false; + if (instance[0] >= '0' && instance[0] <= '9') + return false; // Identifiers cannot start with numbers. + for (int i = 0; i < len; i++) { - if (i == 0) - { - if (instance[0] >= '0' && instance[0] <= '9') - return false; // Don't start with number plz - } - - bool validChar = instance[i] >= '0' && - instance[i] <= '9' || instance[i] >= 'a' && - instance[i] <= 'z' || instance[i] >= 'A' && - instance[i] <= 'Z' || instance[i] == '_'; + bool validChar = instance[i] == '_' || + (instance[i] >= 'a' && instance[i] <= 'z') || + (instance[i] >= 'A' && instance[i] <= 'Z') || + (instance[i] >= '0' && instance[i] <= '9'); if (!validChar) return false; @@ -696,6 +835,8 @@ namespace Godot /// <summary> /// Check whether the string contains a valid integer. /// </summary> + /// <param name="instance">The string to check.</param> + /// <returns>If the string contains a valid integer.</returns> public static bool IsValidInteger(this string instance) { int f; @@ -705,6 +846,8 @@ namespace Godot /// <summary> /// Check whether the string contains a valid IP address. /// </summary> + /// <param name="instance">The string to check.</param> + /// <returns>If the string contains a valid IP address.</returns> public static bool IsValidIPAddress(this string instance) { // TODO: Support IPv6 addresses @@ -728,8 +871,10 @@ namespace Godot } /// <summary> - /// Return a copy of the string with special characters escaped using the JSON standard. + /// Returns a copy of the string with special characters escaped using the JSON standard. /// </summary> + /// <param name="instance">The string to escape.</param> + /// <returns>The escaped string.</returns> public static string JSONEscape(this string instance) { var sb = new StringBuilder(string.Copy(instance)); @@ -747,8 +892,12 @@ namespace Godot } /// <summary> - /// Return an amount of characters from the left of the string. + /// Returns an amount of characters from the left of the string. /// </summary> + /// <seealso cref="Right(string, int)"/> + /// <param name="instance">The original string.</param> + /// <param name="pos">The position in the string where the left side ends.</param> + /// <returns>The left side of the string from the given position.</returns> public static string Left(this string instance, int pos) { if (pos <= 0) @@ -761,8 +910,10 @@ namespace Godot } /// <summary> - /// Return the length of the string in characters. + /// Returns the length of the string in characters. /// </summary> + /// <param name="instance">The string to check.</param> + /// <returns>The length of the string.</returns> public static int Length(this string instance) { return instance.Length; @@ -771,6 +922,7 @@ namespace Godot /// <summary> /// Returns a copy of the string with characters removed from the left. /// </summary> + /// <seealso cref="RStrip(string, string)"/> /// <param name="instance">The string to remove characters from.</param> /// <param name="chars">The characters to be removed.</param> /// <returns>A copy of the string with characters removed from the left.</returns> @@ -799,6 +951,12 @@ namespace Godot /// Do a simple expression match, where '*' matches zero or more /// arbitrary characters and '?' matches any single character except '.'. /// </summary> + /// <param name="instance">The string to check.</param> + /// <param name="expr">Expression to check.</param> + /// <param name="caseSensitive"> + /// If <see langword="true"/>, the check will be case sensitive. + /// </param> + /// <returns>If the expression has any matches.</returns> private static bool ExprMatch(this string instance, string expr, bool caseSensitive) { // case '\0': @@ -824,6 +982,13 @@ namespace Godot /// Do a simple case sensitive expression match, using ? and * wildcards /// (see <see cref="ExprMatch(string, string, bool)"/>). /// </summary> + /// <seealso cref="MatchN(string, string)"/> + /// <param name="instance">The string to check.</param> + /// <param name="expr">Expression to check.</param> + /// <param name="caseSensitive"> + /// If <see langword="true"/>, the check will be case sensitive. + /// </param> + /// <returns>If the expression has any matches.</returns> public static bool Match(this string instance, string expr, bool caseSensitive = true) { if (instance.Length == 0 || expr.Length == 0) @@ -836,6 +1001,10 @@ namespace Godot /// Do a simple case insensitive expression match, using ? and * wildcards /// (see <see cref="ExprMatch(string, string, bool)"/>). /// </summary> + /// <seealso cref="Match(string, string, bool)"/> + /// <param name="instance">The string to check.</param> + /// <param name="expr">Expression to check.</param> + /// <returns>If the expression has any matches.</returns> public static bool MatchN(this string instance, string expr) { if (instance.Length == 0 || expr.Length == 0) @@ -845,46 +1014,65 @@ namespace Godot } /// <summary> - /// Return the MD5 hash of the string as an array of bytes. + /// Returns the MD5 hash of the string as an array of bytes. /// </summary> + /// <seealso cref="MD5Text(string)"/> + /// <param name="instance">The string to hash.</param> + /// <returns>The MD5 hash of the string.</returns> public static byte[] MD5Buffer(this string instance) { return godot_icall_String_md5_buffer(instance); } [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static byte[] godot_icall_String_md5_buffer(string str); + internal static extern byte[] godot_icall_String_md5_buffer(string str); /// <summary> - /// Return the MD5 hash of the string as a string. + /// Returns the MD5 hash of the string as a string. /// </summary> + /// <seealso cref="MD5Buffer(string)"/> + /// <param name="instance">The string to hash.</param> + /// <returns>The MD5 hash of the string.</returns> public static string MD5Text(this string instance) { return godot_icall_String_md5_text(instance); } [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static string godot_icall_String_md5_text(string str); + internal static extern string godot_icall_String_md5_text(string str); /// <summary> /// Perform a case-insensitive comparison to another string, return -1 if less, 0 if equal and +1 if greater. /// </summary> + /// <seealso cref="CasecmpTo(string, string)"/> + /// <seealso cref="CompareTo(string, string, bool)"/> + /// <param name="instance">The string to compare.</param> + /// <param name="to">The other string to compare.</param> + /// <returns>-1 if less, 0 if equal and +1 if greater.</returns> public static int NocasecmpTo(this string instance, string to) { return instance.CompareTo(to, caseSensitive: false); } /// <summary> - /// Return the character code at position <paramref name="at"/>. + /// Returns the character code at position <paramref name="at"/>. /// </summary> + /// <param name="instance">The string to check.</param> + /// <param name="at">The position int the string for the character to check.</param> + /// <returns>The character code.</returns> public static int OrdAt(this string instance, int at) { return instance[at]; } /// <summary> - /// Format a number to have an exact number of <paramref name="digits"/> after the decimal point. + /// Format a number to have an exact number of <paramref name="digits"/> + /// after the decimal point. /// </summary> + /// <seealso cref="PadZeros(string, int)"/> + /// <param name="instance">The string to pad.</param> + /// <param name="digits">Amount of digits after the decimal point.</param> + /// <returns>The string padded with zeroes.</returns> public static string PadDecimals(this string instance, int digits) { int c = instance.Find("."); @@ -919,8 +1107,13 @@ namespace Godot } /// <summary> - /// Format a number to have an exact number of <paramref name="digits"/> before the decimal point. + /// Format a number to have an exact number of <paramref name="digits"/> + /// before the decimal point. /// </summary> + /// <seealso cref="PadDecimals(string, int)"/> + /// <param name="instance">The string to pad.</param> + /// <param name="digits">Amount of digits before the decimal point.</param> + /// <returns>The string padded with zeroes.</returns> public static string PadZeros(this string instance, int digits) { string s = instance; @@ -952,9 +1145,13 @@ namespace Godot } /// <summary> - /// If the string is a path, this concatenates <paramref name="file"/> at the end of the string as a subpath. + /// If the string is a path, this concatenates <paramref name="file"/> + /// at the end of the string as a subpath. /// E.g. <c>"this/is".PlusFile("path") == "this/is/path"</c>. /// </summary> + /// <param name="instance">The path that will be concatenated.</param> + /// <param name="file">File name to concatenate with the path.</param> + /// <returns>The concatenated path with the given file name.</returns> public static string PlusFile(this string instance, string file) { if (instance.Length > 0 && instance[instance.Length - 1] == '/') @@ -965,6 +1162,11 @@ namespace Godot /// <summary> /// Replace occurrences of a substring for different ones inside the string. /// </summary> + /// <seealso cref="ReplaceN(string, string, string)"/> + /// <param name="instance">The string to modify.</param> + /// <param name="what">The substring to be replaced in the string.</param> + /// <param name="forwhat">The substring that replaces <paramref name="what"/>.</param> + /// <returns>The string with the substring occurrences replaced.</returns> public static string Replace(this string instance, string what, string forwhat) { return instance.Replace(what, forwhat); @@ -973,6 +1175,11 @@ namespace Godot /// <summary> /// Replace occurrences of a substring for different ones inside the string, but search case-insensitive. /// </summary> + /// <seealso cref="Replace(string, string, string)"/> + /// <param name="instance">The string to modify.</param> + /// <param name="what">The substring to be replaced in the string.</param> + /// <param name="forwhat">The substring that replaces <paramref name="what"/>.</param> + /// <returns>The string with the substring occurrences replaced.</returns> public static string ReplaceN(this string instance, string what, string forwhat) { return Regex.Replace(instance, what, forwhat, RegexOptions.IgnoreCase); @@ -981,29 +1188,43 @@ namespace Godot /// <summary> /// Perform a search for a substring, but start from the end of the string instead of the beginning. /// </summary> + /// <seealso cref="RFindN(string, string, int)"/> + /// <param name="instance">The string that will be searched.</param> + /// <param name="what">The substring to search in the string.</param> + /// <param name="from">The position at which to start searching.</param> + /// <returns>The position at which the substring was found, or -1 if not found.</returns> public static int RFind(this string instance, string what, int from = -1) { return godot_icall_String_rfind(instance, what, from); } [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static int godot_icall_String_rfind(string str, string what, int from); + internal static extern int godot_icall_String_rfind(string str, string what, int from); /// <summary> /// Perform a search for a substring, but start from the end of the string instead of the beginning. /// Also search case-insensitive. /// </summary> + /// <seealso cref="RFind(string, string, int)"/> + /// <param name="instance">The string that will be searched.</param> + /// <param name="what">The substring to search in the string.</param> + /// <param name="from">The position at which to start searching.</param> + /// <returns>The position at which the substring was found, or -1 if not found.</returns> public static int RFindN(this string instance, string what, int from = -1) { return godot_icall_String_rfindn(instance, what, from); } [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static int godot_icall_String_rfindn(string str, string what, int from); + internal static extern int godot_icall_String_rfindn(string str, string what, int from); /// <summary> - /// Return the right side of the string from a given position. + /// Returns the right side of the string from a given position. /// </summary> + /// <seealso cref="Left(string, int)"/> + /// <param name="instance">The original string.</param> + /// <param name="pos">The position in the string from which the right side starts.</param> + /// <returns>The right side of the string from the given position.</returns> public static string Right(this string instance, int pos) { if (pos >= instance.Length) @@ -1018,6 +1239,7 @@ namespace Godot /// <summary> /// Returns a copy of the string with characters removed from the right. /// </summary> + /// <seealso cref="LStrip(string, string)"/> /// <param name="instance">The string to remove characters from.</param> /// <param name="chars">The characters to be removed.</param> /// <returns>A copy of the string with characters removed from the right.</returns> @@ -1042,29 +1264,41 @@ namespace Godot return instance.Substr(0, end + 1); } + /// <summary> + /// Returns the SHA-256 hash of the string as an array of bytes. + /// </summary> + /// <seealso cref="SHA256Text(string)"/> + /// <param name="instance">The string to hash.</param> + /// <returns>The SHA-256 hash of the string.</returns> public static byte[] SHA256Buffer(this string instance) { return godot_icall_String_sha256_buffer(instance); } [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static byte[] godot_icall_String_sha256_buffer(string str); + internal static extern byte[] godot_icall_String_sha256_buffer(string str); /// <summary> - /// Return the SHA-256 hash of the string as a string. + /// Returns the SHA-256 hash of the string as a string. /// </summary> + /// <seealso cref="SHA256Buffer(string)"/> + /// <param name="instance">The string to hash.</param> + /// <returns>The SHA-256 hash of the string.</returns> public static string SHA256Text(this string instance) { return godot_icall_String_sha256_text(instance); } [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static string godot_icall_String_sha256_text(string str); + internal static extern string godot_icall_String_sha256_text(string str); /// <summary> - /// Return the similarity index of the text compared to this string. + /// Returns the similarity index of the text compared to this string. /// 1 means totally similar and 0 means totally dissimilar. /// </summary> + /// <param name="instance">The string to compare.</param> + /// <param name="text">The other string to compare.</param> + /// <returns>The similarity index.</returns> public static float Similarity(this string instance, string text) { if (instance == text) @@ -1117,6 +1351,13 @@ namespace Godot /// Split the string by a divisor string, return an array of the substrings. /// Example "One,Two,Three" will return ["One","Two","Three"] if split by ",". /// </summary> + /// <seealso cref="SplitFloats(string, string, bool)"/> + /// <param name="instance">The string to split.</param> + /// <param name="divisor">The divisor string that splits the string.</param> + /// <param name="allowEmpty"> + /// If <see langword="true"/>, the array may include empty strings. + /// </param> + /// <returns>The array of strings split from the string.</returns> public static string[] Split(this string instance, string divisor, bool allowEmpty = true) { return instance.Split(new[] { divisor }, allowEmpty ? StringSplitOptions.None : StringSplitOptions.RemoveEmptyEntries); @@ -1126,6 +1367,13 @@ namespace Godot /// Split the string in floats by using a divisor string, return an array of the substrings. /// Example "1,2.5,3" will return [1,2.5,3] if split by ",". /// </summary> + /// <seealso cref="Split(string, string, bool)"/> + /// <param name="instance">The string to split.</param> + /// <param name="divisor">The divisor string that splits the string.</param> + /// <param name="allowEmpty"> + /// If <see langword="true"/>, the array may include empty floats. + /// </param> + /// <returns>The array of floats split from the string.</returns> public static float[] SplitFloats(this string instance, string divisor, bool allowEmpty = true) { var ret = new List<float>(); @@ -1148,7 +1396,8 @@ namespace Godot return ret.ToArray(); } - private static readonly char[] _nonPrintable = { + private static readonly char[] _nonPrintable = + { (char)00, (char)01, (char)02, (char)03, (char)04, (char)05, (char)06, (char)07, (char)08, (char)09, (char)10, (char)11, (char)12, (char)13, (char)14, (char)15, (char)16, (char)17, @@ -1158,9 +1407,13 @@ namespace Godot }; /// <summary> - /// Return a copy of the string stripped of any non-printable character at the beginning and the end. + /// Returns a copy of the string stripped of any non-printable character at the beginning and the end. /// The optional arguments are used to toggle stripping on the left and right edges respectively. /// </summary> + /// <param name="instance">The string to strip.</param> + /// <param name="left">If the left side should be stripped.</param> + /// <param name="right">If the right side should be stripped.</param> + /// <returns>The string stripped of any non-printable characters.</returns> public static string StripEdges(this string instance, bool left = true, bool right = true) { if (left) @@ -1174,8 +1427,14 @@ namespace Godot } /// <summary> - /// Return part of the string from the position <paramref name="from"/>, with length <paramref name="len"/>. + /// Returns part of the string from the position <paramref name="from"/>, with length <paramref name="len"/>. /// </summary> + /// <param name="instance">The string to slice.</param> + /// <param name="from">The position in the string that the part starts from.</param> + /// <param name="len">The length of the returned part.</param> + /// <returns> + /// Part of the string from the position <paramref name="from"/>, with length <paramref name="len"/>. + /// </returns> public static string Substr(this string instance, int from, int len) { int max = instance.Length - from; @@ -1183,52 +1442,70 @@ namespace Godot } /// <summary> - /// Convert the String (which is a character array) to PackedByteArray (which is an array of bytes). + /// Converts the String (which is a character array) to PackedByteArray (which is an array of bytes). /// The conversion is speeded up in comparison to <see cref="ToUTF8(string)"/> with the assumption /// that all the characters the String contains are only ASCII characters. /// </summary> + /// <seealso cref="ToUTF8(string)"/> + /// <param name="instance">The string to convert.</param> + /// <returns>The string as ASCII encoded bytes.</returns> public static byte[] ToAscii(this string instance) { return Encoding.ASCII.GetBytes(instance); } /// <summary> - /// Convert a string, containing a decimal number, into a <see langword="float" />. + /// Converts a string, containing a decimal number, into a <see langword="float" />. /// </summary> + /// <seealso cref="ToInt(string)"/> + /// <param name="instance">The string to convert.</param> + /// <returns>The number representation of the string.</returns> public static float ToFloat(this string instance) { return float.Parse(instance); } /// <summary> - /// Convert a string, containing an integer number, into an <see langword="int" />. + /// Converts a string, containing an integer number, into an <see langword="int" />. /// </summary> + /// <seealso cref="ToFloat(string)"/> + /// <param name="instance">The string to convert.</param> + /// <returns>The number representation of the string.</returns> public static int ToInt(this string instance) { return int.Parse(instance); } /// <summary> - /// Return the string converted to lowercase. + /// Returns the string converted to lowercase. /// </summary> + /// <seealso cref="ToUpper(string)"/> + /// <param name="instance">The string to convert.</param> + /// <returns>The string converted to lowercase.</returns> public static string ToLower(this string instance) { return instance.ToLower(); } /// <summary> - /// Return the string converted to uppercase. + /// Returns the string converted to uppercase. /// </summary> + /// <seealso cref="ToLower(string)"/> + /// <param name="instance">The string to convert.</param> + /// <returns>The string converted to uppercase.</returns> public static string ToUpper(this string instance) { return instance.ToUpper(); } /// <summary> - /// Convert the String (which is an array of characters) to PackedByteArray (which is an array of bytes). + /// Converts the String (which is an array of characters) to PackedByteArray (which is an array of bytes). /// The conversion is a bit slower than <see cref="ToAscii(string)"/>, but supports all UTF-8 characters. /// Therefore, you should prefer this function over <see cref="ToAscii(string)"/>. /// </summary> + /// <seealso cref="ToAscii(string)"/> + /// <param name="instance">The string to convert.</param> + /// <returns>The string as UTF-8 encoded bytes.</returns> public static byte[] ToUTF8(this string instance) { return Encoding.UTF8.GetBytes(instance); @@ -1237,8 +1514,8 @@ namespace Godot /// <summary> /// Decodes a string in URL encoded format. This is meant to /// decode parameters in a URL when receiving an HTTP request. - /// This mostly wraps around `System.Uri.UnescapeDataString()`, - /// but also handles `+`. + /// This mostly wraps around <see cref="Uri.UnescapeDataString"/>, + /// but also handles <c>+</c>. /// See <see cref="URIEncode"/> for encoding. /// </summary> /// <param name="instance">The string to decode.</param> @@ -1251,7 +1528,7 @@ namespace Godot /// <summary> /// Encodes a string to URL friendly format. This is meant to /// encode parameters in a URL when sending an HTTP request. - /// This wraps around `System.Uri.EscapeDataString()`. + /// This wraps around <see cref="Uri.EscapeDataString"/>. /// See <see cref="URIDecode"/> for decoding. /// </summary> /// <param name="instance">The string to encode.</param> @@ -1262,17 +1539,23 @@ namespace Godot } /// <summary> - /// Return a copy of the string with special characters escaped using the XML standard. + /// Returns a copy of the string with special characters escaped using the XML standard. /// </summary> + /// <seealso cref="XMLUnescape(string)"/> + /// <param name="instance">The string to escape.</param> + /// <returns>The escaped string.</returns> public static string XMLEscape(this string instance) { return SecurityElement.Escape(instance); } /// <summary> - /// Return a copy of the string with escaped characters replaced by their meanings + /// Returns a copy of the string with escaped characters replaced by their meanings /// according to the XML standard. /// </summary> + /// <seealso cref="XMLEscape(string)"/> + /// <param name="instance">The string to unescape.</param> + /// <returns>The unescaped string.</returns> public static string XMLUnescape(this string instance) { return SecurityElement.FromString(instance).Text; diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/StringName.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/StringName.cs index 7700b6d4ed..b1d504410b 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/StringName.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/StringName.cs @@ -3,6 +3,13 @@ using System.Runtime.CompilerServices; namespace Godot { + /// <summary> + /// StringNames are immutable strings designed for general-purpose representation of unique names. + /// StringName ensures that only one instance of a given name exists (so two StringNames with the + /// same value are the same object). + /// Comparing them is much faster than with regular strings, because only the pointers are compared, + /// not the whole strings. + /// </summary> public sealed partial class StringName : IDisposable { private IntPtr ptr; @@ -23,6 +30,9 @@ namespace Godot Dispose(false); } + /// <summary> + /// Disposes of this <see cref="StringName"/>. + /// </summary> public void Dispose() { Dispose(true); @@ -43,25 +53,48 @@ namespace Godot this.ptr = ptr; } + /// <summary> + /// Constructs an empty <see cref="StringName"/>. + /// </summary> public StringName() { ptr = IntPtr.Zero; } + /// <summary> + /// Constructs a <see cref="StringName"/> from the given <paramref name="path"/> string. + /// </summary> + /// <param name="path">String to construct the <see cref="StringName"/> from.</param> public StringName(string path) { ptr = path == null ? IntPtr.Zero : godot_icall_StringName_Ctor(path); } + /// <summary> + /// Converts a string to a <see cref="StringName"/>. + /// </summary> + /// <param name="from">The string to convert.</param> public static implicit operator StringName(string from) => new StringName(from); + /// <summary> + /// Converts a <see cref="StringName"/> to a string. + /// </summary> + /// <param name="from">The <see cref="StringName"/> to convert.</param> public static implicit operator string(StringName from) => from.ToString(); + /// <summary> + /// Converts this <see cref="StringName"/> to a string. + /// </summary> + /// <returns>A string representation of this <see cref="StringName"/>.</returns> public override string ToString() { return ptr == IntPtr.Zero ? string.Empty : godot_icall_StringName_operator_String(GetPtr(this)); } + /// <summary> + /// Check whether this <see cref="StringName"/> is empty. + /// </summary> + /// <returns>If the <see cref="StringName"/> is empty.</returns> public bool IsEmpty() { return ptr == IntPtr.Zero || godot_icall_StringName_is_empty(GetPtr(this)); diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs index 62a6fe6959..daea09ba72 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs @@ -21,18 +21,18 @@ namespace Godot public struct Transform2D : IEquatable<Transform2D> { /// <summary> - /// The basis matrix's X vector (column 0). Equivalent to array index `[0]`. + /// The basis matrix's X vector (column 0). Equivalent to array index <c>[0]</c>. /// </summary> /// <value></value> public Vector2 x; /// <summary> - /// The basis matrix's Y vector (column 1). Equivalent to array index `[1]`. + /// The basis matrix's Y vector (column 1). Equivalent to array index <c>[1]</c>. /// </summary> public Vector2 y; /// <summary> - /// The origin vector (column 2, the third column). Equivalent to array index `[2]`. + /// The origin vector (column 2, the third column). Equivalent to array index <c>[2]</c>. /// The origin vector represents translation. /// </summary> public Vector2 origin; @@ -77,7 +77,8 @@ namespace Godot } /// <summary> - /// Access whole columns in the form of Vector2. The third column is the origin vector. + /// Access whole columns in the form of <see cref="Vector2"/>. + /// The third column is the <see cref="origin"/> vector. /// </summary> /// <param name="column">Which column vector.</param> public Vector2 this[int column] @@ -116,7 +117,8 @@ namespace Godot } /// <summary> - /// Access matrix elements in column-major order. The third column is the origin vector. + /// Access matrix elements in column-major order. + /// The third column is the <see cref="origin"/> vector. /// </summary> /// <param name="column">Which column, the matrix horizontal position.</param> /// <param name="row">Which row, the matrix vertical position.</param> @@ -138,6 +140,7 @@ namespace Godot /// Returns the inverse of the transform, under the assumption that /// the transformation is composed of rotation, scaling, and translation. /// </summary> + /// <seealso cref="Inverse"/> /// <returns>The inverse transformation matrix.</returns> public Transform2D AffineInverse() { @@ -146,7 +149,7 @@ namespace Godot if (det == 0) throw new InvalidOperationException("Matrix determinant is zero and cannot be inverted."); - var inv = this; + Transform2D inv = this; real_t temp = inv[0, 0]; inv[0, 0] = inv[1, 1]; @@ -173,13 +176,14 @@ namespace Godot /// <returns>The determinant of the basis matrix.</returns> private real_t BasisDeterminant() { - return x.x * y.y - x.y * y.x; + 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). + /// This method does not account for translation (the <see cref="origin"/> vector). /// </summary> + /// <seealso cref="BasisXformInv(Vector2)"/> /// <param name="v">A vector to transform.</param> /// <returns>The transformed vector.</returns> public Vector2 BasisXform(Vector2 v) @@ -189,11 +193,12 @@ namespace Godot /// <summary> /// Returns a vector transformed (multiplied) by the inverse basis matrix. - /// This method does not account for translation (the origin vector). + /// This method does not account for translation (the <see cref="origin"/> vector). /// /// Note: This results in a multiplication by the inverse of the /// basis matrix only if it represents a rotation-reflection. /// </summary> + /// <seealso cref="BasisXform(Vector2)"/> /// <param name="v">A vector to inversely transform.</param> /// <returns>The inversely transformed vector.</returns> public Vector2 BasisXformInv(Vector2 v) @@ -202,7 +207,7 @@ namespace Godot } /// <summary> - /// Interpolates this transform to the other `transform` by `weight`. + /// Interpolates this transform to the other <paramref name="transform"/> by <paramref name="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> @@ -233,8 +238,8 @@ namespace Godot else { real_t angle = weight * Mathf.Acos(dot); - Vector2 v3 = (v2 - v1 * dot).Normalized(); - v = v1 * Mathf.Cos(angle) + v3 * Mathf.Sin(angle); + Vector2 v3 = (v2 - (v1 * dot)).Normalized(); + v = (v1 * Mathf.Cos(angle)) + (v3 * Mathf.Sin(angle)); } // Extract parameters @@ -258,7 +263,7 @@ namespace Godot /// <returns>The inverse matrix.</returns> public Transform2D Inverse() { - var inv = this; + Transform2D inv = this; // Swap real_t temp = inv.x.y; @@ -277,13 +282,13 @@ namespace Godot /// <returns>The orthonormalized transform.</returns> public Transform2D Orthonormalized() { - var on = this; + Transform2D on = this; Vector2 onX = on.x; Vector2 onY = on.y; onX.Normalize(); - onY = onY - onX * onX.Dot(onY); + onY = onY - (onX * onX.Dot(onY)); onY.Normalize(); on.x = onX; @@ -293,7 +298,7 @@ namespace Godot } /// <summary> - /// Rotates the transform by `phi` (in radians), using matrix multiplication. + /// Rotates the transform by <paramref name="phi"/> (in radians), using matrix multiplication. /// </summary> /// <param name="phi">The angle to rotate, in radians.</param> /// <returns>The rotated transformation matrix.</returns> @@ -309,7 +314,7 @@ namespace Godot /// <returns>The scaled transformation matrix.</returns> public Transform2D Scaled(Vector2 scale) { - var copy = this; + Transform2D copy = this; copy.x *= scale; copy.y *= scale; copy.origin *= scale; @@ -326,16 +331,16 @@ namespace Godot private real_t Tdotx(Vector2 with) { - return this[0, 0] * with[0] + this[1, 0] * with[1]; + return (this[0, 0] * with[0]) + (this[1, 0] * with[1]); } private real_t Tdoty(Vector2 with) { - return this[0, 1] * with[0] + this[1, 1] * with[1]; + return (this[0, 1] * with[0]) + (this[1, 1] * with[1]); } /// <summary> - /// Translates the transform by the given `offset`, + /// Translates the transform by the given <paramref name="offset"/>, /// relative to the transform's basis vectors. /// /// Unlike <see cref="Rotated"/> and <see cref="Scaled"/>, @@ -345,7 +350,7 @@ namespace Godot /// <returns>The translated matrix.</returns> public Transform2D Translated(Vector2 offset) { - var copy = this; + Transform2D copy = this; copy.origin += copy.BasisXform(offset); return copy; } @@ -353,6 +358,7 @@ namespace Godot /// <summary> /// Returns a vector transformed (multiplied) by this transformation matrix. /// </summary> + /// <seealso cref="XformInv(Vector2)"/> /// <param name="v">A vector to transform.</param> /// <returns>The transformed vector.</returns> public Vector2 Xform(Vector2 v) @@ -363,6 +369,7 @@ namespace Godot /// <summary> /// Returns a vector transformed (multiplied) by the inverse transformation matrix. /// </summary> + /// <seealso cref="Xform(Vector2)"/> /// <param name="v">A vector to inversely transform.</param> /// <returns>The inversely transformed vector.</returns> public Vector2 XformInv(Vector2 v) @@ -378,20 +385,20 @@ namespace Godot /// <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. + /// This is used as a replacement for <c>Transform2D()</c> in GDScript. + /// Do not use <c>new Transform2D()</c> 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> + /// <value>Equivalent to <c>new Transform2D(Vector2.Right, Vector2.Down, Vector2.Zero)</c>.</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> + /// <value>Equivalent to <c>new Transform2D(Vector2.Left, Vector2.Down, Vector2.Zero)</c>.</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> + /// <value>Equivalent to <c>new Transform2D(Vector2.Right, Vector2.Up, Vector2.Zero)</c>.</value> public static Transform2D FlipY { get { return _flipY; } } /// <summary> @@ -411,12 +418,12 @@ namespace Godot /// 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> + /// <param name="xx">The X component of the X column vector, accessed via <c>t.x.x</c> or <c>[0][0]</c></param> + /// <param name="xy">The Y component of the X column vector, accessed via <c>t.x.y</c> or <c>[0][1]</c></param> + /// <param name="yx">The X component of the Y column vector, accessed via <c>t.y.x</c> or <c>[1][0]</c></param> + /// <param name="yy">The Y component of the Y column vector, accessed via <c>t.y.y</c> or <c>[1][1]</c></param> + /// <param name="ox">The X component of the origin vector, accessed via <c>t.origin.x</c> or <c>[2][0]</c></param> + /// <param name="oy">The Y component of the origin vector, accessed via <c>t.origin.y</c> or <c>[2][1]</c></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); @@ -425,16 +432,17 @@ namespace Godot } /// <summary> - /// Constructs a transformation matrix from a rotation value and origin vector. + /// Constructs a transformation matrix from a <paramref name="rotation"/> value and + /// <paramref name="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) + /// <param name="rotation">The rotation of the new transform, in radians.</param> + /// <param name="origin">The origin vector, or column index 2.</param> + public Transform2D(real_t rotation, Vector2 origin) { - x.x = y.y = Mathf.Cos(rot); - x.y = y.x = Mathf.Sin(rot); + x.x = y.y = Mathf.Cos(rotation); + x.y = y.x = Mathf.Sin(rotation); y.x *= -1; - origin = pos; + this.origin = origin; } public static Transform2D operator *(Transform2D left, Transform2D right) @@ -464,19 +472,29 @@ namespace Godot return !left.Equals(right); } + /// <summary> + /// Returns <see langword="true"/> if this transform and <paramref name="obj"/> are equal. + /// </summary> + /// <param name="obj">The other object to compare.</param> + /// <returns>Whether or not the transform and the other object are equal.</returns> public override bool Equals(object obj) { return obj is Transform2D transform2D && Equals(transform2D); } + /// <summary> + /// Returns <see langword="true"/> if this transform and <paramref name="other"/> are equal. + /// </summary> + /// <param name="other">The other transform to compare.</param> + /// <returns>Whether or not the matrices are equal.</returns> public bool Equals(Transform2D other) { 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. + /// Returns <see langword="true"/> if this transform and <paramref name="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> @@ -485,16 +503,28 @@ namespace Godot return x.IsEqualApprox(other.x) && y.IsEqualApprox(other.y) && origin.IsEqualApprox(other.origin); } + /// <summary> + /// Serves as the hash function for <see cref="Transform2D"/>. + /// </summary> + /// <returns>A hash code for this transform.</returns> public override int GetHashCode() { return x.GetHashCode() ^ y.GetHashCode() ^ origin.GetHashCode(); } + /// <summary> + /// Converts this <see cref="Transform2D"/> to a string. + /// </summary> + /// <returns>A string representation of this transform.</returns> public override string ToString() { return $"[X: {x}, Y: {y}, O: {origin}]"; } + /// <summary> + /// Converts this <see cref="Transform2D"/> to a string with the given <paramref name="format"/>. + /// </summary> + /// <returns>A string representation of this transform.</returns> public string ToString(string format) { return $"[X: {x.ToString(format)}, Y: {y.ToString(format)}, O: {origin.ToString(format)}]"; diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform3D.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform3D.cs index afc6a65a45..7176cd60dc 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform3D.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform3D.cs @@ -28,12 +28,13 @@ namespace Godot public Basis basis; /// <summary> - /// The origin vector (column 3, the fourth column). Equivalent to array index `[3]`. + /// The origin vector (column 3, the fourth column). Equivalent to array index <c>[3]</c>. /// </summary> public Vector3 origin; /// <summary> - /// Access whole columns in the form of Vector3. The fourth column is the origin vector. + /// Access whole columns in the form of <see cref="Vector3"/>. + /// The fourth column is the <see cref="origin"/> vector. /// </summary> /// <param name="column">Which column vector.</param> public Vector3 this[int column] @@ -77,7 +78,8 @@ namespace Godot } /// <summary> - /// Access matrix elements in column-major order. The fourth column is the origin vector. + /// Access matrix elements in column-major order. + /// The fourth column is the <see cref="origin"/> vector. /// </summary> /// <param name="column">Which column, the matrix horizontal position.</param> /// <param name="row">Which row, the matrix vertical position.</param> @@ -106,6 +108,7 @@ namespace Godot /// Returns the inverse of the transform, under the assumption that /// the transformation is composed of rotation, scaling, and translation. /// </summary> + /// <seealso cref="Inverse"/> /// <returns>The inverse transformation matrix.</returns> public Transform3D AffineInverse() { @@ -114,7 +117,7 @@ namespace Godot } /// <summary> - /// Interpolates this transform to the other `transform` by `weight`. + /// Interpolates this transform to the other <paramref name="transform"/> by <paramref name="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> @@ -132,7 +135,9 @@ namespace Godot Vector3 destinationLocation = transform.origin; var interpolated = new Transform3D(); - interpolated.basis.SetQuaternionScale(sourceRotation.Slerp(destinationRotation, weight).Normalized(), sourceScale.Lerp(destinationScale, weight)); + Quaternion quaternion = sourceRotation.Slerp(destinationRotation, weight).Normalized(); + Vector3 scale = sourceScale.Lerp(destinationScale, weight); + interpolated.basis.SetQuaternionScale(quaternion, scale); interpolated.origin = sourceLocation.Lerp(destinationLocation, weight); return interpolated; @@ -152,11 +157,11 @@ namespace Godot /// <summary> /// Returns a copy of the transform rotated such that its - /// -Z axis (forward) points towards the target position. + /// -Z axis (forward) points towards the <paramref name="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. + /// The transform will first be rotated around the given <paramref name="up"/> vector, + /// and then fully aligned to the <paramref name="target"/> by a further rotation around + /// an axis perpendicular to both the <paramref name="target"/> and <paramref name="up"/> vectors. /// /// Operations take place in global space. /// </summary> @@ -165,7 +170,7 @@ namespace Godot /// <returns>The resulting transform.</returns> public Transform3D LookingAt(Vector3 target, Vector3 up) { - var t = this; + Transform3D t = this; t.SetLookAt(origin, target, up); return t; } @@ -181,7 +186,7 @@ namespace Godot } /// <summary> - /// Rotates the transform around the given `axis` by `phi` (in radians), + /// Rotates the transform around the given <paramref name="axis"/> by <paramref name="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> @@ -226,7 +231,7 @@ namespace Godot } /// <summary> - /// Translates the transform by the given `offset`, + /// Translates the transform by the given <paramref name="offset"/>, /// relative to the transform's basis vectors. /// /// Unlike <see cref="Rotated"/> and <see cref="Scaled"/>, @@ -247,6 +252,7 @@ namespace Godot /// <summary> /// Returns a vector transformed (multiplied) by this transformation matrix. /// </summary> + /// <seealso cref="XformInv(Vector3)"/> /// <param name="v">A vector to transform.</param> /// <returns>The transformed vector.</returns> public Vector3 Xform(Vector3 v) @@ -265,6 +271,7 @@ namespace Godot /// Note: This results in a multiplication by the inverse of the /// transformation matrix only if it represents a rotation-reflection. /// </summary> + /// <seealso cref="Xform(Vector3)"/> /// <param name="v">A vector to inversely transform.</param> /// <returns>The inversely transformed vector.</returns> public Vector3 XformInv(Vector3 v) @@ -273,9 +280,9 @@ namespace Godot return new Vector3 ( - basis.Row0[0] * vInv.x + basis.Row1[0] * vInv.y + basis.Row2[0] * vInv.z, - basis.Row0[1] * vInv.x + basis.Row1[1] * vInv.y + basis.Row2[1] * vInv.z, - basis.Row0[2] * vInv.x + basis.Row1[2] * vInv.y + basis.Row2[2] * vInv.z + (basis.Row0[0] * vInv.x) + (basis.Row1[0] * vInv.y) + (basis.Row2[0] * vInv.z), + (basis.Row0[1] * vInv.x) + (basis.Row1[1] * vInv.y) + (basis.Row2[1] * vInv.z), + (basis.Row0[2] * vInv.x) + (basis.Row1[2] * vInv.y) + (basis.Row2[2] * vInv.z) ); } @@ -287,25 +294,25 @@ namespace Godot /// <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. + /// This is used as a replacement for <c>Transform()</c> in GDScript. + /// Do not use <c>new Transform()</c> 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> + /// <value>Equivalent to <c>new Transform(Vector3.Right, Vector3.Up, Vector3.Back, Vector3.Zero)</c>.</value> public static Transform3D 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> + /// <value>Equivalent to <c>new Transform(Vector3.Left, Vector3.Up, Vector3.Back, Vector3.Zero)</c>.</value> public static Transform3D 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> + /// <value>Equivalent to <c>new Transform(Vector3.Right, Vector3.Down, Vector3.Back, Vector3.Zero)</c>.</value> public static Transform3D 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> + /// <value>Equivalent to <c>new Transform(Vector3.Right, Vector3.Up, Vector3.Forward, Vector3.Zero)</c>.</value> public static Transform3D FlipZ { get { return _flipZ; } } /// <summary> @@ -322,9 +329,10 @@ namespace Godot } /// <summary> - /// Constructs a transformation matrix from the given quaternion and origin vector. + /// Constructs a transformation matrix from the given <paramref name="quaternion"/> + /// and <paramref name="origin"/> vector. /// </summary> - /// <param name="quaternion">The <see cref="Godot.Quaternion"/> to create the basis from.</param> + /// <param name="quaternion">The <see cref="Quaternion"/> to create the basis from.</param> /// <param name="origin">The origin vector, or column index 3.</param> public Transform3D(Quaternion quaternion, Vector3 origin) { @@ -333,9 +341,10 @@ namespace Godot } /// <summary> - /// Constructs a transformation matrix from the given basis and origin vector. + /// Constructs a transformation matrix from the given <paramref name="basis"/> and + /// <paramref name="origin"/> vector. /// </summary> - /// <param name="basis">The <see cref="Godot.Basis"/> to create the basis from.</param> + /// <param name="basis">The <see cref="Basis"/> to create the basis from.</param> /// <param name="origin">The origin vector, or column index 3.</param> public Transform3D(Basis basis, Vector3 origin) { @@ -360,6 +369,11 @@ namespace Godot return !left.Equals(right); } + /// <summary> + /// Returns <see langword="true"/> if this transform and <paramref name="obj"/> are equal. + /// </summary> + /// <param name="obj">The other object to compare.</param> + /// <returns>Whether or not the transform and the other object are equal.</returns> public override bool Equals(object obj) { if (obj is Transform3D) @@ -370,14 +384,19 @@ namespace Godot return false; } + /// <summary> + /// Returns <see langword="true"/> if this transform and <paramref name="other"/> are equal. + /// </summary> + /// <param name="other">The other transform to compare.</param> + /// <returns>Whether or not the matrices are equal.</returns> public bool Equals(Transform3D other) { 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. + /// Returns <see langword="true"/> if this transform and <paramref name="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> @@ -386,16 +405,28 @@ namespace Godot return basis.IsEqualApprox(other.basis) && origin.IsEqualApprox(other.origin); } + /// <summary> + /// Serves as the hash function for <see cref="Transform3D"/>. + /// </summary> + /// <returns>A hash code for this transform.</returns> public override int GetHashCode() { return basis.GetHashCode() ^ origin.GetHashCode(); } + /// <summary> + /// Converts this <see cref="Transform3D"/> to a string. + /// </summary> + /// <returns>A string representation of this transform.</returns> public override string ToString() { return $"[X: {basis.x}, Y: {basis.y}, Z: {basis.z}, O: {origin}]"; } + /// <summary> + /// Converts this <see cref="Transform3D"/> to a string with the given <paramref name="format"/>. + /// </summary> + /// <returns>A string representation of this transform.</returns> public string ToString(string format) { return $"[X: {basis.x.ToString(format)}, Y: {basis.y.ToString(format)}, Z: {basis.z.ToString(format)}, O: {origin.ToString(format)}]"; diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/UnhandledExceptionArgs.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/UnhandledExceptionArgs.cs index be01674568..eae8927ceb 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/UnhandledExceptionArgs.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/UnhandledExceptionArgs.cs @@ -8,7 +8,7 @@ namespace Godot public class UnhandledExceptionArgs { /// <summary> - /// Exception object + /// Exception object. /// </summary> public Exception Exception { get; private set; } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs index b4c4ddabb9..fe70d71cce 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs @@ -21,23 +21,36 @@ namespace Godot /// </summary> public enum Axis { + /// <summary> + /// The vector's X axis. + /// </summary> X = 0, + /// <summary> + /// The vector's Y axis. + /// </summary> Y } /// <summary> - /// The vector's X component. Also accessible by using the index position `[0]`. + /// The vector's X component. Also accessible by using the index position <c>[0]</c>. /// </summary> public real_t x; + /// <summary> - /// The vector's Y component. Also accessible by using the index position `[1]`. + /// The vector's Y component. Also accessible by using the index position <c>[1]</c>. /// </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> + /// <exception cref="IndexOutOfRangeException"> + /// Thrown when the given the <paramref name="index"/> is not 0 or 1. + /// </exception> + /// <value> + /// <c>[0]</c> is equivalent to <see cref="x"/>, + /// <c>[1]</c> is equivalent to <see cref="y"/>. + /// </value> public real_t this[int index] { get @@ -97,7 +110,7 @@ namespace Godot /// 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)`. + /// called with the vector's <see cref="y"/> and <see cref="x"/> as parameters: <c>Mathf.Atan2(v.y, v.x)</c>. /// </summary> /// <returns>The angle of this vector, in radians.</returns> public real_t Angle() @@ -126,9 +139,9 @@ namespace Godot } /// <summary> - /// Returns the aspect ratio of this vector, the ratio of `x` to `y`. + /// Returns the aspect ratio of this vector, the ratio of <see cref="x"/> to <see cref="y"/>. /// </summary> - /// <returns>The `x` component divided by the `y` component.</returns> + /// <returns>The <see cref="x"/> component divided by the <see cref="y"/> component.</returns> public real_t Aspect() { return x / y; @@ -155,7 +168,7 @@ namespace Godot /// <summary> /// Returns a new vector with all components clamped between the - /// components of `min` and `max` using + /// components of <paramref name="min"/> and <paramref name="max"/> using /// <see cref="Mathf.Clamp(real_t, real_t, real_t)"/>. /// </summary> /// <param name="min">The vector with minimum allowed values.</param> @@ -171,21 +184,22 @@ namespace Godot } /// <summary> - /// Returns the cross product of this vector and `b`. + /// Returns the cross product of this vector and <paramref name="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; + 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`. + /// Performs a cubic interpolation between vectors <paramref name="preA"/>, this vector, + /// <paramref name="b"/>, and <paramref name="postB"/>, by the given amount <paramref name="weight"/>. /// </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="postB">A vector after <paramref name="b"/>.</param> /// <param name="weight">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 weight) @@ -199,24 +213,26 @@ namespace Godot real_t t2 = t * t; real_t t3 = t2 * t; - return 0.5f * (p1 * 2.0f + - (-p0 + p2) * t + - (2.0f * p0 - 5.0f * p1 + 4 * p2 - p3) * t2 + - (-p0 + 3.0f * p1 - 3.0f * p2 + p3) * t3); + return 0.5f * ( + (p1 * 2.0f) + + ((-p0 + p2) * t) + + (((2.0f * p0) - (5.0f * p1) + (4 * p2) - p3) * t2) + + ((-p0 + (3.0f * p1) - (3.0f * p2) + p3) * t3) + ); } /// <summary> - /// Returns the normalized vector pointing from this vector to `b`. + /// Returns the normalized vector pointing from this vector to <paramref name="b"/>. /// </summary> /// <param name="b">The other vector to point towards.</param> - /// <returns>The direction from this vector to `b`.</returns> + /// <returns>The direction from this vector to <paramref name="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`. + /// Returns the squared distance between this vector and <paramref name="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> @@ -228,7 +244,7 @@ namespace Godot } /// <summary> - /// Returns the distance between this vector and `to`. + /// Returns the distance between this vector and <paramref name="to"/>. /// </summary> /// <param name="to">The other vector to use.</param> /// <returns>The distance between the two vectors.</returns> @@ -238,13 +254,13 @@ namespace Godot } /// <summary> - /// Returns the dot product of this vector and `with`. + /// Returns the dot product of this vector and <paramref name="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; + return (x * with.x) + (y * with.y); } /// <summary> @@ -257,7 +273,7 @@ namespace Godot } /// <summary> - /// Returns the inverse of this vector. This is the same as `new Vector2(1 / v.x, 1 / v.y)`. + /// Returns the inverse of this vector. This is the same as <c>new Vector2(1 / v.x, 1 / v.y)</c>. /// </summary> /// <returns>The inverse of this vector.</returns> public Vector2 Inverse() @@ -266,9 +282,9 @@ namespace Godot } /// <summary> - /// Returns true if the vector is normalized, and false otherwise. + /// Returns <see langword="true"/> if the vector is normalized, and <see langword="false"/> otherwise. /// </summary> - /// <returns>A bool indicating whether or not the vector is normalized.</returns> + /// <returns>A <see langword="bool"/> indicating whether or not the vector is normalized.</returns> public bool IsNormalized() { return Mathf.Abs(LengthSquared() - 1.0f) < Mathf.Epsilon; @@ -277,10 +293,11 @@ namespace Godot /// <summary> /// Returns the length (magnitude) of this vector. /// </summary> + /// <seealso cref="LengthSquared"/> /// <returns>The length of this vector.</returns> public real_t Length() { - return Mathf.Sqrt(x * x + y * y); + return Mathf.Sqrt((x * x) + (y * y)); } /// <summary> @@ -291,12 +308,12 @@ namespace Godot /// <returns>The squared length of this vector.</returns> public real_t LengthSquared() { - return x * x + y * y; + return (x * x) + (y * y); } /// <summary> /// Returns the result of the linear interpolation between - /// this vector and `to` by amount `weight`. + /// this vector and <paramref name="to"/> by amount <paramref name="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> @@ -312,10 +329,12 @@ namespace Godot /// <summary> /// Returns the result of the linear interpolation between - /// this vector and `to` by the vector amount `weight`. + /// this vector and <paramref name="to"/> by the vector amount <paramref name="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> + /// <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) { @@ -327,7 +346,7 @@ namespace Godot } /// <summary> - /// Returns the vector with a maximum length by limiting its length to `length`. + /// Returns the vector with a maximum length by limiting its length to <paramref name="length"/>. /// </summary> /// <param name="length">The length to limit to.</param> /// <returns>The vector with its length limited.</returns> @@ -366,35 +385,41 @@ namespace Godot } /// <summary> - /// Moves this vector toward `to` by the fixed `delta` amount. + /// Moves this vector toward <paramref name="to"/> by the fixed <paramref name="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; - var vd = to - v; - var len = vd.Length(); - return len <= delta || len < Mathf.Epsilon ? to : v + vd / len * delta; + Vector2 v = this; + Vector2 vd = to - v; + real_t len = vd.Length(); + if (len <= delta || len < Mathf.Epsilon) + return to; + + return v + (vd / len * delta); } /// <summary> - /// Returns the vector scaled to unit length. Equivalent to `v / v.Length()`. + /// Returns the vector scaled to unit length. Equivalent to <c>v / v.Length()</c>. /// </summary> /// <returns>A normalized version of the vector.</returns> public Vector2 Normalized() { - var v = this; + Vector2 v = this; v.Normalize(); return v; } /// <summary> - /// Returns a vector composed of the <see cref="Mathf.PosMod(real_t, real_t)"/> of this vector's components and `mod`. + /// Returns a vector composed of the <see cref="Mathf.PosMod(real_t, real_t)"/> of this vector's components + /// and <paramref name="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> + /// <returns> + /// A vector with each component <see cref="Mathf.PosMod(real_t, real_t)"/> by <paramref name="mod"/>. + /// </returns> public Vector2 PosMod(real_t mod) { Vector2 v; @@ -404,10 +429,13 @@ namespace Godot } /// <summary> - /// Returns a vector composed of the <see cref="Mathf.PosMod(real_t, real_t)"/> of this vector's components and `modv`'s components. + /// Returns a vector composed of the <see cref="Mathf.PosMod(real_t, real_t)"/> of this vector's components + /// and <paramref name="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> + /// <returns> + /// A vector with each component <see cref="Mathf.PosMod(real_t, real_t)"/> by <paramref name="modv"/>'s components. + /// </returns> public Vector2 PosMod(Vector2 modv) { Vector2 v; @@ -417,7 +445,7 @@ namespace Godot } /// <summary> - /// Returns this vector projected onto another vector `b`. + /// Returns this vector projected onto another vector <paramref name="onNormal"/>. /// </summary> /// <param name="onNormal">The vector to project onto.</param> /// <returns>The projected vector.</returns> @@ -427,7 +455,7 @@ namespace Godot } /// <summary> - /// Returns this vector reflected from a plane defined by the given `normal`. + /// Returns this vector reflected from a plane defined by the given <paramref name="normal"/>. /// </summary> /// <param name="normal">The normal vector defining the plane to reflect from. Must be normalized.</param> /// <returns>The reflected vector.</returns> @@ -439,11 +467,11 @@ namespace Godot throw new ArgumentException("Argument is not normalized", nameof(normal)); } #endif - return 2 * Dot(normal) * normal - this; + return (2 * Dot(normal) * normal) - this; } /// <summary> - /// Rotates this vector by `phi` radians. + /// Rotates this vector by <paramref name="phi"/> radians. /// </summary> /// <param name="phi">The angle to rotate by, in radians.</param> /// <returns>The rotated vector.</returns> @@ -471,7 +499,7 @@ namespace Godot /// 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> + /// <returns>A vector with all components as either <c>1</c>, <c>-1</c>, or <c>0</c>.</returns> public Vector2 Sign() { Vector2 v; @@ -482,7 +510,7 @@ namespace Godot /// <summary> /// Returns the result of the spherical linear interpolation between - /// this vector and `to` by amount `weight`. + /// this vector and <paramref name="to"/> by amount <paramref name="weight"/>. /// /// Note: Both vectors must be normalized. /// </summary> @@ -498,24 +526,24 @@ namespace Godot } if (!to.IsNormalized()) { - throw new InvalidOperationException("Vector2.Slerp: `to` is not normalized."); + throw new InvalidOperationException($"Vector2.Slerp: `{nameof(to)}` is not normalized."); } #endif return Rotated(AngleTo(to) * weight); } /// <summary> - /// Returns this vector slid along a plane defined by the given normal. + /// Returns this vector slid along a plane defined by the given <paramref name="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 - normal * Dot(normal); + return this - (normal * Dot(normal)); } /// <summary> - /// Returns this vector with each component snapped to the nearest multiple of `step`. + /// Returns this vector with each component snapped to the nearest multiple of <paramref name="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> @@ -546,40 +574,40 @@ namespace Godot private static readonly Vector2 _left = new Vector2(-1, 0); /// <summary> - /// Zero vector, a vector with all components set to `0`. + /// Zero vector, a vector with all components set to <c>0</c>. /// </summary> - /// <value>Equivalent to `new Vector2(0, 0)`</value> + /// <value>Equivalent to <c>new Vector2(0, 0)</c>.</value> public static Vector2 Zero { get { return _zero; } } /// <summary> - /// One vector, a vector with all components set to `1`. + /// One vector, a vector with all components set to <c>1</c>. /// </summary> - /// <value>Equivalent to `new Vector2(1, 1)`</value> + /// <value>Equivalent to <c>new Vector2(1, 1)</c>.</value> public static Vector2 One { get { return _one; } } /// <summary> - /// Infinity vector, a vector with all components set to `Mathf.Inf`. + /// Infinity vector, a vector with all components set to <see cref="Mathf.Inf"/>. /// </summary> - /// <value>Equivalent to `new Vector2(Mathf.Inf, Mathf.Inf)`</value> + /// <value>Equivalent to <c>new Vector2(Mathf.Inf, Mathf.Inf)</c>.</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> + /// <value>Equivalent to <c>new Vector2(0, -1)</c>.</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> + /// <value>Equivalent to <c>new Vector2(0, 1)</c>.</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> + /// <value>Equivalent to <c>new Vector2(1, 0)</c>.</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> + /// <value>Equivalent to <c>new Vector2(-1, 0)</c>.</value> public static Vector2 Left { get { return _left; } } /// <summary> @@ -605,7 +633,7 @@ namespace Godot /// <summary> /// Creates a unit Vector2 rotated to the given angle. This is equivalent to doing - /// `Vector2(Mathf.Cos(angle), Mathf.Sin(angle))` or `Vector2.Right.Rotated(angle)`. + /// <c>Vector2(Mathf.Cos(angle), Mathf.Sin(angle))</c> or <c>Vector2.Right.Rotated(angle)</c>. /// </summary> /// <param name="angle">Angle of the vector, in radians.</param> /// <returns>The resulting vector.</returns> @@ -730,6 +758,11 @@ namespace Godot return left.x >= right.x; } + /// <summary> + /// Returns <see langword="true"/> if this vector and <paramref name="obj"/> are equal. + /// </summary> + /// <param name="obj">The other object to compare.</param> + /// <returns>Whether or not the vector and the other object are equal.</returns> public override bool Equals(object obj) { if (obj is Vector2) @@ -739,14 +772,19 @@ namespace Godot return false; } + /// <summary> + /// Returns <see langword="true"/> if this vector and <paramref name="other"/> are equal. + /// </summary> + /// <param name="other">The other vector to compare.</param> + /// <returns>Whether or not the vectors are equal.</returns> public bool Equals(Vector2 other) { 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. + /// Returns <see langword="true"/> if this vector and <paramref name="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> @@ -755,16 +793,28 @@ namespace Godot return Mathf.IsEqualApprox(x, other.x) && Mathf.IsEqualApprox(y, other.y); } + /// <summary> + /// Serves as the hash function for <see cref="Vector2"/>. + /// </summary> + /// <returns>A hash code for this vector.</returns> public override int GetHashCode() { return y.GetHashCode() ^ x.GetHashCode(); } + /// <summary> + /// Converts this <see cref="Vector2"/> to a string. + /// </summary> + /// <returns>A string representation of this vector.</returns> public override string ToString() { return $"({x}, {y})"; } + /// <summary> + /// Converts this <see cref="Vector2"/> to a string with the given <paramref name="format"/>. + /// </summary> + /// <returns>A string representation of this vector.</returns> public string ToString(string format) { return $"({x.ToString(format)}, {y.ToString(format)})"; diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2i.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2i.cs index 959f262f52..ca4531d885 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2i.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2i.cs @@ -21,23 +21,36 @@ namespace Godot /// </summary> public enum Axis { + /// <summary> + /// The vector's X axis. + /// </summary> X = 0, + /// <summary> + /// The vector's Y axis. + /// </summary> Y } /// <summary> - /// The vector's X component. Also accessible by using the index position `[0]`. + /// The vector's X component. Also accessible by using the index position <c>[0]</c>. /// </summary> public int x; + /// <summary> - /// The vector's Y component. Also accessible by using the index position `[1]`. + /// The vector's Y component. Also accessible by using the index position <c>[1]</c>. /// </summary> public int y; /// <summary> /// Access vector components using their index. /// </summary> - /// <value>`[0]` is equivalent to `.x`, `[1]` is equivalent to `.y`.</value> + /// <exception cref="IndexOutOfRangeException"> + /// Thrown when the given the <paramref name="index"/> is not 0 or 1. + /// </exception> + /// <value> + /// <c>[0]</c> is equivalent to <see cref="x"/>, + /// <c>[1]</c> is equivalent to <see cref="y"/>. + /// </value> public int this[int index] { get @@ -81,7 +94,7 @@ namespace Godot /// 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)`. + /// called with the vector's <see cref="y"/> and <see cref="x"/> as parameters: <c>Mathf.Atan2(v.y, v.x)</c>. /// </summary> /// <returns>The angle of this vector, in radians.</returns> public real_t Angle() @@ -110,9 +123,9 @@ namespace Godot } /// <summary> - /// Returns the aspect ratio of this vector, the ratio of `x` to `y`. + /// Returns the aspect ratio of this vector, the ratio of <see cref="x"/> to <see cref="y"/>. /// </summary> - /// <returns>The `x` component divided by the `y` component.</returns> + /// <returns>The <see cref="x"/> component divided by the <see cref="y"/> component.</returns> public real_t Aspect() { return x / (real_t)y; @@ -120,7 +133,7 @@ namespace Godot /// <summary> /// Returns a new vector with all components clamped between the - /// components of `min` and `max` using + /// components of <paramref name="min"/> and <paramref name="max"/> using /// <see cref="Mathf.Clamp(int, int, int)"/>. /// </summary> /// <param name="min">The vector with minimum allowed values.</param> @@ -136,7 +149,7 @@ namespace Godot } /// <summary> - /// Returns the cross product of this vector and `b`. + /// Returns the cross product of this vector and <paramref name="b"/>. /// </summary> /// <param name="b">The other vector.</param> /// <returns>The cross product vector.</returns> @@ -146,7 +159,7 @@ namespace Godot } /// <summary> - /// Returns the squared distance between this vector and `b`. + /// Returns the squared distance between this vector and <paramref name="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> @@ -158,7 +171,7 @@ namespace Godot } /// <summary> - /// Returns the distance between this vector and `b`. + /// Returns the distance between this vector and <paramref name="b"/>. /// </summary> /// <param name="b">The other vector to use.</param> /// <returns>The distance between the two vectors.</returns> @@ -168,7 +181,7 @@ namespace Godot } /// <summary> - /// Returns the dot product of this vector and `b`. + /// Returns the dot product of this vector and <paramref name="b"/>. /// </summary> /// <param name="b">The other vector to use.</param> /// <returns>The dot product of the two vectors.</returns> @@ -180,6 +193,7 @@ namespace Godot /// <summary> /// Returns the length (magnitude) of this vector. /// </summary> + /// <seealso cref="LengthSquared"/> /// <returns>The length of this vector.</returns> public real_t Length() { @@ -224,10 +238,13 @@ namespace Godot } /// <summary> - /// Returns a vector composed of the <see cref="Mathf.PosMod(int, int)"/> of this vector's components and `mod`. + /// Returns a vector composed of the <see cref="Mathf.PosMod(int, int)"/> of this vector's components + /// and <paramref name="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> + /// <returns> + /// A vector with each component <see cref="Mathf.PosMod(int, int)"/> by <paramref name="mod"/>. + /// </returns> public Vector2i PosMod(int mod) { Vector2i v = this; @@ -237,10 +254,13 @@ namespace Godot } /// <summary> - /// Returns a vector composed of the <see cref="Mathf.PosMod(int, int)"/> of this vector's components and `modv`'s components. + /// Returns a vector composed of the <see cref="Mathf.PosMod(int, int)"/> of this vector's components + /// and <paramref name="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> + /// <returns> + /// A vector with each component <see cref="Mathf.PosMod(int, int)"/> by <paramref name="modv"/>'s components. + /// </returns> public Vector2i PosMod(Vector2i modv) { Vector2i v = this; @@ -254,7 +274,7 @@ namespace Godot /// 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> + /// <returns>A vector with all components as either <c>1</c>, <c>-1</c>, or <c>0</c>.</returns> public Vector2i Sign() { Vector2i v = this; @@ -283,35 +303,35 @@ namespace Godot private static readonly Vector2i _left = new Vector2i(-1, 0); /// <summary> - /// Zero vector, a vector with all components set to `0`. + /// Zero vector, a vector with all components set to <c>0</c>. /// </summary> - /// <value>Equivalent to `new Vector2i(0, 0)`</value> + /// <value>Equivalent to <c>new Vector2i(0, 0)</c>.</value> public static Vector2i Zero { get { return _zero; } } /// <summary> - /// One vector, a vector with all components set to `1`. + /// One vector, a vector with all components set to <c>1</c>. /// </summary> - /// <value>Equivalent to `new Vector2i(1, 1)`</value> + /// <value>Equivalent to <c>new Vector2i(1, 1)</c>.</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> + /// <value>Equivalent to <c>new Vector2i(0, -1)</c>.</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> + /// <value>Equivalent to <c>new Vector2i(0, 1)</c>.</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> + /// <value>Equivalent to <c>new Vector2i(1, 0)</c>.</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> + /// <value>Equivalent to <c>new Vector2i(-1, 0)</c>.</value> public static Vector2i Left { get { return _left; } } /// <summary> @@ -476,16 +496,29 @@ namespace Godot return left.x >= right.x; } + /// <summary> + /// Converts this <see cref="Vector2i"/> to a <see cref="Vector2"/>. + /// </summary> + /// <param name="value">The vector to convert.</param> public static implicit operator Vector2(Vector2i value) { return new Vector2(value.x, value.y); } + /// <summary> + /// Converts a <see cref="Vector2"/> to a <see cref="Vector2i"/>. + /// </summary> + /// <param name="value">The vector to convert.</param> public static explicit operator Vector2i(Vector2 value) { return new Vector2i(value); } + /// <summary> + /// Returns <see langword="true"/> if this vector and <paramref name="obj"/> are equal. + /// </summary> + /// <param name="obj">The other object to compare.</param> + /// <returns>Whether or not the vector and the other object are equal.</returns> public override bool Equals(object obj) { if (obj is Vector2i) @@ -496,21 +529,38 @@ namespace Godot return false; } + /// <summary> + /// Returns <see langword="true"/> if this vector and <paramref name="other"/> are equal. + /// </summary> + /// <param name="other">The other vector to compare.</param> + /// <returns>Whether or not the vectors are equal.</returns> public bool Equals(Vector2i other) { return x == other.x && y == other.y; } + /// <summary> + /// Serves as the hash function for <see cref="Vector2i"/>. + /// </summary> + /// <returns>A hash code for this vector.</returns> public override int GetHashCode() { return y.GetHashCode() ^ x.GetHashCode(); } + /// <summary> + /// Converts this <see cref="Vector2i"/> to a string. + /// </summary> + /// <returns>A string representation of this vector.</returns> public override string ToString() { return $"({x}, {y})"; } + /// <summary> + /// Converts this <see cref="Vector2i"/> to a string with the given <paramref name="format"/>. + /// </summary> + /// <returns>A string representation of this vector.</returns> public string ToString(string format) { return $"({x.ToString(format)}, {y.ToString(format)})"; diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs index bdf64159e9..01e3a71bcb 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs @@ -21,28 +21,46 @@ namespace Godot /// </summary> public enum Axis { + /// <summary> + /// The vector's X axis. + /// </summary> X = 0, + /// <summary> + /// The vector's Y axis. + /// </summary> Y, + /// <summary> + /// The vector's Z axis. + /// </summary> Z } /// <summary> - /// The vector's X component. Also accessible by using the index position `[0]`. + /// The vector's X component. Also accessible by using the index position <c>[0]</c>. /// </summary> public real_t x; + /// <summary> - /// The vector's Y component. Also accessible by using the index position `[1]`. + /// The vector's Y component. Also accessible by using the index position <c>[1]</c>. /// </summary> public real_t y; + /// <summary> - /// The vector's Z component. Also accessible by using the index position `[2]`. + /// The vector's Z component. Also accessible by using the index position <c>[2]</c>. /// </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> + /// <exception cref="IndexOutOfRangeException"> + /// Thrown when the given the <paramref name="index"/> is not 0, 1 or 2. + /// </exception> + /// <value> + /// <c>[0]</c> is equivalent to <see cref="x"/>, + /// <c>[1]</c> is equivalent to <see cref="y"/>, + /// <c>[2]</c> is equivalent to <see cref="z"/>. + /// </value> public real_t this[int index] { get @@ -135,7 +153,7 @@ namespace Godot /// <summary> /// Returns a new vector with all components clamped between the - /// components of `min` and `max` using + /// components of <paramref name="min"/> and <paramref name="max"/> using /// <see cref="Mathf.Clamp(real_t, real_t, real_t)"/>. /// </summary> /// <param name="min">The vector with minimum allowed values.</param> @@ -152,7 +170,7 @@ namespace Godot } /// <summary> - /// Returns the cross product of this vector and `b`. + /// Returns the cross product of this vector and <paramref name="b"/>. /// </summary> /// <param name="b">The other vector.</param> /// <returns>The cross product vector.</returns> @@ -160,19 +178,19 @@ namespace Godot { return new Vector3 ( - y * b.z - z * b.y, - z * b.x - x * b.z, - x * b.y - y * b.x + (y * b.z) - (z * b.y), + (z * b.x) - (x * b.z), + (x * b.y) - (y * b.x) ); } /// <summary> - /// Performs a cubic interpolation between vectors `preA`, this vector, - /// `b`, and `postB`, by the given amount `t`. + /// Performs a cubic interpolation between vectors <paramref name="preA"/>, this vector, + /// <paramref name="b"/>, and <paramref name="postB"/>, by the given amount <paramref name="weight"/>. /// </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="postB">A vector after <paramref name="b"/>.</param> /// <param name="weight">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 weight) @@ -187,24 +205,24 @@ namespace Godot real_t t3 = t2 * t; return 0.5f * ( - p1 * 2.0f + (-p0 + p2) * t + - (2.0f * p0 - 5.0f * p1 + 4f * p2 - p3) * t2 + - (-p0 + 3.0f * p1 - 3.0f * p2 + p3) * t3 - ); + (p1 * 2.0f) + ((-p0 + p2) * t) + + (((2.0f * p0) - (5.0f * p1) + (4f * p2) - p3) * t2) + + ((-p0 + (3.0f * p1) - (3.0f * p2) + p3) * t3) + ); } /// <summary> - /// Returns the normalized vector pointing from this vector to `b`. + /// Returns the normalized vector pointing from this vector to <paramref name="b"/>. /// </summary> /// <param name="b">The other vector to point towards.</param> - /// <returns>The direction from this vector to `b`.</returns> + /// <returns>The direction from this vector to <paramref name="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`. + /// Returns the squared distance between this vector and <paramref name="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> @@ -216,8 +234,9 @@ namespace Godot } /// <summary> - /// Returns the distance between this vector and `b`. + /// Returns the distance between this vector and <paramref name="b"/>. /// </summary> + /// <seealso cref="DistanceSquaredTo(Vector3)"/> /// <param name="b">The other vector to use.</param> /// <returns>The distance between the two vectors.</returns> public real_t DistanceTo(Vector3 b) @@ -226,13 +245,13 @@ namespace Godot } /// <summary> - /// Returns the dot product of this vector and `b`. + /// Returns the dot product of this vector and <paramref name="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; + return (x * b.x) + (y * b.y) + (z * b.z); } /// <summary> @@ -245,7 +264,7 @@ namespace Godot } /// <summary> - /// Returns the inverse of this vector. This is the same as `new Vector3(1 / v.x, 1 / v.y, 1 / v.z)`. + /// Returns the inverse of this vector. This is the same as <c>new Vector3(1 / v.x, 1 / v.y, 1 / v.z)</c>. /// </summary> /// <returns>The inverse of this vector.</returns> public Vector3 Inverse() @@ -254,9 +273,9 @@ namespace Godot } /// <summary> - /// Returns true if the vector is normalized, and false otherwise. + /// Returns <see langword="true"/> if the vector is normalized, and <see langword="false"/> otherwise. /// </summary> - /// <returns>A bool indicating whether or not the vector is normalized.</returns> + /// <returns>A <see langword="bool"/> indicating whether or not the vector is normalized.</returns> public bool IsNormalized() { return Mathf.Abs(LengthSquared() - 1.0f) < Mathf.Epsilon; @@ -265,6 +284,7 @@ namespace Godot /// <summary> /// Returns the length (magnitude) of this vector. /// </summary> + /// <seealso cref="LengthSquared"/> /// <returns>The length of this vector.</returns> public real_t Length() { @@ -292,7 +312,7 @@ namespace Godot /// <summary> /// Returns the result of the linear interpolation between - /// this vector and `to` by amount `weight`. + /// this vector and <paramref name="to"/> by amount <paramref name="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> @@ -309,7 +329,7 @@ namespace Godot /// <summary> /// Returns the result of the linear interpolation between - /// this vector and `to` by the vector amount `weight`. + /// this vector and <paramref name="to"/> by the vector amount <paramref name="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> @@ -325,7 +345,7 @@ namespace Godot } /// <summary> - /// Returns the vector with a maximum length by limiting its length to `length`. + /// Returns the vector with a maximum length by limiting its length to <paramref name="length"/>. /// </summary> /// <param name="length">The length to limit to.</param> /// <returns>The vector with its length limited.</returns> @@ -364,32 +384,35 @@ namespace Godot } /// <summary> - /// Moves this vector toward `to` by the fixed `delta` amount. + /// Moves this vector toward <paramref name="to"/> by the fixed <paramref name="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; + Vector3 v = this; + Vector3 vd = to - v; + real_t len = vd.Length(); + if (len <= delta || len < Mathf.Epsilon) + return to; + + return v + (vd / len * delta); } /// <summary> - /// Returns the vector scaled to unit length. Equivalent to `v / v.Length()`. + /// Returns the vector scaled to unit length. Equivalent to <c>v / v.Length()</c>. /// </summary> /// <returns>A normalized version of the vector.</returns> public Vector3 Normalized() { - var v = this; + Vector3 v = this; v.Normalize(); return v; } /// <summary> - /// Returns the outer product with `b`. + /// Returns the outer product with <paramref name="b"/>. /// </summary> /// <param name="b">The other vector.</param> /// <returns>A <see cref="Basis"/> representing the outer product matrix.</returns> @@ -403,10 +426,13 @@ namespace Godot } /// <summary> - /// Returns a vector composed of the <see cref="Mathf.PosMod(real_t, real_t)"/> of this vector's components and `mod`. + /// Returns a vector composed of the <see cref="Mathf.PosMod(real_t, real_t)"/> of this vector's components + /// and <paramref name="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> + /// <returns> + /// A vector with each component <see cref="Mathf.PosMod(real_t, real_t)"/> by <paramref name="mod"/>. + /// </returns> public Vector3 PosMod(real_t mod) { Vector3 v; @@ -417,10 +443,13 @@ namespace Godot } /// <summary> - /// Returns a vector composed of the <see cref="Mathf.PosMod(real_t, real_t)"/> of this vector's components and `modv`'s components. + /// Returns a vector composed of the <see cref="Mathf.PosMod(real_t, real_t)"/> of this vector's components + /// and <paramref name="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> + /// <returns> + /// A vector with each component <see cref="Mathf.PosMod(real_t, real_t)"/> by <paramref name="modv"/>'s components. + /// </returns> public Vector3 PosMod(Vector3 modv) { Vector3 v; @@ -431,7 +460,7 @@ namespace Godot } /// <summary> - /// Returns this vector projected onto another vector `b`. + /// Returns this vector projected onto another vector <paramref name="onNormal"/>. /// </summary> /// <param name="onNormal">The vector to project onto.</param> /// <returns>The projected vector.</returns> @@ -441,7 +470,7 @@ namespace Godot } /// <summary> - /// Returns this vector reflected from a plane defined by the given `normal`. + /// Returns this vector reflected from a plane defined by the given <paramref name="normal"/>. /// </summary> /// <param name="normal">The normal vector defining the plane to reflect from. Must be normalized.</param> /// <returns>The reflected vector.</returns> @@ -453,12 +482,12 @@ namespace Godot throw new ArgumentException("Argument is not normalized", nameof(normal)); } #endif - return 2.0f * Dot(normal) * normal - this; + return (2.0f * Dot(normal) * normal) - this; } /// <summary> - /// Rotates this vector around a given `axis` vector by `phi` radians. - /// The `axis` vector must be a normalized vector. + /// Rotates this vector around a given <paramref name="axis"/> vector by <paramref name="phi"/> radians. + /// The <paramref name="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> @@ -489,7 +518,7 @@ namespace Godot /// 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> + /// <returns>A vector with all components as either <c>1</c>, <c>-1</c>, or <c>0</c>.</returns> public Vector3 Sign() { Vector3 v; @@ -503,7 +532,7 @@ namespace Godot /// Returns the signed angle to the given vector, in radians. /// The sign of the angle is positive in a counter-clockwise /// direction and negative in a clockwise direction when viewed - /// from the side specified by the `axis`. + /// from the side specified by the <paramref name="axis"/>. /// </summary> /// <param name="to">The other vector to compare this vector to.</param> /// <param name="axis">The reference axis to use for the angle sign.</param> @@ -518,7 +547,7 @@ namespace Godot /// <summary> /// Returns the result of the spherical linear interpolation between - /// this vector and `to` by amount `weight`. + /// this vector and <paramref name="to"/> by amount <paramref name="weight"/>. /// /// Note: Both vectors must be normalized. /// </summary> @@ -534,7 +563,7 @@ namespace Godot } if (!to.IsNormalized()) { - throw new InvalidOperationException("Vector3.Slerp: `to` is not normalized."); + throw new InvalidOperationException($"Vector3.Slerp: `{nameof(to)}` is not normalized."); } #endif real_t theta = AngleTo(to); @@ -542,17 +571,17 @@ namespace Godot } /// <summary> - /// Returns this vector slid along a plane defined by the given normal. + /// Returns this vector slid along a plane defined by the given <paramref name="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 - normal * Dot(normal); + return this - (normal * Dot(normal)); } /// <summary> - /// Returns this vector with each component snapped to the nearest multiple of `step`. + /// Returns this vector with each component snapped to the nearest multiple of <paramref name="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> @@ -570,10 +599,10 @@ namespace Godot /// <summary> /// Returns a diagonal matrix with the vector as main diagonal. /// - /// This is equivalent to a Basis with no rotation or shearing and + /// This is equivalent to a <see cref="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> + /// <returns>A <see cref="Basis"/> with the vector as its main diagonal.</returns> public Basis ToDiagonalMatrix() { return new Basis( @@ -596,54 +625,54 @@ namespace Godot private static readonly Vector3 _back = new Vector3(0, 0, 1); /// <summary> - /// Zero vector, a vector with all components set to `0`. + /// Zero vector, a vector with all components set to <c>0</c>. /// </summary> - /// <value>Equivalent to `new Vector3(0, 0, 0)`</value> + /// <value>Equivalent to <c>new Vector3(0, 0, 0)</c>.</value> public static Vector3 Zero { get { return _zero; } } /// <summary> - /// One vector, a vector with all components set to `1`. + /// One vector, a vector with all components set to <c>1</c>. /// </summary> - /// <value>Equivalent to `new Vector3(1, 1, 1)`</value> + /// <value>Equivalent to <c>new Vector3(1, 1, 1)</c>.</value> public static Vector3 One { get { return _one; } } /// <summary> - /// Infinity vector, a vector with all components set to `Mathf.Inf`. + /// Infinity vector, a vector with all components set to <see cref="Mathf.Inf"/>. /// </summary> - /// <value>Equivalent to `new Vector3(Mathf.Inf, Mathf.Inf, Mathf.Inf)`</value> + /// <value>Equivalent to <c>new Vector3(Mathf.Inf, Mathf.Inf, Mathf.Inf)</c>.</value> public static Vector3 Inf { get { return _inf; } } /// <summary> /// Up unit vector. /// </summary> - /// <value>Equivalent to `new Vector3(0, 1, 0)`</value> + /// <value>Equivalent to <c>new Vector3(0, 1, 0)</c>.</value> public static Vector3 Up { get { return _up; } } /// <summary> /// Down unit vector. /// </summary> - /// <value>Equivalent to `new Vector3(0, -1, 0)`</value> + /// <value>Equivalent to <c>new Vector3(0, -1, 0)</c>.</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> + /// <value>Equivalent to <c>new Vector3(1, 0, 0)</c>.</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> + /// <value>Equivalent to <c>new Vector3(-1, 0, 0)</c>.</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> + /// <value>Equivalent to <c>new Vector3(0, 0, -1)</c>.</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> + /// <value>Equivalent to <c>new Vector3(0, 0, 1)</c>.</value> public static Vector3 Back { get { return _back; } } /// <summary> @@ -812,6 +841,11 @@ namespace Godot return left.x > right.x; } + /// <summary> + /// Returns <see langword="true"/> if this vector and <paramref name="obj"/> are equal. + /// </summary> + /// <param name="obj">The other object to compare.</param> + /// <returns>Whether or not the vector and the other object are equal.</returns> public override bool Equals(object obj) { if (obj is Vector3) @@ -822,14 +856,19 @@ namespace Godot return false; } + /// <summary> + /// Returns <see langword="true"/> if this vector and <paramref name="other"/> are equal + /// </summary> + /// <param name="other">The other vector to compare.</param> + /// <returns>Whether or not the vectors are equal.</returns> public bool Equals(Vector3 other) { 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. + /// Returns <see langword="true"/> if this vector and <paramref name="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> @@ -838,16 +877,28 @@ namespace Godot return Mathf.IsEqualApprox(x, other.x) && Mathf.IsEqualApprox(y, other.y) && Mathf.IsEqualApprox(z, other.z); } + /// <summary> + /// Serves as the hash function for <see cref="Vector3"/>. + /// </summary> + /// <returns>A hash code for this vector.</returns> public override int GetHashCode() { return y.GetHashCode() ^ x.GetHashCode() ^ z.GetHashCode(); } + /// <summary> + /// Converts this <see cref="Vector3"/> to a string. + /// </summary> + /// <returns>A string representation of this vector.</returns> public override string ToString() { return $"({x}, {y}, {z})"; } + /// <summary> + /// Converts this <see cref="Vector3"/> to a string with the given <paramref name="format"/>. + /// </summary> + /// <returns>A string representation of this vector.</returns> public string ToString(string format) { return $"({x.ToString(format)}, {y.ToString(format)}, {z.ToString(format)})"; diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3i.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3i.cs index c96a7cf1b0..2a7771cdfc 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3i.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3i.cs @@ -21,28 +21,46 @@ namespace Godot /// </summary> public enum Axis { + /// <summary> + /// The vector's X axis. + /// </summary> X = 0, + /// <summary> + /// The vector's Y axis. + /// </summary> Y, + /// <summary> + /// The vector's Z axis. + /// </summary> Z } /// <summary> - /// The vector's X component. Also accessible by using the index position `[0]`. + /// The vector's X component. Also accessible by using the index position <c>[0]</c>. /// </summary> public int x; + /// <summary> - /// The vector's Y component. Also accessible by using the index position `[1]`. + /// The vector's Y component. Also accessible by using the index position <c>[1]</c>. /// </summary> public int y; + /// <summary> - /// The vector's Z component. Also accessible by using the index position `[2]`. + /// The vector's Z component. Also accessible by using the index position <c>[2]</c>. /// </summary> public int z; /// <summary> - /// Access vector components using their index. + /// Access vector components using their <paramref name="index"/>. /// </summary> - /// <value>`[0]` is equivalent to `.x`, `[1]` is equivalent to `.y`, `[2]` is equivalent to `.z`.</value> + /// <exception cref="IndexOutOfRangeException"> + /// Thrown when the given the <paramref name="index"/> is not 0, 1 or 2. + /// </exception> + /// <value> + /// <c>[0]</c> is equivalent to <see cref="x"/>, + /// <c>[1]</c> is equivalent to <see cref="y"/>, + /// <c>[2]</c> is equivalent to <see cref="z"/>. + /// </value> public int this[int index] { get @@ -89,7 +107,7 @@ namespace Godot /// <summary> /// Returns a new vector with all components clamped between the - /// components of `min` and `max` using + /// components of <paramref name="min"/> and <paramref name="max"/> using /// <see cref="Mathf.Clamp(int, int, int)"/>. /// </summary> /// <param name="min">The vector with minimum allowed values.</param> @@ -106,7 +124,7 @@ namespace Godot } /// <summary> - /// Returns the squared distance between this vector and `b`. + /// Returns the squared distance between this vector and <paramref name="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> @@ -118,8 +136,9 @@ namespace Godot } /// <summary> - /// Returns the distance between this vector and `b`. + /// Returns the distance between this vector and <paramref name="b"/>. /// </summary> + /// <seealso cref="DistanceSquaredTo(Vector3i)"/> /// <param name="b">The other vector to use.</param> /// <returns>The distance between the two vectors.</returns> public real_t DistanceTo(Vector3i b) @@ -128,7 +147,7 @@ namespace Godot } /// <summary> - /// Returns the dot product of this vector and `b`. + /// Returns the dot product of this vector and <paramref name="b"/>. /// </summary> /// <param name="b">The other vector to use.</param> /// <returns>The dot product of the two vectors.</returns> @@ -140,6 +159,7 @@ namespace Godot /// <summary> /// Returns the length (magnitude) of this vector. /// </summary> + /// <seealso cref="LengthSquared"/> /// <returns>The length of this vector.</returns> public real_t Length() { @@ -186,10 +206,13 @@ namespace Godot } /// <summary> - /// Returns a vector composed of the <see cref="Mathf.PosMod(int, int)"/> of this vector's components and `mod`. + /// Returns a vector composed of the <see cref="Mathf.PosMod(int, int)"/> of this vector's components + /// and <paramref name="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> + /// <returns> + /// A vector with each component <see cref="Mathf.PosMod(int, int)"/> by <paramref name="mod"/>. + /// </returns> public Vector3i PosMod(int mod) { Vector3i v = this; @@ -200,10 +223,13 @@ namespace Godot } /// <summary> - /// Returns a vector composed of the <see cref="Mathf.PosMod(int, int)"/> of this vector's components and `modv`'s components. + /// Returns a vector composed of the <see cref="Mathf.PosMod(int, int)"/> of this vector's components + /// and <paramref name="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> + /// <returns> + /// A vector with each component <see cref="Mathf.PosMod(int, int)"/> by <paramref name="modv"/>'s components. + /// </returns> public Vector3i PosMod(Vector3i modv) { Vector3i v = this; @@ -218,7 +244,7 @@ namespace Godot /// 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> + /// <returns>A vector with all components as either <c>1</c>, <c>-1</c>, or <c>0</c>.</returns> public Vector3i Sign() { Vector3i v = this; @@ -240,49 +266,49 @@ namespace Godot private static readonly Vector3i _back = new Vector3i(0, 0, 1); /// <summary> - /// Zero vector, a vector with all components set to `0`. + /// Zero vector, a vector with all components set to <c>0</c>. /// </summary> - /// <value>Equivalent to `new Vector3i(0, 0, 0)`</value> + /// <value>Equivalent to <c>new Vector3i(0, 0, 0)</c>.</value> public static Vector3i Zero { get { return _zero; } } /// <summary> - /// One vector, a vector with all components set to `1`. + /// One vector, a vector with all components set to <c>1</c>. /// </summary> - /// <value>Equivalent to `new Vector3i(1, 1, 1)`</value> + /// <value>Equivalent to <c>new Vector3i(1, 1, 1)</c>.</value> public static Vector3i One { get { return _one; } } /// <summary> /// Up unit vector. /// </summary> - /// <value>Equivalent to `new Vector3i(0, 1, 0)`</value> + /// <value>Equivalent to <c>new Vector3i(0, 1, 0)</c>.</value> public static Vector3i Up { get { return _up; } } /// <summary> /// Down unit vector. /// </summary> - /// <value>Equivalent to `new Vector3i(0, -1, 0)`</value> + /// <value>Equivalent to <c>new Vector3i(0, -1, 0)</c>.</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> + /// <value>Equivalent to <c>new Vector3i(1, 0, 0)</c>.</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> + /// <value>Equivalent to <c>new Vector3i(-1, 0, 0)</c>.</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> + /// <value>Equivalent to <c>new Vector3i(0, 0, -1)</c>.</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> + /// <value>Equivalent to <c>new Vector3i(0, 0, 1)</c>.</value> public static Vector3i Back { get { return _back; } } /// <summary> @@ -479,16 +505,29 @@ namespace Godot return left.x > right.x; } + /// <summary> + /// Converts this <see cref="Vector3i"/> to a <see cref="Vector3"/>. + /// </summary> + /// <param name="value">The vector to convert.</param> public static implicit operator Vector3(Vector3i value) { return new Vector3(value.x, value.y, value.z); } + /// <summary> + /// Converts a <see cref="Vector3"/> to a <see cref="Vector3i"/>. + /// </summary> + /// <param name="value">The vector to convert.</param> public static explicit operator Vector3i(Vector3 value) { return new Vector3i(value); } + /// <summary> + /// Returns <see langword="true"/> if this vector and <paramref name="obj"/> are equal. + /// </summary> + /// <param name="obj">The other object to compare.</param> + /// <returns>Whether or not the vector and the other object are equal.</returns> public override bool Equals(object obj) { if (obj is Vector3i) @@ -499,21 +538,38 @@ namespace Godot return false; } + /// <summary> + /// Returns <see langword="true"/> if this vector and <paramref name="other"/> are equal + /// </summary> + /// <param name="other">The other vector to compare.</param> + /// <returns>Whether or not the vectors are equal.</returns> public bool Equals(Vector3i other) { return x == other.x && y == other.y && z == other.z; } + /// <summary> + /// Serves as the hash function for <see cref="Vector3i"/>. + /// </summary> + /// <returns>A hash code for this vector.</returns> public override int GetHashCode() { return y.GetHashCode() ^ x.GetHashCode() ^ z.GetHashCode(); } + /// <summary> + /// Converts this <see cref="Vector3i"/> to a string. + /// </summary> + /// <returns>A string representation of this vector.</returns> public override string ToString() { return $"({x}, {y}, {z})"; } + /// <summary> + /// Converts this <see cref="Vector3i"/> to a string with the given <paramref name="format"/>. + /// </summary> + /// <returns>A string representation of this vector.</returns> public string ToString(string format) { return $"({x.ToString(format)}, {y.ToString(format)}, {z.ToString(format)})"; diff --git a/modules/mono/utils/string_utils.cpp b/modules/mono/utils/string_utils.cpp index 053618ebe4..2fb5e446da 100644 --- a/modules/mono/utils/string_utils.cpp +++ b/modules/mono/utils/string_utils.cpp @@ -200,7 +200,7 @@ String str_format(const char *p_format, ...) { return res; } -#if defined(MINGW_ENABLED) || defined(_MSC_VER) && _MSC_VER < 1900 +#if defined(MINGW_ENABLED) #define gd_vsnprintf(m_buffer, m_count, m_format, m_args_copy) vsnprintf_s(m_buffer, m_count, _TRUNCATE, m_format, m_args_copy) #define gd_vscprintf(m_format, m_args_copy) _vscprintf(m_format, m_args_copy) #else diff --git a/modules/ogg/config.py b/modules/ogg/config.py index d22f9454ed..5a417ba8dd 100644 --- a/modules/ogg/config.py +++ b/modules/ogg/config.py @@ -4,3 +4,14 @@ def can_build(env, platform): def configure(env): pass + + +def get_doc_classes(): + return [ + "OGGPacketSequence", + "OGGPacketSequencePlayback", + ] + + +def get_doc_path(): + return "doc_classes" diff --git a/modules/ogg/doc_classes/OGGPacketSequence.xml b/modules/ogg/doc_classes/OGGPacketSequence.xml new file mode 100644 index 0000000000..9d3789cb07 --- /dev/null +++ b/modules/ogg/doc_classes/OGGPacketSequence.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="OGGPacketSequence" inherits="Resource" version="4.0"> + <brief_description> + A sequence of OGG packets. + </brief_description> + <description> + A sequence of OGG packets. + </description> + <tutorials> + </tutorials> + <methods> + <method name="get_length" qualifiers="const"> + <return type="float" /> + <description> + The length of this stream, in seconds. + </description> + </method> + </methods> + <members> + <member name="granule_positions" type="Array" setter="set_packet_granule_positions" getter="get_packet_granule_positions" default="[]"> + Contains the granule positions for each page in this packet sequence. + </member> + <member name="packet_data" type="Array" setter="set_packet_data" getter="get_packet_data" default="[]"> + Contains the raw packets that make up this OGGPacketSequence. + </member> + <member name="sampling_rate" type="float" setter="set_sampling_rate" getter="get_sampling_rate" default="0.0"> + Holds sample rate information about this sequence. Must be set by another class that actually understands the codec. + </member> + </members> + <constants> + </constants> +</class> diff --git a/modules/ogg/doc_classes/OGGPacketSequencePlayback.xml b/modules/ogg/doc_classes/OGGPacketSequencePlayback.xml new file mode 100644 index 0000000000..49e32f0d6e --- /dev/null +++ b/modules/ogg/doc_classes/OGGPacketSequencePlayback.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="OGGPacketSequencePlayback" inherits="RefCounted" version="4.0"> + <brief_description> + </brief_description> + <description> + </description> + <tutorials> + </tutorials> + <methods> + </methods> + <constants> + </constants> +</class> diff --git a/modules/ogg/ogg_packet_sequence.cpp b/modules/ogg/ogg_packet_sequence.cpp new file mode 100644 index 0000000000..b7a3ad2876 --- /dev/null +++ b/modules/ogg/ogg_packet_sequence.cpp @@ -0,0 +1,220 @@ +/*************************************************************************/ +/* ogg_packet_sequence.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "ogg_packet_sequence.h" +#include "core/variant/typed_array.h" + +void OGGPacketSequence::push_page(int64_t p_granule_pos, const Vector<PackedByteArray> &p_data) { + Vector<PackedByteArray> data_stored; + for (int i = 0; i < p_data.size(); i++) { + data_stored.push_back(p_data[i]); + } + page_granule_positions.push_back(p_granule_pos); + page_data.push_back(data_stored); + data_version++; +} + +void OGGPacketSequence::set_packet_data(const Array &p_data) { + data_version++; // Update the data version so old playbacks know that they can't rely on us anymore. + page_data.clear(); + for (int page_idx = 0; page_idx < p_data.size(); page_idx++) { + // Push a new page. We cleared the vector so this will be at index `page_idx`. + page_data.push_back(Vector<PackedByteArray>()); + TypedArray<PackedByteArray> this_page_data = p_data[page_idx]; + for (int packet = 0; packet < this_page_data.size(); packet++) { + page_data.write[page_idx].push_back(this_page_data[packet]); + } + } +} + +Array OGGPacketSequence::get_packet_data() const { + Array ret; + for (const Vector<PackedByteArray> &page : page_data) { + Array page_variant; + for (const PackedByteArray &packet : page) { + page_variant.push_back(packet); + } + ret.push_back(page_variant); + } + return ret; +} + +void OGGPacketSequence::set_packet_granule_positions(const Array &p_granule_positions) { + data_version++; // Update the data version so old playbacks know that they can't rely on us anymore. + page_granule_positions.clear(); + for (int page_idx = 0; page_idx < p_granule_positions.size(); page_idx++) { + int64_t granule_pos = p_granule_positions[page_idx]; + page_granule_positions.push_back(granule_pos); + } +} + +Array OGGPacketSequence::get_packet_granule_positions() const { + Array ret; + for (int64_t granule_pos : page_granule_positions) { + ret.push_back(granule_pos); + } + return ret; +} + +void OGGPacketSequence::set_sampling_rate(float p_sampling_rate) { + sampling_rate = p_sampling_rate; +} + +float OGGPacketSequence::get_sampling_rate() const { + return sampling_rate; +} + +int64_t OGGPacketSequence::get_final_granule_pos() const { + if (!page_granule_positions.is_empty()) { + return page_granule_positions[page_granule_positions.size() - 1]; + } + return -1; +} + +float OGGPacketSequence::get_length() const { + int64_t granule_pos = get_final_granule_pos(); + if (granule_pos < 0) { + return 0; + } + return granule_pos / sampling_rate; +} + +Ref<OGGPacketSequencePlayback> OGGPacketSequence::instance_playback() { + Ref<OGGPacketSequencePlayback> playback; + playback.instantiate(); + playback->ogg_packet_sequence = Ref<OGGPacketSequence>(this); + playback->data_version = data_version; + + return playback; +} + +void OGGPacketSequence::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_packet_data", "packet_data"), &OGGPacketSequence::set_packet_data); + ClassDB::bind_method(D_METHOD("get_packet_data"), &OGGPacketSequence::get_packet_data); + + ClassDB::bind_method(D_METHOD("set_packet_granule_positions", "granule_positions"), &OGGPacketSequence::set_packet_granule_positions); + ClassDB::bind_method(D_METHOD("get_packet_granule_positions"), &OGGPacketSequence::get_packet_granule_positions); + + ClassDB::bind_method(D_METHOD("set_sampling_rate", "sampling_rate"), &OGGPacketSequence::set_sampling_rate); + ClassDB::bind_method(D_METHOD("get_sampling_rate"), &OGGPacketSequence::get_sampling_rate); + + ClassDB::bind_method(D_METHOD("get_length"), &OGGPacketSequence::get_length); + + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "packet_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_packet_data", "get_packet_data"); + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "granule_positions", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_packet_granule_positions", "get_packet_granule_positions"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "sampling_rate", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_sampling_rate", "get_sampling_rate"); +} + +bool OGGPacketSequencePlayback::next_ogg_packet(ogg_packet **p_packet) const { + ERR_FAIL_COND_V(data_version != ogg_packet_sequence->data_version, false); + ERR_FAIL_COND_V(ogg_packet_sequence->page_data.is_empty(), false); + ERR_FAIL_COND_V(ogg_packet_sequence->page_granule_positions.is_empty(), false); + // Move on to the next page if need be. This happens first to help simplify seek logic. + while (packet_cursor >= ogg_packet_sequence->page_data[page_cursor].size()) { + packet_cursor = 0; + page_cursor++; + if (page_cursor >= ogg_packet_sequence->page_data.size()) { + return false; + } + } + + ERR_FAIL_COND_V(page_cursor >= ogg_packet_sequence->page_data.size(), false); + + packet->b_o_s = page_cursor == 0 && packet_cursor == 0; + packet->e_o_s = page_cursor == ogg_packet_sequence->page_data.size() - 1 && packet_cursor == ogg_packet_sequence->page_data[page_cursor].size() - 1; + packet->granulepos = packet_cursor == ogg_packet_sequence->page_data[page_cursor].size() - 1 ? ogg_packet_sequence->page_granule_positions[page_cursor] : -1; + packet->packetno = packetno++; + packet->bytes = ogg_packet_sequence->page_data[page_cursor][packet_cursor].size(); + packet->packet = (unsigned char *)(ogg_packet_sequence->page_data[page_cursor][packet_cursor].ptr()); + + *p_packet = packet; + + packet_cursor++; + + return true; +} + +uint32_t OGGPacketSequencePlayback::seek_page_internal(int64_t granule, uint32_t after_page_inclusive, uint32_t before_page_inclusive) { + if (before_page_inclusive == after_page_inclusive) { + return before_page_inclusive; + } + uint32_t actual_middle_page = after_page_inclusive + (before_page_inclusive - after_page_inclusive) / 2; + // Complicating the bisection search algorithm, the middle page might not have a packet that ends on it, + // which means it might not have a correct granule position. Find a nearby page that does have a packet ending on it. + uint32_t bisection_page = -1; + for (uint32_t test_page = actual_middle_page; test_page <= before_page_inclusive; test_page++) { + if (ogg_packet_sequence->page_data[test_page].size() > 0) { + bisection_page = test_page; + break; + } + } + // Check if we have to go backwards. + if (bisection_page == (unsigned int)-1) { + for (uint32_t test_page = actual_middle_page; test_page >= after_page_inclusive; test_page--) { + if (ogg_packet_sequence->page_data[test_page].size() > 0) { + bisection_page = test_page; + break; + } + } + } + if (bisection_page == (unsigned int)-1) { + return -1; + } + + int64_t bisection_granule_pos = ogg_packet_sequence->page_granule_positions[bisection_page]; + if (granule > bisection_granule_pos) { + return seek_page_internal(granule, bisection_page + 1, before_page_inclusive); + } else { + return seek_page_internal(granule, after_page_inclusive, bisection_page); + } +} + +bool OGGPacketSequencePlayback::seek_page(int64_t p_granule_pos) { + int correct_page = seek_page_internal(p_granule_pos, 0, ogg_packet_sequence->page_data.size() - 1); + if (correct_page == -1) { + return false; + } + + packet_cursor = 0; + page_cursor = correct_page; + + // Don't pretend subsequent packets are contiguous with previous ones. + packetno = 0; + + return true; +} + +OGGPacketSequencePlayback::OGGPacketSequencePlayback() { + packet = new ogg_packet(); +} + +OGGPacketSequencePlayback::~OGGPacketSequencePlayback() { + delete packet; +} diff --git a/modules/ogg/ogg_packet_sequence.h b/modules/ogg/ogg_packet_sequence.h new file mode 100644 index 0000000000..b00ada06c1 --- /dev/null +++ b/modules/ogg/ogg_packet_sequence.h @@ -0,0 +1,128 @@ +/*************************************************************************/ +/* ogg_packet_sequence.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef OGG_PACKET_SEQUENCE_H +#define OGG_PACKET_SEQUENCE_H + +#include "core/io/resource.h" +#include "core/object/gdvirtual.gen.inc" +#include "core/variant/native_ptr.h" +#include "core/variant/typed_array.h" +#include "core/variant/variant.h" +#include "thirdparty/libogg/ogg/ogg.h" + +class OGGPacketSequencePlayback; + +class OGGPacketSequence : public Resource { + GDCLASS(OGGPacketSequence, Resource); + + friend class OGGPacketSequencePlayback; + + // List of pages, each of which is a list of packets on that page. The innermost PackedByteArrays contain complete ogg packets. + Vector<Vector<PackedByteArray>> page_data; + + // List of the granule position for each page. + Vector<uint64_t> page_granule_positions; + + // The page after the current last page. Similar semantics to an end() iterator. + int64_t end_page = 0; + + uint64_t data_version = 0; + + float sampling_rate = 0; + float length = 0; + +protected: + static void _bind_methods(); + +public: + // Pushes information about all the pages that ended on this page. + // This should be called for each page, even for pages that no packets ended on. + void push_page(int64_t p_granule_pos, const Vector<PackedByteArray> &p_data); + + void set_packet_data(const Array &p_data); + Array get_packet_data() const; + + void set_packet_granule_positions(const Array &p_granule_positions); + Array get_packet_granule_positions() const; + + // Sets a sampling rate associated with this object. OGGPacketSequence doesn't understand codecs, + // so this value is naively stored as a convenience. + void set_sampling_rate(float p_sampling_rate); + + // Returns a sampling rate previously set by set_sampling_rate(). + float get_sampling_rate() const; + + // Returns a length previously set by set_length(). + float get_length() const; + + // Returns the granule position of the last page in this sequence. + int64_t get_final_granule_pos() const; + + Ref<OGGPacketSequencePlayback> instance_playback(); + + OGGPacketSequence() {} + virtual ~OGGPacketSequence() {} +}; + +class OGGPacketSequencePlayback : public RefCounted { + GDCLASS(OGGPacketSequencePlayback, RefCounted); + + friend class OGGPacketSequence; + + Ref<OGGPacketSequence> ogg_packet_sequence; + + mutable int64_t page_cursor = 0; + mutable int32_t packet_cursor = 0; + + mutable ogg_packet *packet; + + uint64_t data_version; + + mutable int64_t packetno = 0; + + // Recursive bisection search for the correct page. + uint32_t seek_page_internal(int64_t granule, uint32_t after_page_inclusive, uint32_t before_page_inclusive); + +public: + // Calling functions must not modify this packet. + // Returns true on success, false on error or if there is no next packet. + bool next_ogg_packet(ogg_packet **p_packet) const; + + // Seeks to the page such that the previous page has a granule position less than or equal to this value, + // and the current page has a granule position greater than this value. + // Returns true on success, false on failure. + bool seek_page(int64_t p_granule_pos); + + OGGPacketSequencePlayback(); + virtual ~OGGPacketSequencePlayback(); +}; + +#endif // OGG_PACKET_SEQUENCE_H diff --git a/modules/ogg/register_types.cpp b/modules/ogg/register_types.cpp index b23ea65378..3448e7063a 100644 --- a/modules/ogg/register_types.cpp +++ b/modules/ogg/register_types.cpp @@ -30,8 +30,11 @@ #include "register_types.h" -// Dummy module as libogg is needed by other modules (vorbis, theora, opus, ...) +#include "ogg_packet_sequence.h" -void register_ogg_types() {} +void register_ogg_types() { + GDREGISTER_CLASS(OGGPacketSequence); + GDREGISTER_CLASS(OGGPacketSequencePlayback); +} void unregister_ogg_types() {} diff --git a/modules/stb_vorbis/SCsub b/modules/stb_vorbis/SCsub deleted file mode 100644 index 8fddb23dc8..0000000000 --- a/modules/stb_vorbis/SCsub +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env python - -Import("env") -Import("env_modules") - -env_stb_vorbis = env_modules.Clone() - -# Thirdparty source files - -thirdparty_obj = [] - -thirdparty_sources = ["#thirdparty/misc/stb_vorbis.c"] - -env_thirdparty = env_stb_vorbis.Clone() -env_thirdparty.disable_warnings() -env_thirdparty.add_source_files(thirdparty_obj, thirdparty_sources) -env.modules_sources += thirdparty_obj - -# Godot source files - -module_obj = [] - -env_stb_vorbis.add_source_files(module_obj, "*.cpp") -env.modules_sources += module_obj - -# Needed to force rebuilding the module files when the thirdparty library is updated. -env.Depends(module_obj, thirdparty_obj) diff --git a/modules/stb_vorbis/audio_stream_ogg_vorbis.cpp b/modules/stb_vorbis/audio_stream_ogg_vorbis.cpp deleted file mode 100644 index 3a938200e9..0000000000 --- a/modules/stb_vorbis/audio_stream_ogg_vorbis.cpp +++ /dev/null @@ -1,274 +0,0 @@ -/*************************************************************************/ -/* audio_stream_ogg_vorbis.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#include "audio_stream_ogg_vorbis.h" - -#include "core/io/file_access.h" - -int AudioStreamPlaybackOGGVorbis::_mix_internal(AudioFrame *p_buffer, int p_frames) { - ERR_FAIL_COND_V(!active, 0); - - int todo = p_frames; - - int start_buffer = 0; - - int frames_mixed_this_step = p_frames; - - while (todo && active) { - float *buffer = (float *)p_buffer; - if (start_buffer > 0) { - buffer = (buffer + start_buffer * 2); - } - int mixed = stb_vorbis_get_samples_float_interleaved(ogg_stream, 2, buffer, todo * 2); - if (vorbis_stream->channels == 1 && mixed > 0) { - //mix mono to stereo - for (int i = start_buffer; i < start_buffer + mixed; i++) { - p_buffer[i].r = p_buffer[i].l; - } - } - todo -= mixed; - frames_mixed += mixed; - - if (todo) { - //end of file! - bool is_not_empty = mixed > 0 || stb_vorbis_stream_length_in_samples(ogg_stream) > 0; - if (vorbis_stream->loop && is_not_empty) { - //loop - seek(vorbis_stream->loop_offset); - loops++; - // we still have buffer to fill, start from this element in the next iteration. - start_buffer = p_frames - todo; - } else { - frames_mixed_this_step = p_frames - todo; - for (int i = p_frames - todo; i < p_frames; i++) { - p_buffer[i] = AudioFrame(0, 0); - } - active = false; - todo = 0; - } - } - } - return frames_mixed_this_step; -} - -float AudioStreamPlaybackOGGVorbis::get_stream_sampling_rate() { - return vorbis_stream->sample_rate; -} - -void AudioStreamPlaybackOGGVorbis::start(float p_from_pos) { - active = true; - seek(p_from_pos); - loops = 0; - _begin_resample(); -} - -void AudioStreamPlaybackOGGVorbis::stop() { - active = false; -} - -bool AudioStreamPlaybackOGGVorbis::is_playing() const { - return active; -} - -int AudioStreamPlaybackOGGVorbis::get_loop_count() const { - return loops; -} - -float AudioStreamPlaybackOGGVorbis::get_playback_position() const { - return float(frames_mixed) / vorbis_stream->sample_rate; -} - -void AudioStreamPlaybackOGGVorbis::seek(float p_time) { - if (!active) { - return; - } - - if (p_time >= vorbis_stream->get_length()) { - p_time = 0; - } - frames_mixed = uint32_t(vorbis_stream->sample_rate * p_time); - - stb_vorbis_seek(ogg_stream, frames_mixed); -} - -AudioStreamPlaybackOGGVorbis::~AudioStreamPlaybackOGGVorbis() { - if (ogg_alloc.alloc_buffer) { - stb_vorbis_close(ogg_stream); - memfree(ogg_alloc.alloc_buffer); - } -} - -Ref<AudioStreamPlayback> AudioStreamOGGVorbis::instance_playback() { - Ref<AudioStreamPlaybackOGGVorbis> ovs; - - ERR_FAIL_COND_V_MSG(data == nullptr, ovs, - "This AudioStreamOGGVorbis does not have an audio file assigned " - "to it. AudioStreamOGGVorbis should not be created from the " - "inspector or with `.new()`. Instead, load an audio file."); - - ovs.instantiate(); - ovs->vorbis_stream = Ref<AudioStreamOGGVorbis>(this); - ovs->ogg_alloc.alloc_buffer = (char *)memalloc(decode_mem_size); - ovs->ogg_alloc.alloc_buffer_length_in_bytes = decode_mem_size; - ovs->frames_mixed = 0; - ovs->active = false; - ovs->loops = 0; - int error; - ovs->ogg_stream = stb_vorbis_open_memory((const unsigned char *)data, data_len, &error, &ovs->ogg_alloc); - if (!ovs->ogg_stream) { - memfree(ovs->ogg_alloc.alloc_buffer); - ovs->ogg_alloc.alloc_buffer = nullptr; - ERR_FAIL_COND_V(!ovs->ogg_stream, Ref<AudioStreamPlaybackOGGVorbis>()); - } - - return ovs; -} - -String AudioStreamOGGVorbis::get_stream_name() const { - return ""; //return stream_name; -} - -void AudioStreamOGGVorbis::clear_data() { - if (data) { - memfree(data); - data = nullptr; - data_len = 0; - } -} - -void AudioStreamOGGVorbis::set_data(const Vector<uint8_t> &p_data) { - int src_data_len = p_data.size(); - uint32_t alloc_try = 1024; - Vector<char> alloc_mem; - char *w; - stb_vorbis *ogg_stream = nullptr; - stb_vorbis_alloc ogg_alloc; - - // Vorbis comments may be up to UINT32_MAX, but that's arguably pretty rare. - // Let's go with 2^30 so we don't risk going out of bounds. - const uint32_t MAX_TEST_MEM = 1 << 30; - - while (alloc_try < MAX_TEST_MEM) { - alloc_mem.resize(alloc_try); - w = alloc_mem.ptrw(); - - ogg_alloc.alloc_buffer = w; - ogg_alloc.alloc_buffer_length_in_bytes = alloc_try; - - const uint8_t *src_datar = p_data.ptr(); - - int error; - ogg_stream = stb_vorbis_open_memory((const unsigned char *)src_datar, src_data_len, &error, &ogg_alloc); - - if (!ogg_stream && error == VORBIS_outofmem) { - alloc_try *= 2; - } else { - ERR_FAIL_COND(alloc_try == MAX_TEST_MEM); - ERR_FAIL_COND(ogg_stream == nullptr); - - stb_vorbis_info info = stb_vorbis_get_info(ogg_stream); - - channels = info.channels; - sample_rate = info.sample_rate; - decode_mem_size = alloc_try; - //does this work? (it's less mem..) - //decode_mem_size = ogg_alloc.alloc_buffer_length_in_bytes + info.setup_memory_required + info.temp_memory_required + info.max_frame_size; - - length = stb_vorbis_stream_length_in_seconds(ogg_stream); - stb_vorbis_close(ogg_stream); - - // free any existing data - clear_data(); - - data = memalloc(src_data_len); - memcpy(data, src_datar, src_data_len); - data_len = src_data_len; - - break; - } - } - - ERR_FAIL_COND_MSG(alloc_try == MAX_TEST_MEM, vformat("Couldn't set vorbis data even with an alloc buffer of %d bytes, report bug.", MAX_TEST_MEM)); -} - -Vector<uint8_t> AudioStreamOGGVorbis::get_data() const { - Vector<uint8_t> vdata; - - if (data_len && data) { - vdata.resize(data_len); - { - uint8_t *w = vdata.ptrw(); - memcpy(w, data, data_len); - } - } - - return vdata; -} - -void AudioStreamOGGVorbis::set_loop(bool p_enable) { - loop = p_enable; -} - -bool AudioStreamOGGVorbis::has_loop() const { - return loop; -} - -void AudioStreamOGGVorbis::set_loop_offset(float p_seconds) { - loop_offset = p_seconds; -} - -float AudioStreamOGGVorbis::get_loop_offset() const { - return loop_offset; -} - -float AudioStreamOGGVorbis::get_length() const { - return length; -} - -void AudioStreamOGGVorbis::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_data", "data"), &AudioStreamOGGVorbis::set_data); - ClassDB::bind_method(D_METHOD("get_data"), &AudioStreamOGGVorbis::get_data); - - ClassDB::bind_method(D_METHOD("set_loop", "enable"), &AudioStreamOGGVorbis::set_loop); - ClassDB::bind_method(D_METHOD("has_loop"), &AudioStreamOGGVorbis::has_loop); - - ClassDB::bind_method(D_METHOD("set_loop_offset", "seconds"), &AudioStreamOGGVorbis::set_loop_offset); - ClassDB::bind_method(D_METHOD("get_loop_offset"), &AudioStreamOGGVorbis::get_loop_offset); - - ADD_PROPERTY(PropertyInfo(Variant::PACKED_BYTE_ARRAY, "data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_data", "get_data"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "loop"), "set_loop", "has_loop"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "loop_offset"), "set_loop_offset", "get_loop_offset"); -} - -AudioStreamOGGVorbis::AudioStreamOGGVorbis() {} - -AudioStreamOGGVorbis::~AudioStreamOGGVorbis() { - clear_data(); -} diff --git a/modules/stb_vorbis/config.py b/modules/stb_vorbis/config.py deleted file mode 100644 index 1eb0a8cf33..0000000000 --- a/modules/stb_vorbis/config.py +++ /dev/null @@ -1,16 +0,0 @@ -def can_build(env, platform): - return True - - -def configure(env): - pass - - -def get_doc_classes(): - return [ - "AudioStreamOGGVorbis", - ] - - -def get_doc_path(): - return "doc_classes" diff --git a/modules/text_server_adv/text_server_adv.cpp b/modules/text_server_adv/text_server_adv.cpp index 78a87be971..19e94adf68 100644 --- a/modules/text_server_adv/text_server_adv.cpp +++ b/modules/text_server_adv/text_server_adv.cpp @@ -440,266 +440,260 @@ bool TextServerAdvanced::is_locale_right_to_left(const String &p_locale) { } } -struct FeatureInfo { - int32_t tag; - String name; -}; +static Map<StringName, int32_t> feature_sets; -static FeatureInfo feature_set[] = { +static void _insert_feature_sets() { // Registered OpenType feature tags. - { HB_TAG('a', 'a', 'l', 't'), "access_all_alternates" }, - { HB_TAG('a', 'b', 'v', 'f'), "above_base_forms" }, - { HB_TAG('a', 'b', 'v', 'm'), "above_base_mark_positioning" }, - { HB_TAG('a', 'b', 'v', 's'), "above_base_substitutions" }, - { HB_TAG('a', 'f', 'r', 'c'), "alternative_fractions" }, - { HB_TAG('a', 'k', 'h', 'n'), "akhands" }, - { HB_TAG('b', 'l', 'w', 'f'), "below_base_forms" }, - { HB_TAG('b', 'l', 'w', 'm'), "below_base_mark_positioning" }, - { HB_TAG('b', 'l', 'w', 's'), "below_base_substitutions" }, - { HB_TAG('c', 'a', 'l', 't'), "contextual_alternates" }, - { HB_TAG('c', 'a', 's', 'e'), "case_sensitive_forms" }, - { HB_TAG('c', 'c', 'm', 'p'), "glyph_composition" }, - { HB_TAG('c', 'f', 'a', 'r'), "conjunct_form_after_ro" }, - { HB_TAG('c', 'j', 'c', 't'), "conjunct_forms" }, - { HB_TAG('c', 'l', 'i', 'g'), "contextual_ligatures" }, - { HB_TAG('c', 'p', 'c', 't'), "centered_cjk_punctuation" }, - { HB_TAG('c', 'p', 's', 'p'), "capital_spacing" }, - { HB_TAG('c', 's', 'w', 'h'), "contextual_swash" }, - { HB_TAG('c', 'u', 'r', 's'), "cursive_positioning" }, - { HB_TAG('c', 'v', '0', '1'), "character_variant_01" }, - { HB_TAG('c', 'v', '0', '2'), "character_variant_02" }, - { HB_TAG('c', 'v', '0', '3'), "character_variant_03" }, - { HB_TAG('c', 'v', '0', '4'), "character_variant_04" }, - { HB_TAG('c', 'v', '0', '5'), "character_variant_05" }, - { HB_TAG('c', 'v', '0', '6'), "character_variant_06" }, - { HB_TAG('c', 'v', '0', '7'), "character_variant_07" }, - { HB_TAG('c', 'v', '0', '8'), "character_variant_08" }, - { HB_TAG('c', 'v', '0', '9'), "character_variant_09" }, - { HB_TAG('c', 'v', '1', '0'), "character_variant_10" }, - { HB_TAG('c', 'v', '1', '1'), "character_variant_11" }, - { HB_TAG('c', 'v', '1', '2'), "character_variant_12" }, - { HB_TAG('c', 'v', '1', '3'), "character_variant_13" }, - { HB_TAG('c', 'v', '1', '4'), "character_variant_14" }, - { HB_TAG('c', 'v', '1', '5'), "character_variant_15" }, - { HB_TAG('c', 'v', '1', '6'), "character_variant_16" }, - { HB_TAG('c', 'v', '1', '7'), "character_variant_17" }, - { HB_TAG('c', 'v', '1', '8'), "character_variant_18" }, - { HB_TAG('c', 'v', '1', '9'), "character_variant_19" }, - { HB_TAG('c', 'v', '2', '0'), "character_variant_20" }, - { HB_TAG('c', 'v', '2', '1'), "character_variant_21" }, - { HB_TAG('c', 'v', '2', '2'), "character_variant_22" }, - { HB_TAG('c', 'v', '2', '3'), "character_variant_23" }, - { HB_TAG('c', 'v', '2', '4'), "character_variant_24" }, - { HB_TAG('c', 'v', '2', '5'), "character_variant_25" }, - { HB_TAG('c', 'v', '2', '6'), "character_variant_26" }, - { HB_TAG('c', 'v', '2', '7'), "character_variant_27" }, - { HB_TAG('c', 'v', '2', '8'), "character_variant_28" }, - { HB_TAG('c', 'v', '2', '9'), "character_variant_29" }, - { HB_TAG('c', 'v', '3', '0'), "character_variant_30" }, - { HB_TAG('c', 'v', '3', '1'), "character_variant_31" }, - { HB_TAG('c', 'v', '3', '2'), "character_variant_32" }, - { HB_TAG('c', 'v', '3', '3'), "character_variant_33" }, - { HB_TAG('c', 'v', '3', '4'), "character_variant_34" }, - { HB_TAG('c', 'v', '3', '5'), "character_variant_35" }, - { HB_TAG('c', 'v', '3', '6'), "character_variant_36" }, - { HB_TAG('c', 'v', '3', '7'), "character_variant_37" }, - { HB_TAG('c', 'v', '3', '8'), "character_variant_38" }, - { HB_TAG('c', 'v', '3', '9'), "character_variant_39" }, - { HB_TAG('c', 'v', '4', '0'), "character_variant_40" }, - { HB_TAG('c', 'v', '4', '1'), "character_variant_41" }, - { HB_TAG('c', 'v', '4', '2'), "character_variant_42" }, - { HB_TAG('c', 'v', '4', '3'), "character_variant_43" }, - { HB_TAG('c', 'v', '4', '4'), "character_variant_44" }, - { HB_TAG('c', 'v', '4', '5'), "character_variant_45" }, - { HB_TAG('c', 'v', '4', '6'), "character_variant_46" }, - { HB_TAG('c', 'v', '4', '7'), "character_variant_47" }, - { HB_TAG('c', 'v', '4', '8'), "character_variant_48" }, - { HB_TAG('c', 'v', '4', '9'), "character_variant_49" }, - { HB_TAG('c', 'v', '5', '0'), "character_variant_50" }, - { HB_TAG('c', 'v', '5', '1'), "character_variant_51" }, - { HB_TAG('c', 'v', '5', '2'), "character_variant_52" }, - { HB_TAG('c', 'v', '5', '3'), "character_variant_53" }, - { HB_TAG('c', 'v', '5', '4'), "character_variant_54" }, - { HB_TAG('c', 'v', '5', '5'), "character_variant_55" }, - { HB_TAG('c', 'v', '5', '6'), "character_variant_56" }, - { HB_TAG('c', 'v', '5', '7'), "character_variant_57" }, - { HB_TAG('c', 'v', '5', '8'), "character_variant_58" }, - { HB_TAG('c', 'v', '5', '9'), "character_variant_59" }, - { HB_TAG('c', 'v', '6', '0'), "character_variant_60" }, - { HB_TAG('c', 'v', '6', '1'), "character_variant_61" }, - { HB_TAG('c', 'v', '6', '2'), "character_variant_62" }, - { HB_TAG('c', 'v', '6', '3'), "character_variant_63" }, - { HB_TAG('c', 'v', '6', '4'), "character_variant_64" }, - { HB_TAG('c', 'v', '6', '5'), "character_variant_65" }, - { HB_TAG('c', 'v', '6', '6'), "character_variant_66" }, - { HB_TAG('c', 'v', '6', '7'), "character_variant_67" }, - { HB_TAG('c', 'v', '6', '8'), "character_variant_68" }, - { HB_TAG('c', 'v', '6', '9'), "character_variant_69" }, - { HB_TAG('c', 'v', '7', '0'), "character_variant_70" }, - { HB_TAG('c', 'v', '7', '1'), "character_variant_71" }, - { HB_TAG('c', 'v', '7', '2'), "character_variant_72" }, - { HB_TAG('c', 'v', '7', '3'), "character_variant_73" }, - { HB_TAG('c', 'v', '7', '4'), "character_variant_74" }, - { HB_TAG('c', 'v', '7', '5'), "character_variant_75" }, - { HB_TAG('c', 'v', '7', '6'), "character_variant_76" }, - { HB_TAG('c', 'v', '7', '7'), "character_variant_77" }, - { HB_TAG('c', 'v', '7', '8'), "character_variant_78" }, - { HB_TAG('c', 'v', '7', '9'), "character_variant_79" }, - { HB_TAG('c', 'v', '8', '0'), "character_variant_80" }, - { HB_TAG('c', 'v', '8', '1'), "character_variant_81" }, - { HB_TAG('c', 'v', '8', '2'), "character_variant_82" }, - { HB_TAG('c', 'v', '8', '3'), "character_variant_83" }, - { HB_TAG('c', 'v', '8', '4'), "character_variant_84" }, - { HB_TAG('c', 'v', '8', '5'), "character_variant_85" }, - { HB_TAG('c', 'v', '8', '6'), "character_variant_86" }, - { HB_TAG('c', 'v', '8', '7'), "character_variant_87" }, - { HB_TAG('c', 'v', '8', '8'), "character_variant_88" }, - { HB_TAG('c', 'v', '8', '9'), "character_variant_89" }, - { HB_TAG('c', 'v', '9', '0'), "character_variant_90" }, - { HB_TAG('c', 'v', '9', '1'), "character_variant_91" }, - { HB_TAG('c', 'v', '9', '2'), "character_variant_92" }, - { HB_TAG('c', 'v', '9', '3'), "character_variant_93" }, - { HB_TAG('c', 'v', '9', '4'), "character_variant_94" }, - { HB_TAG('c', 'v', '9', '5'), "character_variant_95" }, - { HB_TAG('c', 'v', '9', '6'), "character_variant_96" }, - { HB_TAG('c', 'v', '9', '7'), "character_variant_97" }, - { HB_TAG('c', 'v', '9', '8'), "character_variant_98" }, - { HB_TAG('c', 'v', '9', '9'), "character_variant_99" }, - { HB_TAG('c', '2', 'p', 'c'), "petite_capitals_from_capitals" }, - { HB_TAG('c', '2', 's', 'c'), "small_capitals_from_capitals" }, - { HB_TAG('d', 'i', 's', 't'), "distances" }, - { HB_TAG('d', 'l', 'i', 'g'), "discretionary_ligatures" }, - { HB_TAG('d', 'n', 'o', 'm'), "denominators" }, - { HB_TAG('d', 't', 'l', 's'), "dotless_forms" }, - { HB_TAG('e', 'x', 'p', 't'), "expert_forms" }, - { HB_TAG('f', 'a', 'l', 't'), "final_glyph_on_line_alternates" }, - { HB_TAG('f', 'i', 'n', '2'), "terminal_forms_2" }, - { HB_TAG('f', 'i', 'n', '3'), "terminal_forms_3" }, - { HB_TAG('f', 'i', 'n', 'a'), "terminal_forms" }, - { HB_TAG('f', 'l', 'a', 'c'), "flattened_accent_forms" }, - { HB_TAG('f', 'r', 'a', 'c'), "fractions" }, - { HB_TAG('f', 'w', 'i', 'd'), "full_widths" }, - { HB_TAG('h', 'a', 'l', 'f'), "half_forms" }, - { HB_TAG('h', 'a', 'l', 'n'), "halant_forms" }, - { HB_TAG('h', 'a', 'l', 't'), "alternate_half_widths" }, - { HB_TAG('h', 'i', 's', 't'), "historical_forms" }, - { HB_TAG('h', 'k', 'n', 'a'), "horizontal_kana_alternates" }, - { HB_TAG('h', 'l', 'i', 'g'), "historical_ligatures" }, - { HB_TAG('h', 'n', 'g', 'l'), "hangul" }, - { HB_TAG('h', 'o', 'j', 'o'), "hojo_kanji_forms" }, - { HB_TAG('h', 'w', 'i', 'd'), "half_widths" }, - { HB_TAG('i', 'n', 'i', 't'), "initial_forms" }, - { HB_TAG('i', 's', 'o', 'l'), "isolated_forms" }, - { HB_TAG('i', 't', 'a', 'l'), "italics" }, - { HB_TAG('j', 'a', 'l', 't'), "justification_alternates" }, - { HB_TAG('j', 'p', '7', '8'), "jis78_forms" }, - { HB_TAG('j', 'p', '8', '3'), "jis83_forms" }, - { HB_TAG('j', 'p', '9', '0'), "jis90_forms" }, - { HB_TAG('j', 'p', '0', '4'), "jis2004_forms" }, - { HB_TAG('k', 'e', 'r', 'n'), "kerning" }, - { HB_TAG('l', 'f', 'b', 'd'), "left_bounds" }, - { HB_TAG('l', 'i', 'g', 'a'), "standard_ligatures" }, - { HB_TAG('l', 'j', 'm', 'o'), "leading_jamo_forms" }, - { HB_TAG('l', 'n', 'u', 'm'), "lining_figures" }, - { HB_TAG('l', 'o', 'c', 'l'), "localized_forms" }, - { HB_TAG('l', 't', 'r', 'a'), "left_to_right_alternates" }, - { HB_TAG('l', 't', 'r', 'm'), "left_to_right_mirrored_forms" }, - { HB_TAG('m', 'a', 'r', 'k'), "mark_positioning" }, - { HB_TAG('m', 'e', 'd', '2'), "medial_forms_2" }, - { HB_TAG('m', 'e', 'd', 'i'), "medial_forms" }, - { HB_TAG('m', 'g', 'r', 'k'), "mathematical_greek" }, - { HB_TAG('m', 'k', 'm', 'k'), "mark_to_mark_positioning" }, - { HB_TAG('m', 's', 'e', 't'), "mark_positioning_via_substitution" }, - { HB_TAG('n', 'a', 'l', 't'), "alternate_annotation_forms" }, - { HB_TAG('n', 'l', 'c', 'k'), "nlc_kanji_forms" }, - { HB_TAG('n', 'u', 'k', 't'), "nukta_forms" }, - { HB_TAG('n', 'u', 'm', 'r'), "numerators" }, - { HB_TAG('o', 'n', 'u', 'm'), "oldstyle_figures" }, - { HB_TAG('o', 'p', 'b', 'd'), "optical_bounds" }, - { HB_TAG('o', 'r', 'd', 'n'), "ordinals" }, - { HB_TAG('o', 'r', 'n', 'm'), "ornaments" }, - { HB_TAG('p', 'a', 'l', 't'), "proportional_alternate_widths" }, - { HB_TAG('p', 'c', 'a', 'p'), "petite_capitals" }, - { HB_TAG('p', 'k', 'n', 'a'), "proportional_kana" }, - { HB_TAG('p', 'n', 'u', 'm'), "proportional_figures" }, - { HB_TAG('p', 'r', 'e', 'f'), "pre_base_forms" }, - { HB_TAG('p', 'r', 'e', 's'), "pre_base_substitutions" }, - { HB_TAG('p', 's', 't', 'f'), "post_base_forms" }, - { HB_TAG('p', 's', 't', 's'), "post_base_substitutions" }, - { HB_TAG('p', 'w', 'i', 'd'), "proportional_widths" }, - { HB_TAG('q', 'w', 'i', 'd'), "quarter_widths" }, - { HB_TAG('r', 'a', 'n', 'd'), "randomize" }, - { HB_TAG('r', 'c', 'l', 't'), "required_contextual_alternates" }, - { HB_TAG('r', 'k', 'r', 'f'), "rakar_forms" }, - { HB_TAG('r', 'l', 'i', 'g'), "required_ligatures" }, - { HB_TAG('r', 'p', 'h', 'f'), "reph_forms" }, - { HB_TAG('r', 't', 'b', 'd'), "right_bounds" }, - { HB_TAG('r', 't', 'l', 'a'), "right_to_left_alternates" }, - { HB_TAG('r', 't', 'l', 'm'), "right_to_left_mirrored_forms" }, - { HB_TAG('r', 'u', 'b', 'y'), "ruby_notation_forms" }, - { HB_TAG('r', 'v', 'r', 'n'), "required_variation_alternates" }, - { HB_TAG('s', 'a', 'l', 't'), "stylistic_alternates" }, - { HB_TAG('s', 'i', 'n', 'f'), "scientific_inferiors" }, - { HB_TAG('s', 'i', 'z', 'e'), "optical_size" }, - { HB_TAG('s', 'm', 'c', 'p'), "small_capitals" }, - { HB_TAG('s', 'm', 'p', 'l'), "simplified_forms" }, - { HB_TAG('s', 's', '0', '1'), "stylistic_set_01" }, - { HB_TAG('s', 's', '0', '2'), "stylistic_set_02" }, - { HB_TAG('s', 's', '0', '3'), "stylistic_set_03" }, - { HB_TAG('s', 's', '0', '4'), "stylistic_set_04" }, - { HB_TAG('s', 's', '0', '5'), "stylistic_set_05" }, - { HB_TAG('s', 's', '0', '6'), "stylistic_set_06" }, - { HB_TAG('s', 's', '0', '7'), "stylistic_set_07" }, - { HB_TAG('s', 's', '0', '8'), "stylistic_set_08" }, - { HB_TAG('s', 's', '0', '9'), "stylistic_set_09" }, - { HB_TAG('s', 's', '1', '0'), "stylistic_set_10" }, - { HB_TAG('s', 's', '1', '1'), "stylistic_set_11" }, - { HB_TAG('s', 's', '1', '2'), "stylistic_set_12" }, - { HB_TAG('s', 's', '1', '3'), "stylistic_set_13" }, - { HB_TAG('s', 's', '1', '4'), "stylistic_set_14" }, - { HB_TAG('s', 's', '1', '5'), "stylistic_set_15" }, - { HB_TAG('s', 's', '1', '6'), "stylistic_set_16" }, - { HB_TAG('s', 's', '1', '7'), "stylistic_set_17" }, - { HB_TAG('s', 's', '1', '8'), "stylistic_set_18" }, - { HB_TAG('s', 's', '1', '9'), "stylistic_set_19" }, - { HB_TAG('s', 's', '2', '0'), "stylistic_set_20" }, - { HB_TAG('s', 's', 't', 'y'), "math_script_style_alternates" }, - { HB_TAG('s', 't', 'c', 'h'), "stretching_glyph_decomposition" }, - { HB_TAG('s', 'u', 'b', 's'), "subscript" }, - { HB_TAG('s', 'u', 'p', 's'), "superscript" }, - { HB_TAG('s', 'w', 's', 'h'), "swash" }, - { HB_TAG('t', 'i', 't', 'l'), "titling" }, - { HB_TAG('t', 'j', 'm', 'o'), "trailing_jamo_forms" }, - { HB_TAG('t', 'n', 'a', 'm'), "traditional_name_forms" }, - { HB_TAG('t', 'n', 'u', 'm'), "tabular_figures" }, - { HB_TAG('t', 'r', 'a', 'd'), "traditional_forms" }, - { HB_TAG('t', 'w', 'i', 'd'), "third_widths" }, - { HB_TAG('u', 'n', 'i', 'c'), "unicase" }, - { HB_TAG('v', 'a', 'l', 't'), "alternate_vertical_metrics" }, - { HB_TAG('v', 'a', 't', 'u'), "vattu_variants" }, - { HB_TAG('v', 'e', 'r', 't'), "vertical_writing" }, - { HB_TAG('v', 'h', 'a', 'l'), "alternate_vertical_half_metrics" }, - { HB_TAG('v', 'j', 'm', 'o'), "vowel_jamo_forms" }, - { HB_TAG('v', 'k', 'n', 'a'), "vertical_kana_alternates" }, - { HB_TAG('v', 'k', 'r', 'n'), "vertical_kerning" }, - { HB_TAG('v', 'p', 'a', 'l'), "proportional_alternate_vertical_metrics" }, - { HB_TAG('v', 'r', 't', '2'), "vertical_alternates_and_rotation" }, - { HB_TAG('v', 'r', 't', 'r'), "vertical_alternates_for_rotation" }, - { HB_TAG('z', 'e', 'r', 'o'), "slashed_zero" }, - // Registered OpenType variation tags. - { HB_TAG('i', 't', 'a', 'l'), "italic" }, - { HB_TAG('o', 'p', 's', 'z'), "optical_size" }, - { HB_TAG('s', 'l', 'n', 't'), "slant" }, - { HB_TAG('w', 'd', 't', 'h'), "width" }, - { HB_TAG('w', 'g', 'h', 't'), "weight" }, - { 0, String() }, -}; + feature_sets.insert("access_all_alternates", HB_TAG('a', 'a', 'l', 't')); + feature_sets.insert("above_base_forms", HB_TAG('a', 'b', 'v', 'f')); + feature_sets.insert("above_base_mark_positioning", HB_TAG('a', 'b', 'v', 'm')); + feature_sets.insert("above_base_substitutions", HB_TAG('a', 'b', 'v', 's')); + feature_sets.insert("alternative_fractions", HB_TAG('a', 'f', 'r', 'c')); + feature_sets.insert("akhands", HB_TAG('a', 'k', 'h', 'n')); + feature_sets.insert("below_base_forms", HB_TAG('b', 'l', 'w', 'f')); + feature_sets.insert("below_base_mark_positioning", HB_TAG('b', 'l', 'w', 'm')); + feature_sets.insert("below_base_substitutions", HB_TAG('b', 'l', 'w', 's')); + feature_sets.insert("contextual_alternates", HB_TAG('c', 'a', 'l', 't')); + feature_sets.insert("case_sensitive_forms", HB_TAG('c', 'a', 's', 'e')); + feature_sets.insert("glyph_composition", HB_TAG('c', 'c', 'm', 'p')); + feature_sets.insert("conjunct_form_after_ro", HB_TAG('c', 'f', 'a', 'r')); + feature_sets.insert("conjunct_forms", HB_TAG('c', 'j', 'c', 't')); + feature_sets.insert("contextual_ligatures", HB_TAG('c', 'l', 'i', 'g')); + feature_sets.insert("centered_cjk_punctuation", HB_TAG('c', 'p', 'c', 't')); + feature_sets.insert("capital_spacing", HB_TAG('c', 'p', 's', 'p')); + feature_sets.insert("contextual_swash", HB_TAG('c', 's', 'w', 'h')); + feature_sets.insert("cursive_positioning", HB_TAG('c', 'u', 'r', 's')); + feature_sets.insert("character_variant_01", HB_TAG('c', 'v', '0', '1')); + feature_sets.insert("character_variant_02", HB_TAG('c', 'v', '0', '2')); + feature_sets.insert("character_variant_03", HB_TAG('c', 'v', '0', '3')); + feature_sets.insert("character_variant_04", HB_TAG('c', 'v', '0', '4')); + feature_sets.insert("character_variant_05", HB_TAG('c', 'v', '0', '5')); + feature_sets.insert("character_variant_06", HB_TAG('c', 'v', '0', '6')); + feature_sets.insert("character_variant_07", HB_TAG('c', 'v', '0', '7')); + feature_sets.insert("character_variant_08", HB_TAG('c', 'v', '0', '8')); + feature_sets.insert("character_variant_09", HB_TAG('c', 'v', '0', '9')); + feature_sets.insert("character_variant_10", HB_TAG('c', 'v', '1', '0')); + feature_sets.insert("character_variant_11", HB_TAG('c', 'v', '1', '1')); + feature_sets.insert("character_variant_12", HB_TAG('c', 'v', '1', '2')); + feature_sets.insert("character_variant_13", HB_TAG('c', 'v', '1', '3')); + feature_sets.insert("character_variant_14", HB_TAG('c', 'v', '1', '4')); + feature_sets.insert("character_variant_15", HB_TAG('c', 'v', '1', '5')); + feature_sets.insert("character_variant_16", HB_TAG('c', 'v', '1', '6')); + feature_sets.insert("character_variant_17", HB_TAG('c', 'v', '1', '7')); + feature_sets.insert("character_variant_18", HB_TAG('c', 'v', '1', '8')); + feature_sets.insert("character_variant_19", HB_TAG('c', 'v', '1', '9')); + feature_sets.insert("character_variant_20", HB_TAG('c', 'v', '2', '0')); + feature_sets.insert("character_variant_21", HB_TAG('c', 'v', '2', '1')); + feature_sets.insert("character_variant_22", HB_TAG('c', 'v', '2', '2')); + feature_sets.insert("character_variant_23", HB_TAG('c', 'v', '2', '3')); + feature_sets.insert("character_variant_24", HB_TAG('c', 'v', '2', '4')); + feature_sets.insert("character_variant_25", HB_TAG('c', 'v', '2', '5')); + feature_sets.insert("character_variant_26", HB_TAG('c', 'v', '2', '6')); + feature_sets.insert("character_variant_27", HB_TAG('c', 'v', '2', '7')); + feature_sets.insert("character_variant_28", HB_TAG('c', 'v', '2', '8')); + feature_sets.insert("character_variant_29", HB_TAG('c', 'v', '2', '9')); + feature_sets.insert("character_variant_30", HB_TAG('c', 'v', '3', '0')); + feature_sets.insert("character_variant_31", HB_TAG('c', 'v', '3', '1')); + feature_sets.insert("character_variant_32", HB_TAG('c', 'v', '3', '2')); + feature_sets.insert("character_variant_33", HB_TAG('c', 'v', '3', '3')); + feature_sets.insert("character_variant_34", HB_TAG('c', 'v', '3', '4')); + feature_sets.insert("character_variant_35", HB_TAG('c', 'v', '3', '5')); + feature_sets.insert("character_variant_36", HB_TAG('c', 'v', '3', '6')); + feature_sets.insert("character_variant_37", HB_TAG('c', 'v', '3', '7')); + feature_sets.insert("character_variant_38", HB_TAG('c', 'v', '3', '8')); + feature_sets.insert("character_variant_39", HB_TAG('c', 'v', '3', '9')); + feature_sets.insert("character_variant_40", HB_TAG('c', 'v', '4', '0')); + feature_sets.insert("character_variant_41", HB_TAG('c', 'v', '4', '1')); + feature_sets.insert("character_variant_42", HB_TAG('c', 'v', '4', '2')); + feature_sets.insert("character_variant_43", HB_TAG('c', 'v', '4', '3')); + feature_sets.insert("character_variant_44", HB_TAG('c', 'v', '4', '4')); + feature_sets.insert("character_variant_45", HB_TAG('c', 'v', '4', '5')); + feature_sets.insert("character_variant_46", HB_TAG('c', 'v', '4', '6')); + feature_sets.insert("character_variant_47", HB_TAG('c', 'v', '4', '7')); + feature_sets.insert("character_variant_48", HB_TAG('c', 'v', '4', '8')); + feature_sets.insert("character_variant_49", HB_TAG('c', 'v', '4', '9')); + feature_sets.insert("character_variant_50", HB_TAG('c', 'v', '5', '0')); + feature_sets.insert("character_variant_51", HB_TAG('c', 'v', '5', '1')); + feature_sets.insert("character_variant_52", HB_TAG('c', 'v', '5', '2')); + feature_sets.insert("character_variant_53", HB_TAG('c', 'v', '5', '3')); + feature_sets.insert("character_variant_54", HB_TAG('c', 'v', '5', '4')); + feature_sets.insert("character_variant_55", HB_TAG('c', 'v', '5', '5')); + feature_sets.insert("character_variant_56", HB_TAG('c', 'v', '5', '6')); + feature_sets.insert("character_variant_57", HB_TAG('c', 'v', '5', '7')); + feature_sets.insert("character_variant_58", HB_TAG('c', 'v', '5', '8')); + feature_sets.insert("character_variant_59", HB_TAG('c', 'v', '5', '9')); + feature_sets.insert("character_variant_60", HB_TAG('c', 'v', '6', '0')); + feature_sets.insert("character_variant_61", HB_TAG('c', 'v', '6', '1')); + feature_sets.insert("character_variant_62", HB_TAG('c', 'v', '6', '2')); + feature_sets.insert("character_variant_63", HB_TAG('c', 'v', '6', '3')); + feature_sets.insert("character_variant_64", HB_TAG('c', 'v', '6', '4')); + feature_sets.insert("character_variant_65", HB_TAG('c', 'v', '6', '5')); + feature_sets.insert("character_variant_66", HB_TAG('c', 'v', '6', '6')); + feature_sets.insert("character_variant_67", HB_TAG('c', 'v', '6', '7')); + feature_sets.insert("character_variant_68", HB_TAG('c', 'v', '6', '8')); + feature_sets.insert("character_variant_69", HB_TAG('c', 'v', '6', '9')); + feature_sets.insert("character_variant_70", HB_TAG('c', 'v', '7', '0')); + feature_sets.insert("character_variant_71", HB_TAG('c', 'v', '7', '1')); + feature_sets.insert("character_variant_72", HB_TAG('c', 'v', '7', '2')); + feature_sets.insert("character_variant_73", HB_TAG('c', 'v', '7', '3')); + feature_sets.insert("character_variant_74", HB_TAG('c', 'v', '7', '4')); + feature_sets.insert("character_variant_75", HB_TAG('c', 'v', '7', '5')); + feature_sets.insert("character_variant_76", HB_TAG('c', 'v', '7', '6')); + feature_sets.insert("character_variant_77", HB_TAG('c', 'v', '7', '7')); + feature_sets.insert("character_variant_78", HB_TAG('c', 'v', '7', '8')); + feature_sets.insert("character_variant_79", HB_TAG('c', 'v', '7', '9')); + feature_sets.insert("character_variant_80", HB_TAG('c', 'v', '8', '0')); + feature_sets.insert("character_variant_81", HB_TAG('c', 'v', '8', '1')); + feature_sets.insert("character_variant_82", HB_TAG('c', 'v', '8', '2')); + feature_sets.insert("character_variant_83", HB_TAG('c', 'v', '8', '3')); + feature_sets.insert("character_variant_84", HB_TAG('c', 'v', '8', '4')); + feature_sets.insert("character_variant_85", HB_TAG('c', 'v', '8', '5')); + feature_sets.insert("character_variant_86", HB_TAG('c', 'v', '8', '6')); + feature_sets.insert("character_variant_87", HB_TAG('c', 'v', '8', '7')); + feature_sets.insert("character_variant_88", HB_TAG('c', 'v', '8', '8')); + feature_sets.insert("character_variant_89", HB_TAG('c', 'v', '8', '9')); + feature_sets.insert("character_variant_90", HB_TAG('c', 'v', '9', '0')); + feature_sets.insert("character_variant_91", HB_TAG('c', 'v', '9', '1')); + feature_sets.insert("character_variant_92", HB_TAG('c', 'v', '9', '2')); + feature_sets.insert("character_variant_93", HB_TAG('c', 'v', '9', '3')); + feature_sets.insert("character_variant_94", HB_TAG('c', 'v', '9', '4')); + feature_sets.insert("character_variant_95", HB_TAG('c', 'v', '9', '5')); + feature_sets.insert("character_variant_96", HB_TAG('c', 'v', '9', '6')); + feature_sets.insert("character_variant_97", HB_TAG('c', 'v', '9', '7')); + feature_sets.insert("character_variant_98", HB_TAG('c', 'v', '9', '8')); + feature_sets.insert("character_variant_99", HB_TAG('c', 'v', '9', '9')); + feature_sets.insert("petite_capitals_from_capitals", HB_TAG('c', '2', 'p', 'c')); + feature_sets.insert("small_capitals_from_capitals", HB_TAG('c', '2', 's', 'c')); + feature_sets.insert("distances", HB_TAG('d', 'i', 's', 't')); + feature_sets.insert("discretionary_ligatures", HB_TAG('d', 'l', 'i', 'g')); + feature_sets.insert("denominators", HB_TAG('d', 'n', 'o', 'm')); + feature_sets.insert("dotless_forms", HB_TAG('d', 't', 'l', 's')); + feature_sets.insert("expert_forms", HB_TAG('e', 'x', 'p', 't')); + feature_sets.insert("final_glyph_on_line_alternates", HB_TAG('f', 'a', 'l', 't')); + feature_sets.insert("terminal_forms_2", HB_TAG('f', 'i', 'n', '2')); + feature_sets.insert("terminal_forms_3", HB_TAG('f', 'i', 'n', '3')); + feature_sets.insert("terminal_forms", HB_TAG('f', 'i', 'n', 'a')); + feature_sets.insert("flattened_accent_forms", HB_TAG('f', 'l', 'a', 'c')); + feature_sets.insert("fractions", HB_TAG('f', 'r', 'a', 'c')); + feature_sets.insert("full_widths", HB_TAG('f', 'w', 'i', 'd')); + feature_sets.insert("half_forms", HB_TAG('h', 'a', 'l', 'f')); + feature_sets.insert("halant_forms", HB_TAG('h', 'a', 'l', 'n')); + feature_sets.insert("alternate_half_widths", HB_TAG('h', 'a', 'l', 't')); + feature_sets.insert("historical_forms", HB_TAG('h', 'i', 's', 't')); + feature_sets.insert("horizontal_kana_alternates", HB_TAG('h', 'k', 'n', 'a')); + feature_sets.insert("historical_ligatures", HB_TAG('h', 'l', 'i', 'g')); + feature_sets.insert("hangul", HB_TAG('h', 'n', 'g', 'l')); + feature_sets.insert("hojo_kanji_forms", HB_TAG('h', 'o', 'j', 'o')); + feature_sets.insert("half_widths", HB_TAG('h', 'w', 'i', 'd')); + feature_sets.insert("initial_forms", HB_TAG('i', 'n', 'i', 't')); + feature_sets.insert("isolated_forms", HB_TAG('i', 's', 'o', 'l')); + feature_sets.insert("italics", HB_TAG('i', 't', 'a', 'l')); + feature_sets.insert("justification_alternates", HB_TAG('j', 'a', 'l', 't')); + feature_sets.insert("jis78_forms", HB_TAG('j', 'p', '7', '8')); + feature_sets.insert("jis83_forms", HB_TAG('j', 'p', '8', '3')); + feature_sets.insert("jis90_forms", HB_TAG('j', 'p', '9', '0')); + feature_sets.insert("jis2004_forms", HB_TAG('j', 'p', '0', '4')); + feature_sets.insert("kerning", HB_TAG('k', 'e', 'r', 'n')); + feature_sets.insert("left_bounds", HB_TAG('l', 'f', 'b', 'd')); + feature_sets.insert("standard_ligatures", HB_TAG('l', 'i', 'g', 'a')); + feature_sets.insert("leading_jamo_forms", HB_TAG('l', 'j', 'm', 'o')); + feature_sets.insert("lining_figures", HB_TAG('l', 'n', 'u', 'm')); + feature_sets.insert("localized_forms", HB_TAG('l', 'o', 'c', 'l')); + feature_sets.insert("left_to_right_alternates", HB_TAG('l', 't', 'r', 'a')); + feature_sets.insert("left_to_right_mirrored_forms", HB_TAG('l', 't', 'r', 'm')); + feature_sets.insert("mark_positioning", HB_TAG('m', 'a', 'r', 'k')); + feature_sets.insert("medial_forms_2", HB_TAG('m', 'e', 'd', '2')); + feature_sets.insert("medial_forms", HB_TAG('m', 'e', 'd', 'i')); + feature_sets.insert("mathematical_greek", HB_TAG('m', 'g', 'r', 'k')); + feature_sets.insert("mark_to_mark_positioning", HB_TAG('m', 'k', 'm', 'k')); + feature_sets.insert("mark_positioning_via_substitution", HB_TAG('m', 's', 'e', 't')); + feature_sets.insert("alternate_annotation_forms", HB_TAG('n', 'a', 'l', 't')); + feature_sets.insert("nlc_kanji_forms", HB_TAG('n', 'l', 'c', 'k')); + feature_sets.insert("nukta_forms", HB_TAG('n', 'u', 'k', 't')); + feature_sets.insert("numerators", HB_TAG('n', 'u', 'm', 'r')); + feature_sets.insert("oldstyle_figures", HB_TAG('o', 'n', 'u', 'm')); + feature_sets.insert("optical_bounds", HB_TAG('o', 'p', 'b', 'd')); + feature_sets.insert("ordinals", HB_TAG('o', 'r', 'd', 'n')); + feature_sets.insert("ornaments", HB_TAG('o', 'r', 'n', 'm')); + feature_sets.insert("proportional_alternate_widths", HB_TAG('p', 'a', 'l', 't')); + feature_sets.insert("petite_capitals", HB_TAG('p', 'c', 'a', 'p')); + feature_sets.insert("proportional_kana", HB_TAG('p', 'k', 'n', 'a')); + feature_sets.insert("proportional_figures", HB_TAG('p', 'n', 'u', 'm')); + feature_sets.insert("pre_base_forms", HB_TAG('p', 'r', 'e', 'f')); + feature_sets.insert("pre_base_substitutions", HB_TAG('p', 'r', 'e', 's')); + feature_sets.insert("post_base_forms", HB_TAG('p', 's', 't', 'f')); + feature_sets.insert("post_base_substitutions", HB_TAG('p', 's', 't', 's')); + feature_sets.insert("proportional_widths", HB_TAG('p', 'w', 'i', 'd')); + feature_sets.insert("quarter_widths", HB_TAG('q', 'w', 'i', 'd')); + feature_sets.insert("randomize", HB_TAG('r', 'a', 'n', 'd')); + feature_sets.insert("required_contextual_alternates", HB_TAG('r', 'c', 'l', 't')); + feature_sets.insert("rakar_forms", HB_TAG('r', 'k', 'r', 'f')); + feature_sets.insert("required_ligatures", HB_TAG('r', 'l', 'i', 'g')); + feature_sets.insert("reph_forms", HB_TAG('r', 'p', 'h', 'f')); + feature_sets.insert("right_bounds", HB_TAG('r', 't', 'b', 'd')); + feature_sets.insert("right_to_left_alternates", HB_TAG('r', 't', 'l', 'a')); + feature_sets.insert("right_to_left_mirrored_forms", HB_TAG('r', 't', 'l', 'm')); + feature_sets.insert("ruby_notation_forms", HB_TAG('r', 'u', 'b', 'y')); + feature_sets.insert("required_variation_alternates", HB_TAG('r', 'v', 'r', 'n')); + feature_sets.insert("stylistic_alternates", HB_TAG('s', 'a', 'l', 't')); + feature_sets.insert("scientific_inferiors", HB_TAG('s', 'i', 'n', 'f')); + feature_sets.insert("optical_size", HB_TAG('s', 'i', 'z', 'e')); + feature_sets.insert("small_capitals", HB_TAG('s', 'm', 'c', 'p')); + feature_sets.insert("simplified_forms", HB_TAG('s', 'm', 'p', 'l')); + feature_sets.insert("stylistic_set_01", HB_TAG('s', 's', '0', '1')); + feature_sets.insert("stylistic_set_02", HB_TAG('s', 's', '0', '2')); + feature_sets.insert("stylistic_set_03", HB_TAG('s', 's', '0', '3')); + feature_sets.insert("stylistic_set_04", HB_TAG('s', 's', '0', '4')); + feature_sets.insert("stylistic_set_05", HB_TAG('s', 's', '0', '5')); + feature_sets.insert("stylistic_set_06", HB_TAG('s', 's', '0', '6')); + feature_sets.insert("stylistic_set_07", HB_TAG('s', 's', '0', '7')); + feature_sets.insert("stylistic_set_08", HB_TAG('s', 's', '0', '8')); + feature_sets.insert("stylistic_set_09", HB_TAG('s', 's', '0', '9')); + feature_sets.insert("stylistic_set_10", HB_TAG('s', 's', '1', '0')); + feature_sets.insert("stylistic_set_11", HB_TAG('s', 's', '1', '1')); + feature_sets.insert("stylistic_set_12", HB_TAG('s', 's', '1', '2')); + feature_sets.insert("stylistic_set_13", HB_TAG('s', 's', '1', '3')); + feature_sets.insert("stylistic_set_14", HB_TAG('s', 's', '1', '4')); + feature_sets.insert("stylistic_set_15", HB_TAG('s', 's', '1', '5')); + feature_sets.insert("stylistic_set_16", HB_TAG('s', 's', '1', '6')); + feature_sets.insert("stylistic_set_17", HB_TAG('s', 's', '1', '7')); + feature_sets.insert("stylistic_set_18", HB_TAG('s', 's', '1', '8')); + feature_sets.insert("stylistic_set_19", HB_TAG('s', 's', '1', '9')); + feature_sets.insert("stylistic_set_20", HB_TAG('s', 's', '2', '0')); + feature_sets.insert("math_script_style_alternates", HB_TAG('s', 's', 't', 'y')); + feature_sets.insert("stretching_glyph_decomposition", HB_TAG('s', 't', 'c', 'h')); + feature_sets.insert("subscript", HB_TAG('s', 'u', 'b', 's')); + feature_sets.insert("superscript", HB_TAG('s', 'u', 'p', 's')); + feature_sets.insert("swash", HB_TAG('s', 'w', 's', 'h')); + feature_sets.insert("titling", HB_TAG('t', 'i', 't', 'l')); + feature_sets.insert("trailing_jamo_forms", HB_TAG('t', 'j', 'm', 'o')); + feature_sets.insert("traditional_name_forms", HB_TAG('t', 'n', 'a', 'm')); + feature_sets.insert("tabular_figures", HB_TAG('t', 'n', 'u', 'm')); + feature_sets.insert("traditional_forms", HB_TAG('t', 'r', 'a', 'd')); + feature_sets.insert("third_widths", HB_TAG('t', 'w', 'i', 'd')); + feature_sets.insert("unicase", HB_TAG('u', 'n', 'i', 'c')); + feature_sets.insert("alternate_vertical_metrics", HB_TAG('v', 'a', 'l', 't')); + feature_sets.insert("vattu_variants", HB_TAG('v', 'a', 't', 'u')); + feature_sets.insert("vertical_writing", HB_TAG('v', 'e', 'r', 't')); + feature_sets.insert("alternate_vertical_half_metrics", HB_TAG('v', 'h', 'a', 'l')); + feature_sets.insert("vowel_jamo_forms", HB_TAG('v', 'j', 'm', 'o')); + feature_sets.insert("vertical_kana_alternates", HB_TAG('v', 'k', 'n', 'a')); + feature_sets.insert("vertical_kerning", HB_TAG('v', 'k', 'r', 'n')); + feature_sets.insert("proportional_alternate_vertical_metrics", HB_TAG('v', 'p', 'a', 'l')); + feature_sets.insert("vertical_alternates_and_rotation", HB_TAG('v', 'r', 't', '2')); + feature_sets.insert("vertical_alternates_for_rotation", HB_TAG('v', 'r', 't', 'r')); + feature_sets.insert("slashed_zero", HB_TAG('z', 'e', 'r', 'o')); + // Registered OpenType variation tag. + feature_sets.insert("italic", HB_TAG('i', 't', 'a', 'l')); + feature_sets.insert("optical_size", HB_TAG('o', 'p', 's', 'z')); + feature_sets.insert("slant", HB_TAG('s', 'l', 'n', 't')); + feature_sets.insert("width", HB_TAG('w', 'd', 't', 'h')); + feature_sets.insert("weight", HB_TAG('w', 'g', 'h', 't')); +} int32_t TextServerAdvanced::name_to_tag(const String &p_name) const { - for (int i = 0; feature_set[i].tag != 0; i++) { - if (feature_set[i].name == p_name) { - return feature_set[i].tag; - } + if (feature_sets.has(p_name)) { + return feature_sets[p_name]; } // No readable name, use tag string. @@ -707,9 +701,9 @@ int32_t TextServerAdvanced::name_to_tag(const String &p_name) const { } String TextServerAdvanced::tag_to_name(int32_t p_tag) const { - for (int i = 0; feature_set[i].tag != 0; i++) { - if (feature_set[i].tag == p_tag) { - return feature_set[i].name; + for (const KeyValue<StringName, int32_t> &E : feature_sets) { + if (E.value == p_tag) { + return E.key; } } @@ -4633,30 +4627,76 @@ real_t TextServerAdvanced::shaped_text_get_underline_thickness(RID p_shaped) con } struct num_system_data { - String lang; + Set<String> lang; String digits; String percent_sign; String exp; }; static num_system_data num_systems[]{ - { "ar,ar_AR,ar_BH,ar_DJ,ar_EG,ar_ER,ar_IL,ar_IQ,ar_JO,ar_KM,ar_KW,ar_LB,ar_MR,ar_OM,ar_PS,ar_QA,ar_SA,ar_SD,ar_SO,ar_SS,ar_SY,ar_TD,ar_YE", U"٠١٢٣٤٥٦٧٨٩٫", U"٪", U"اس" }, - { "fa,ks,pa_Arab,ps,ug,ur_IN,ur,uz_Arab", U"۰۱۲۳۴۵۶۷۸۹٫", U"٪", U"اس" }, - { "as,bn,mni", U"০১২৩৪৫৬৭৮৯.", U"%", U"e" }, - { "mr,ne", U"०१२३४५६७८९.", U"%", U"e" }, - { "dz", U"༠༡༢༣༤༥༦༧༨༩.", U"%", U"e" }, - { "sat", U"᱐᱑᱒᱓᱔᱕᱖᱗᱘᱙.", U"%", U"e" }, - { "my", U"၀၁၂၃၄၅၆၇၈၉.", U"%", U"e" }, - { String(), String(), String(), String() }, + { Set<String>(), U"٠١٢٣٤٥٦٧٨٩٫", U"٪", U"اس" }, + { Set<String>(), U"۰۱۲۳۴۵۶۷۸۹٫", U"٪", U"اس" }, + { Set<String>(), U"০১২৩৪৫৬৭৮৯.", U"%", U"e" }, + { Set<String>(), U"०१२३४५६७८९.", U"%", U"e" }, + { Set<String>(), U"༠༡༢༣༤༥༦༧༨༩.", U"%", U"e" }, + { Set<String>(), U"᱐᱑᱒᱓᱔᱕᱖᱗᱘᱙.", U"%", U"e" }, + { Set<String>(), U"၀၁၂၃၄၅၆၇၈၉.", U"%", U"e" }, + { Set<String>(), String(), String(), String() }, }; +static void _insert_num_systems_lang() { + num_systems[0].lang.insert(StringName("ar")); + num_systems[0].lang.insert(StringName("ar_AR")); + num_systems[0].lang.insert(StringName("ar_BH")); + num_systems[0].lang.insert(StringName("ar_DJ")); + num_systems[0].lang.insert(StringName("ar_EG")); + num_systems[0].lang.insert(StringName("ar_ER")); + num_systems[0].lang.insert(StringName("ar_IL")); + num_systems[0].lang.insert(StringName("ar_IQ")); + num_systems[0].lang.insert(StringName("ar_JO")); + num_systems[0].lang.insert(StringName("ar_KM")); + num_systems[0].lang.insert(StringName("ar_KW")); + num_systems[0].lang.insert(StringName("ar_LB")); + num_systems[0].lang.insert(StringName("ar_MR")); + num_systems[0].lang.insert(StringName("ar_OM")); + num_systems[0].lang.insert(StringName("ar_PS")); + num_systems[0].lang.insert(StringName("ar_QA")); + num_systems[0].lang.insert(StringName("ar_SA")); + num_systems[0].lang.insert(StringName("ar_SD")); + num_systems[0].lang.insert(StringName("ar_SO")); + num_systems[0].lang.insert(StringName("ar_SS")); + num_systems[0].lang.insert(StringName("ar_SY")); + num_systems[0].lang.insert(StringName("ar_TD")); + num_systems[0].lang.insert(StringName("ar_YE")); + + num_systems[1].lang.insert(StringName("fa")); + num_systems[1].lang.insert(StringName("ks")); + num_systems[1].lang.insert(StringName("pa_Arab")); + num_systems[1].lang.insert(StringName("ug")); + num_systems[1].lang.insert(StringName("ur_IN")); + num_systems[1].lang.insert(StringName("ur")); + num_systems[1].lang.insert(StringName("uz_Arab")); + + num_systems[2].lang.insert(StringName("as")); + num_systems[2].lang.insert(StringName("bn")); + num_systems[2].lang.insert(StringName("mni")); + + num_systems[3].lang.insert(StringName("mr")); + num_systems[3].lang.insert(StringName("ne")); + + num_systems[4].lang.insert(StringName("dz")); + + num_systems[5].lang.insert(StringName("sat")); + + num_systems[6].lang.insert(StringName("my")); +} + String TextServerAdvanced::format_number(const String &p_string, const String &p_language) const { - String lang = (p_language == "") ? TranslationServer::get_singleton()->get_tool_locale() : p_language; + const StringName lang = (p_language == "") ? TranslationServer::get_singleton()->get_tool_locale() : p_language; String res = p_string; - for (int i = 0; num_systems[i].lang != String(); i++) { - Vector<String> langs = num_systems[i].lang.split(","); - if (langs.has(lang)) { + for (int i = 0; num_systems[i].lang.size() != 0; i++) { + if (num_systems[i].lang.has(lang)) { if (num_systems[i].digits == String()) { return p_string; } @@ -4677,12 +4717,11 @@ String TextServerAdvanced::format_number(const String &p_string, const String &p } String TextServerAdvanced::parse_number(const String &p_string, const String &p_language) const { - String lang = (p_language == "") ? TranslationServer::get_singleton()->get_tool_locale() : p_language; + const StringName lang = (p_language == "") ? TranslationServer::get_singleton()->get_tool_locale() : p_language; String res = p_string; - for (int i = 0; num_systems[i].lang != String(); i++) { - Vector<String> langs = num_systems[i].lang.split(","); - if (langs.has(lang)) { + for (int i = 0; num_systems[i].lang.size() != 0; i++) { + if (num_systems[i].lang.has(lang)) { if (num_systems[i].digits == String()) { return p_string; } @@ -4706,11 +4745,10 @@ String TextServerAdvanced::parse_number(const String &p_string, const String &p_ } String TextServerAdvanced::percent_sign(const String &p_language) const { - String lang = (p_language == "") ? TranslationServer::get_singleton()->get_tool_locale() : p_language; + const StringName lang = (p_language == "") ? TranslationServer::get_singleton()->get_tool_locale() : p_language; - for (int i = 0; num_systems[i].lang != String(); i++) { - Vector<String> langs = num_systems[i].lang.split(","); - if (langs.has(lang)) { + for (int i = 0; num_systems[i].lang.size() != 0; i++) { + if (num_systems[i].lang.has(lang)) { if (num_systems[i].percent_sign == String()) { return "%"; } @@ -4730,6 +4768,8 @@ void TextServerAdvanced::register_server() { } TextServerAdvanced::TextServerAdvanced() { + _insert_num_systems_lang(); + _insert_feature_sets(); hb_bmp_create_font_funcs(); } diff --git a/modules/visual_script/visual_script.cpp b/modules/visual_script/visual_script.cpp index 142419f90a..4d5f3420b8 100644 --- a/modules/visual_script/visual_script.cpp +++ b/modules/visual_script/visual_script.cpp @@ -955,7 +955,7 @@ bool VisualScript::are_subnodes_edited() const { } #endif -const Vector<MultiplayerAPI::RPCConfig> VisualScript::get_rpc_methods() const { +const Vector<Multiplayer::RPCConfig> VisualScript::get_rpc_methods() const { return rpc_functions; } @@ -1022,11 +1022,11 @@ void VisualScript::_set_data(const Dictionary &p_data) { if (functions[E].func_id >= 0 && nodes.has(functions[E].func_id)) { Ref<VisualScriptFunction> vsf = nodes[functions[E].func_id].node; if (vsf.is_valid()) { - if (vsf->get_rpc_mode() != MultiplayerAPI::RPC_MODE_DISABLED) { - MultiplayerAPI::RPCConfig nd; + if (vsf->get_rpc_mode() != Multiplayer::RPC_MODE_DISABLED) { + Multiplayer::RPCConfig nd; nd.name = E; nd.rpc_mode = vsf->get_rpc_mode(); - nd.transfer_mode = MultiplayerPeer::TRANSFER_MODE_RELIABLE; // TODO + nd.transfer_mode = Multiplayer::TRANSFER_MODE_RELIABLE; // TODO if (rpc_functions.find(nd) == -1) { rpc_functions.push_back(nd); } @@ -1036,7 +1036,7 @@ void VisualScript::_set_data(const Dictionary &p_data) { } // Sort so we are 100% that they are always the same. - rpc_functions.sort_custom<MultiplayerAPI::SortRPCConfig>(); + rpc_functions.sort_custom<Multiplayer::SortRPCConfig>(); } Dictionary VisualScript::_get_data() const { @@ -1833,7 +1833,7 @@ Ref<Script> VisualScriptInstance::get_script() const { return script; } -const Vector<MultiplayerAPI::RPCConfig> VisualScriptInstance::get_rpc_methods() const { +const Vector<Multiplayer::RPCConfig> VisualScriptInstance::get_rpc_methods() const { return script->get_rpc_methods(); } diff --git a/modules/visual_script/visual_script.h b/modules/visual_script/visual_script.h index 932ebeb27f..39cef8f68b 100644 --- a/modules/visual_script/visual_script.h +++ b/modules/visual_script/visual_script.h @@ -234,7 +234,7 @@ private: HashMap<StringName, Function> functions; HashMap<StringName, Variable> variables; Map<StringName, Vector<Argument>> custom_signals; - Vector<MultiplayerAPI::RPCConfig> rpc_functions; + Vector<Multiplayer::RPCConfig> rpc_functions; Map<Object *, VisualScriptInstance *> instances; @@ -362,7 +362,7 @@ public: virtual int get_member_line(const StringName &p_member) const override; - virtual const Vector<MultiplayerAPI::RPCConfig> get_rpc_methods() const override; + virtual const Vector<Multiplayer::RPCConfig> get_rpc_methods() const override; #ifdef TOOLS_ENABLED virtual bool are_subnodes_edited() const; @@ -443,7 +443,7 @@ public: virtual ScriptLanguage *get_language(); - virtual const Vector<MultiplayerAPI::RPCConfig> get_rpc_methods() const; + virtual const Vector<Multiplayer::RPCConfig> get_rpc_methods() const; VisualScriptInstance(); ~VisualScriptInstance(); diff --git a/modules/visual_script/visual_script_nodes.cpp b/modules/visual_script/visual_script_nodes.cpp index 44fc91d8f5..ef77c0cef3 100644 --- a/modules/visual_script/visual_script_nodes.cpp +++ b/modules/visual_script/visual_script_nodes.cpp @@ -90,7 +90,7 @@ bool VisualScriptFunction::_set(const StringName &p_name, const Variant &p_value } if (p_name == "rpc/mode") { - rpc_mode = MultiplayerAPI::RPCMode(int(p_value)); + rpc_mode = Multiplayer::RPCMode(int(p_value)); return true; } @@ -261,11 +261,11 @@ int VisualScriptFunction::get_argument_count() const { return arguments.size(); } -void VisualScriptFunction::set_rpc_mode(MultiplayerAPI::RPCMode p_mode) { +void VisualScriptFunction::set_rpc_mode(Multiplayer::RPCMode p_mode) { rpc_mode = p_mode; } -MultiplayerAPI::RPCMode VisualScriptFunction::get_rpc_mode() const { +Multiplayer::RPCMode VisualScriptFunction::get_rpc_mode() const { return rpc_mode; } @@ -311,14 +311,14 @@ void VisualScriptFunction::reset_state() { stack_size = 256; stack_less = false; sequenced = true; - rpc_mode = MultiplayerAPI::RPC_MODE_DISABLED; + rpc_mode = Multiplayer::RPC_MODE_DISABLED; } VisualScriptFunction::VisualScriptFunction() { stack_size = 256; stack_less = false; sequenced = true; - rpc_mode = MultiplayerAPI::RPC_MODE_DISABLED; + rpc_mode = Multiplayer::RPC_MODE_DISABLED; } void VisualScriptFunction::set_stack_less(bool p_enable) { diff --git a/modules/visual_script/visual_script_nodes.h b/modules/visual_script/visual_script_nodes.h index 35d9b0b4fe..bf2d8e9683 100644 --- a/modules/visual_script/visual_script_nodes.h +++ b/modules/visual_script/visual_script_nodes.h @@ -49,7 +49,7 @@ class VisualScriptFunction : public VisualScriptNode { bool stack_less; int stack_size; - MultiplayerAPI::RPCMode rpc_mode; + Multiplayer::RPCMode rpc_mode; bool sequenced; protected: @@ -96,8 +96,8 @@ public: void set_return_type(Variant::Type p_type); Variant::Type get_return_type() const; - void set_rpc_mode(MultiplayerAPI::RPCMode p_mode); - MultiplayerAPI::RPCMode get_rpc_mode() const; + void set_rpc_mode(Multiplayer::RPCMode p_mode); + Multiplayer::RPCMode get_rpc_mode() const; virtual VisualScriptNodeInstance *instantiate(VisualScriptInstance *p_instance) override; diff --git a/modules/vorbis/SCsub b/modules/vorbis/SCsub index bc31fff066..322314487f 100644 --- a/modules/vorbis/SCsub +++ b/modules/vorbis/SCsub @@ -3,9 +3,6 @@ Import("env") Import("env_modules") -# Only kept to build the thirdparty library used by the theora and webm -# modules. We now use stb_vorbis for AudioStreamOGGVorbis. - env_vorbis = env_modules.Clone() # Thirdparty source files diff --git a/modules/vorbis/audio_stream_ogg_vorbis.cpp b/modules/vorbis/audio_stream_ogg_vorbis.cpp new file mode 100644 index 0000000000..e4a80c339f --- /dev/null +++ b/modules/vorbis/audio_stream_ogg_vorbis.cpp @@ -0,0 +1,435 @@ +/*************************************************************************/ +/* audio_stream_ogg_vorbis.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "audio_stream_ogg_vorbis.h" + +#include "core/io/file_access.h" +#include "core/variant/typed_array.h" +#include "thirdparty/libogg/ogg/ogg.h" + +int AudioStreamPlaybackOGGVorbis::_mix_internal(AudioFrame *p_buffer, int p_frames) { + ERR_FAIL_COND_V(!ready, 0); + ERR_FAIL_COND_V(!active, 0); + + int todo = p_frames; + + int start_buffer = 0; + + int frames_mixed_this_step = p_frames; + + while (todo && active) { + AudioFrame *buffer = p_buffer; + if (start_buffer > 0) { + buffer = buffer + start_buffer; + } + int mixed = _mix_frames_vorbis(buffer, todo); + if (mixed < 0) { + return 0; + } + todo -= mixed; + frames_mixed += mixed; + start_buffer += mixed; + if (!have_packets_left) { + //end of file! + bool is_not_empty = mixed > 0 || vorbis_stream->get_length() > 0; + if (vorbis_stream->loop && is_not_empty) { + //loop + + seek(vorbis_stream->loop_offset); + loops++; + // we still have buffer to fill, start from this element in the next iteration. + start_buffer = p_frames - todo; + } else { + frames_mixed_this_step = p_frames - todo; + for (int i = p_frames - todo; i < p_frames; i++) { + p_buffer[i] = AudioFrame(0, 0); + } + active = false; + todo = 0; + } + } + } + return frames_mixed_this_step; +} + +int AudioStreamPlaybackOGGVorbis::_mix_frames_vorbis(AudioFrame *p_buffer, int p_frames) { + ERR_FAIL_COND_V(!ready, 0); + if (!have_samples_left) { + ogg_packet *packet = nullptr; + int err; + + if (!vorbis_data_playback->next_ogg_packet(&packet)) { + have_packets_left = false; + WARN_PRINT("ran out of packets in stream"); + return -1; + } + + ERR_FAIL_COND_V_MSG((err = vorbis_synthesis(&block, packet)), 0, "Error during vorbis synthesis " + itos(err)); + ERR_FAIL_COND_V_MSG((err = vorbis_synthesis_blockin(&dsp_state, &block)), 0, "Error during vorbis block processing " + itos(err)); + + have_packets_left = !packet->e_o_s; + } + + float **pcm; // Accessed with pcm[channel_idx][sample_idx]. + + int frames = vorbis_synthesis_pcmout(&dsp_state, &pcm); + if (frames > p_frames) { + frames = p_frames; + have_samples_left = true; + } else { + have_samples_left = false; + } + + if (info.channels > 1) { + for (int frame = 0; frame < frames; frame++) { + p_buffer[frame].l = pcm[0][frame]; + p_buffer[frame].r = pcm[0][frame]; + } + } else { + for (int frame = 0; frame < frames; frame++) { + p_buffer[frame].l = pcm[0][frame]; + p_buffer[frame].r = pcm[0][frame]; + } + } + vorbis_synthesis_read(&dsp_state, frames); + return frames; +} + +float AudioStreamPlaybackOGGVorbis::get_stream_sampling_rate() { + return vorbis_data->get_sampling_rate(); +} + +bool AudioStreamPlaybackOGGVorbis::_alloc_vorbis() { + vorbis_info_init(&info); + info_is_allocated = true; + vorbis_comment_init(&comment); + comment_is_allocated = true; + + ERR_FAIL_COND_V(vorbis_data.is_null(), false); + vorbis_data_playback = vorbis_data->instance_playback(); + + ogg_packet *packet; + int err; + + for (int i = 0; i < 3; i++) { + if (!vorbis_data_playback->next_ogg_packet(&packet)) { + WARN_PRINT("Not enough packets to parse header"); + return false; + } + + err = vorbis_synthesis_headerin(&info, &comment, packet); + ERR_FAIL_COND_V_MSG(err != 0, false, "Error parsing header"); + } + + err = vorbis_synthesis_init(&dsp_state, &info); + ERR_FAIL_COND_V_MSG(err != 0, false, "Error initializing dsp state"); + dsp_state_is_allocated = true; + + err = vorbis_block_init(&dsp_state, &block); + ERR_FAIL_COND_V_MSG(err != 0, false, "Error initializing block"); + block_is_allocated = true; + + ready = true; + + return true; +} + +void AudioStreamPlaybackOGGVorbis::start(float p_from_pos) { + ERR_FAIL_COND(!ready); + active = true; + seek(p_from_pos); + loops = 0; + _begin_resample(); +} + +void AudioStreamPlaybackOGGVorbis::stop() { + active = false; +} + +bool AudioStreamPlaybackOGGVorbis::is_playing() const { + return active; +} + +int AudioStreamPlaybackOGGVorbis::get_loop_count() const { + return loops; +} + +float AudioStreamPlaybackOGGVorbis::get_playback_position() const { + return float(frames_mixed) / vorbis_data->get_sampling_rate(); +} + +void AudioStreamPlaybackOGGVorbis::seek(float p_time) { + ERR_FAIL_COND(!ready); + ERR_FAIL_COND(vorbis_stream.is_null()); + if (!active) { + return; + } + + vorbis_synthesis_restart(&dsp_state); + + if (p_time >= vorbis_stream->get_length()) { + p_time = 0; + } + frames_mixed = uint32_t(vorbis_data->get_sampling_rate() * p_time); + + const int64_t desired_sample = p_time * get_stream_sampling_rate(); + + if (!vorbis_data_playback->seek_page(desired_sample)) { + WARN_PRINT("seek failed"); + return; + } + + ogg_packet *packet; + if (!vorbis_data_playback->next_ogg_packet(&packet)) { + WARN_PRINT_ONCE("seeking beyond limits"); + return; + } + + // The granule position of the page we're seeking through. + int64_t granule_pos = 0; + + int headers_remaining = 0; + int samples_in_page = 0; + int err; + while (true) { + if (vorbis_synthesis_idheader(packet)) { + headers_remaining = 3; + } + if (!headers_remaining) { + ERR_FAIL_COND_MSG((err = vorbis_synthesis(&block, packet)), "Error during vorbis synthesis " + itos(err)); + ERR_FAIL_COND_MSG((err = vorbis_synthesis_blockin(&dsp_state, &block)), "Error during vorbis block processing " + itos(err)); + + int samples_out = vorbis_synthesis_pcmout(&dsp_state, nullptr); + ERR_FAIL_COND_MSG((err = vorbis_synthesis_read(&dsp_state, samples_out)), "Error during vorbis read updating " + itos(err)); + + samples_in_page += samples_out; + + } else { + headers_remaining--; + } + if (packet->granulepos != -1 && headers_remaining == 0) { + // This indicates the end of the page. + granule_pos = packet->granulepos; + break; + } + if (packet->e_o_s) { + break; + } + if (!vorbis_data_playback->next_ogg_packet(&packet)) { + // We should get an e_o_s flag before this happens. + WARN_PRINT("Vorbis file ended without warning."); + break; + } + } + + int64_t samples_to_burn = samples_in_page - (granule_pos - desired_sample); + + if (samples_to_burn > samples_in_page) { + WARN_PRINT("Burning more samples than we have in this page. Check seek algorithm."); + } else if (samples_to_burn < 0) { + WARN_PRINT("Burning negative samples doesn't make sense. Check seek algorithm."); + } + + // Seek again, this time we'll burn a specific number of samples instead of all of them. + if (!vorbis_data_playback->seek_page(desired_sample)) { + WARN_PRINT("seek failed"); + return; + } + + if (!vorbis_data_playback->next_ogg_packet(&packet)) { + WARN_PRINT_ONCE("seeking beyond limits"); + return; + } + vorbis_synthesis_restart(&dsp_state); + + while (true) { + if (vorbis_synthesis_idheader(packet)) { + headers_remaining = 3; + } + if (!headers_remaining) { + ERR_FAIL_COND_MSG((err = vorbis_synthesis(&block, packet)), "Error during vorbis synthesis " + itos(err)); + ERR_FAIL_COND_MSG((err = vorbis_synthesis_blockin(&dsp_state, &block)), "Error during vorbis block processing " + itos(err)); + + int samples_out = vorbis_synthesis_pcmout(&dsp_state, nullptr); + int read_samples = samples_to_burn > samples_out ? samples_out : samples_to_burn; + ERR_FAIL_COND_MSG((err = vorbis_synthesis_read(&dsp_state, samples_out)), "Error during vorbis read updating " + itos(err)); + samples_to_burn -= read_samples; + + if (samples_to_burn <= 0) { + break; + } + } else { + headers_remaining--; + } + if (packet->granulepos != -1 && headers_remaining == 0) { + // This indicates the end of the page. + break; + } + if (packet->e_o_s) { + break; + } + if (!vorbis_data_playback->next_ogg_packet(&packet)) { + // We should get an e_o_s flag before this happens. + WARN_PRINT("Vorbis file ended without warning."); + break; + } + } +} + +AudioStreamPlaybackOGGVorbis::~AudioStreamPlaybackOGGVorbis() { + if (block_is_allocated) { + vorbis_block_clear(&block); + } + if (dsp_state_is_allocated) { + vorbis_dsp_clear(&dsp_state); + } + if (comment_is_allocated) { + vorbis_comment_clear(&comment); + } + if (info_is_allocated) { + vorbis_info_clear(&info); + } +} + +Ref<AudioStreamPlayback> AudioStreamOGGVorbis::instance_playback() { + Ref<AudioStreamPlaybackOGGVorbis> ovs; + + ERR_FAIL_COND_V(packet_sequence.is_null(), nullptr); + + ovs.instantiate(); + ovs->vorbis_stream = Ref<AudioStreamOGGVorbis>(this); + ovs->vorbis_data = packet_sequence; + ovs->frames_mixed = 0; + ovs->active = false; + ovs->loops = 0; + if (ovs->_alloc_vorbis()) { + return ovs; + } + // Failed to allocate data structures. + return nullptr; +} + +String AudioStreamOGGVorbis::get_stream_name() const { + return ""; //return stream_name; +} + +void AudioStreamOGGVorbis::maybe_update_info() { + ERR_FAIL_COND(packet_sequence.is_null()); + + vorbis_info info; + vorbis_comment comment; + int err; + + vorbis_info_init(&info); + vorbis_comment_init(&comment); + + int packet_count = 0; + Ref<OGGPacketSequencePlayback> packet_sequence_playback = packet_sequence->instance_playback(); + + for (int i = 0; i < 3; i++) { + ogg_packet *packet; + if (!packet_sequence_playback->next_ogg_packet(&packet)) { + WARN_PRINT("Failed to get header packet"); + break; + } + if (i == 0) { + packet->b_o_s = 1; + } + + if (i == 0) { + ERR_FAIL_COND(!vorbis_synthesis_idheader(packet)); + } + + err = vorbis_synthesis_headerin(&info, &comment, packet); + ERR_FAIL_COND_MSG(err != 0, "Error parsing header packet " + itos(i) + ": " + itos(err)); + + packet_count++; + } + + packet_sequence->set_sampling_rate(info.rate); + + vorbis_comment_clear(&comment); + vorbis_info_clear(&info); +} + +void AudioStreamOGGVorbis::set_packet_sequence(Ref<OGGPacketSequence> p_packet_sequence) { + packet_sequence = p_packet_sequence; + if (packet_sequence.is_valid()) { + maybe_update_info(); + } +} + +Ref<OGGPacketSequence> AudioStreamOGGVorbis::get_packet_sequence() const { + return packet_sequence; +} + +void AudioStreamOGGVorbis::set_loop(bool p_enable) { + loop = p_enable; +} + +bool AudioStreamOGGVorbis::has_loop() const { + return loop; +} + +void AudioStreamOGGVorbis::set_loop_offset(float p_seconds) { + loop_offset = p_seconds; +} + +float AudioStreamOGGVorbis::get_loop_offset() const { + return loop_offset; +} + +float AudioStreamOGGVorbis::get_length() const { + ERR_FAIL_COND_V(packet_sequence.is_null(), 0); + return packet_sequence->get_length(); +} + +bool AudioStreamOGGVorbis::is_monophonic() const { + return false; +} + +void AudioStreamOGGVorbis::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_packet_sequence", "packet_sequence"), &AudioStreamOGGVorbis::set_packet_sequence); + ClassDB::bind_method(D_METHOD("get_packet_sequence"), &AudioStreamOGGVorbis::get_packet_sequence); + + ClassDB::bind_method(D_METHOD("set_loop", "enable"), &AudioStreamOGGVorbis::set_loop); + ClassDB::bind_method(D_METHOD("has_loop"), &AudioStreamOGGVorbis::has_loop); + + ClassDB::bind_method(D_METHOD("set_loop_offset", "seconds"), &AudioStreamOGGVorbis::set_loop_offset); + ClassDB::bind_method(D_METHOD("get_loop_offset"), &AudioStreamOGGVorbis::get_loop_offset); + + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "packet_sequence", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_packet_sequence", "get_packet_sequence"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "loop"), "set_loop", "has_loop"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "loop_offset"), "set_loop_offset", "get_loop_offset"); +} + +AudioStreamOGGVorbis::AudioStreamOGGVorbis() {} + +AudioStreamOGGVorbis::~AudioStreamOGGVorbis() {} diff --git a/modules/stb_vorbis/audio_stream_ogg_vorbis.h b/modules/vorbis/audio_stream_ogg_vorbis.h index 756c241d1f..59a1318a6b 100644 --- a/modules/stb_vorbis/audio_stream_ogg_vorbis.h +++ b/modules/vorbis/audio_stream_ogg_vorbis.h @@ -28,29 +28,49 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef AUDIO_STREAM_STB_VORBIS_H -#define AUDIO_STREAM_STB_VORBIS_H +#ifndef AUDIO_STREAM_LIBVORBIS_H +#define AUDIO_STREAM_LIBVORBIS_H -#include "core/io/resource_loader.h" +#include "core/variant/variant.h" +#include "modules/ogg/ogg_packet_sequence.h" #include "servers/audio/audio_stream.h" - -#include "thirdparty/misc/stb_vorbis.h" +#include "thirdparty/libvorbis/vorbis/codec.h" class AudioStreamOGGVorbis; class AudioStreamPlaybackOGGVorbis : public AudioStreamPlaybackResampled { GDCLASS(AudioStreamPlaybackOGGVorbis, AudioStreamPlaybackResampled); - stb_vorbis *ogg_stream = nullptr; - stb_vorbis_alloc ogg_alloc; uint32_t frames_mixed = 0; bool active = false; int loops = 0; + vorbis_info info; + vorbis_comment comment; + vorbis_dsp_state dsp_state; + vorbis_block block; + + bool info_is_allocated = false; + bool comment_is_allocated = false; + bool dsp_state_is_allocated = false; + bool block_is_allocated = false; + + bool ready = false; + + bool have_samples_left = false; + bool have_packets_left = false; + friend class AudioStreamOGGVorbis; + Ref<OGGPacketSequence> vorbis_data; + Ref<OGGPacketSequencePlayback> vorbis_data_playback; Ref<AudioStreamOGGVorbis> vorbis_stream; + int _mix_frames_vorbis(AudioFrame *p_buffer, int p_frames); + + // Allocates vorbis data structures. Returns true upon success, false on failure. + bool _alloc_vorbis(); + protected: virtual int _mix_internal(AudioFrame *p_buffer, int p_frames) override; virtual float get_stream_sampling_rate() override; @@ -72,20 +92,20 @@ public: class AudioStreamOGGVorbis : public AudioStream { GDCLASS(AudioStreamOGGVorbis, AudioStream); OBJ_SAVE_TYPE(AudioStream); // Saves derived classes with common type so they can be interchanged. - RES_BASE_EXTENSION("oggstr"); + RES_BASE_EXTENSION("oggvorbisstr"); friend class AudioStreamPlaybackOGGVorbis; - void *data = nullptr; - uint32_t data_len = 0; - - int decode_mem_size = 0; - float sample_rate = 1.0; int channels = 1; float length = 0.0; bool loop = false; float loop_offset = 0.0; - void clear_data(); + + // Performs a seek to the beginning of the stream, should not be called during playback! + // Also causes allocation and deallocation. + void maybe_update_info(); + + Ref<OGGPacketSequence> packet_sequence; protected: static void _bind_methods(); @@ -100,13 +120,15 @@ public: virtual Ref<AudioStreamPlayback> instance_playback() override; virtual String get_stream_name() const override; - void set_data(const Vector<uint8_t> &p_data); - Vector<uint8_t> get_data() const; + void set_packet_sequence(Ref<OGGPacketSequence> p_packet_sequence); + Ref<OGGPacketSequence> get_packet_sequence() const; virtual float get_length() const override; //if supported, otherwise return 0 + virtual bool is_monophonic() const override; + AudioStreamOGGVorbis(); virtual ~AudioStreamOGGVorbis(); }; -#endif +#endif // AUDIO_STREAM_LIBVORBIS_H diff --git a/modules/vorbis/config.py b/modules/vorbis/config.py index 8a384e3066..978eccb29f 100644 --- a/modules/vorbis/config.py +++ b/modules/vorbis/config.py @@ -4,3 +4,14 @@ def can_build(env, platform): def configure(env): pass + + +def get_doc_classes(): + return [ + "AudioStreamOGGVorbis", + "AudioStreamPlaybackOGGVorbis", + ] + + +def get_doc_path(): + return "doc_classes" diff --git a/modules/stb_vorbis/doc_classes/AudioStreamOGGVorbis.xml b/modules/vorbis/doc_classes/AudioStreamOGGVorbis.xml index 94fdff5d43..a680a2f999 100644 --- a/modules/stb_vorbis/doc_classes/AudioStreamOGGVorbis.xml +++ b/modules/vorbis/doc_classes/AudioStreamOGGVorbis.xml @@ -1,25 +1,23 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="AudioStreamOGGVorbis" inherits="AudioStream" version="4.0"> <brief_description> - OGG Vorbis audio stream driver. </brief_description> <description> - OGG Vorbis audio stream driver. </description> <tutorials> </tutorials> <methods> </methods> <members> - <member name="data" type="PackedByteArray" setter="set_data" getter="get_data" default="PackedByteArray()"> - Contains the audio data in bytes. - </member> <member name="loop" type="bool" setter="set_loop" getter="has_loop" default="false"> If [code]true[/code], the stream will automatically loop when it reaches the end. </member> <member name="loop_offset" type="float" setter="set_loop_offset" getter="get_loop_offset" default="0.0"> Time in seconds at which the stream starts after being looped. </member> + <member name="packet_sequence" type="OGGPacketSequence" setter="set_packet_sequence" getter="get_packet_sequence"> + Contains the raw OGG data for this stream. + </member> </members> <constants> </constants> diff --git a/modules/vorbis/doc_classes/AudioStreamPlaybackOGGVorbis.xml b/modules/vorbis/doc_classes/AudioStreamPlaybackOGGVorbis.xml new file mode 100644 index 0000000000..3120f2a9e6 --- /dev/null +++ b/modules/vorbis/doc_classes/AudioStreamPlaybackOGGVorbis.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="AudioStreamPlaybackOGGVorbis" inherits="AudioStreamPlaybackResampled" version="4.0"> + <brief_description> + </brief_description> + <description> + </description> + <tutorials> + </tutorials> + <methods> + </methods> + <constants> + </constants> +</class> diff --git a/modules/vorbis/register_types.cpp b/modules/vorbis/register_types.cpp index d3e77ea629..de3f41afdd 100644 --- a/modules/vorbis/register_types.cpp +++ b/modules/vorbis/register_types.cpp @@ -30,8 +30,19 @@ #include "register_types.h" -// Dummy module as libvorbis is needed by other modules (theora ...) +#include "audio_stream_ogg_vorbis.h" +#include "resource_importer_ogg_vorbis.h" -void register_vorbis_types() {} +void register_vorbis_types() { +#ifdef TOOLS_ENABLED + if (Engine::get_singleton()->is_editor_hint()) { + Ref<ResourceImporterOGGVorbis> ogg_vorbis_importer; + ogg_vorbis_importer.instantiate(); + ResourceFormatImporter::get_singleton()->add_importer(ogg_vorbis_importer); + } +#endif + GDREGISTER_CLASS(AudioStreamOGGVorbis); + GDREGISTER_CLASS(AudioStreamPlaybackOGGVorbis); +} void unregister_vorbis_types() {} diff --git a/modules/stb_vorbis/resource_importer_ogg_vorbis.cpp b/modules/vorbis/resource_importer_ogg_vorbis.cpp index 85de698efd..33ee6cf359 100644 --- a/modules/stb_vorbis/resource_importer_ogg_vorbis.cpp +++ b/modules/vorbis/resource_importer_ogg_vorbis.cpp @@ -30,16 +30,19 @@ #include "resource_importer_ogg_vorbis.h" +#include "audio_stream_ogg_vorbis.h" #include "core/io/file_access.h" #include "core/io/resource_saver.h" #include "scene/resources/texture.h" +#include "thirdparty/libogg/ogg/ogg.h" +#include "thirdparty/libvorbis/vorbis/codec.h" String ResourceImporterOGGVorbis::get_importer_name() const { - return "ogg_vorbis"; + return "oggvorbisstr"; } String ResourceImporterOGGVorbis::get_visible_name() const { - return "OGGVorbis"; + return "oggvorbisstr"; } void ResourceImporterOGGVorbis::get_recognized_extensions(List<String> *p_extensions) const { @@ -47,7 +50,7 @@ void ResourceImporterOGGVorbis::get_recognized_extensions(List<String> *p_extens } String ResourceImporterOGGVorbis::get_save_extension() const { - return "oggstr"; + return "oggvorbisstr"; } String ResourceImporterOGGVorbis::get_resource_type() const { @@ -81,23 +84,106 @@ Error ResourceImporterOGGVorbis::import(const String &p_source_file, const Strin uint64_t len = f->get_length(); - Vector<uint8_t> data; - data.resize(len); - uint8_t *w = data.ptrw(); + Vector<uint8_t> file_data; + file_data.resize(len); + uint8_t *w = file_data.ptrw(); f->get_buffer(w, len); memdelete(f); - Ref<AudioStreamOGGVorbis> ogg_stream; - ogg_stream.instantiate(); - - ogg_stream->set_data(data); - ERR_FAIL_COND_V(!ogg_stream->get_data().size(), ERR_FILE_CORRUPT); - ogg_stream->set_loop(loop); - ogg_stream->set_loop_offset(loop_offset); - - return ResourceSaver::save(p_save_path + ".oggstr", ogg_stream); + Ref<AudioStreamOGGVorbis> ogg_vorbis_stream; + ogg_vorbis_stream.instantiate(); + + Ref<OGGPacketSequence> ogg_packet_sequence; + ogg_packet_sequence.instantiate(); + + ogg_stream_state stream_state; + ogg_sync_state sync_state; + ogg_page page; + ogg_packet packet; + bool initialized_stream = false; + + ogg_sync_init(&sync_state); + int err; + size_t cursor = 0; + size_t packet_count = 0; + bool done = false; + while (!done) { + ERR_FAIL_COND_V_MSG((err = ogg_sync_check(&sync_state)), Error::ERR_INVALID_DATA, "Ogg sync error " + itos(err)); + while (ogg_sync_pageout(&sync_state, &page) != 1) { + if (cursor >= len) { + done = true; + break; + } + ERR_FAIL_COND_V_MSG((err = ogg_sync_check(&sync_state)), Error::ERR_INVALID_DATA, "Ogg sync error " + itos(err)); + char *sync_buf = ogg_sync_buffer(&sync_state, OGG_SYNC_BUFFER_SIZE); + ERR_FAIL_COND_V_MSG((err = ogg_sync_check(&sync_state)), Error::ERR_INVALID_DATA, "Ogg sync error " + itos(err)); + ERR_FAIL_COND_V(cursor > len, Error::ERR_INVALID_DATA); + size_t copy_size = len - cursor; + if (copy_size > OGG_SYNC_BUFFER_SIZE) { + copy_size = OGG_SYNC_BUFFER_SIZE; + } + memcpy(sync_buf, &file_data[cursor], copy_size); + ogg_sync_wrote(&sync_state, copy_size); + cursor += copy_size; + ERR_FAIL_COND_V_MSG((err = ogg_sync_check(&sync_state)), Error::ERR_INVALID_DATA, "Ogg sync error " + itos(err)); + } + if (done) { + break; + } + ERR_FAIL_COND_V_MSG((err = ogg_sync_check(&sync_state)), Error::ERR_INVALID_DATA, "Ogg sync error " + itos(err)); + + // Have a page now. + if (!initialized_stream) { + ogg_stream_init(&stream_state, ogg_page_serialno(&page)); + ERR_FAIL_COND_V_MSG((err = ogg_stream_check(&stream_state)), Error::ERR_INVALID_DATA, "Ogg stream error " + itos(err)); + initialized_stream = true; + } + ERR_FAIL_COND_V_MSG((err = ogg_stream_check(&stream_state)), Error::ERR_INVALID_DATA, "Ogg stream error " + itos(err)); + ogg_stream_pagein(&stream_state, &page); + ERR_FAIL_COND_V_MSG((err = ogg_stream_check(&stream_state)), Error::ERR_INVALID_DATA, "Ogg stream error " + itos(err)); + int desync_iters = 0; + + Vector<Vector<uint8_t>> packet_data; + int64_t granule_pos = 0; + + while (true) { + err = ogg_stream_packetout(&stream_state, &packet); + if (err == -1) { + // According to the docs this is usually recoverable, but don't sit here spinning forever. + desync_iters++; + ERR_FAIL_COND_V_MSG(desync_iters > 100, Error::ERR_INVALID_DATA, "Packet sync issue during ogg import"); + continue; + } else if (err == 0) { + // Not enough data to fully reconstruct a packet. Go on to the next page. + break; + } + if (packet_count == 0 && vorbis_synthesis_idheader(&packet) == 0) { + WARN_PRINT("Found a non-vorbis-header packet in a header position"); + // Clearly this logical stream is not a vorbis stream, so destroy it and try again with the next page. + ogg_stream_destroy(&stream_state); + initialized_stream = false; + break; + } + granule_pos = packet.granulepos; + + PackedByteArray data; + data.resize(packet.bytes); + memcpy(data.ptrw(), packet.packet, packet.bytes); + packet_data.push_back(data); + packet_count++; + } + if (initialized_stream) { + ogg_packet_sequence->push_page(granule_pos, packet_data); + } + } + + ogg_vorbis_stream->set_packet_sequence(ogg_packet_sequence); + ogg_vorbis_stream->set_loop(loop); + ogg_vorbis_stream->set_loop_offset(loop_offset); + + return ResourceSaver::save(p_save_path + ".oggvorbisstr", ogg_vorbis_stream); } ResourceImporterOGGVorbis::ResourceImporterOGGVorbis() { diff --git a/modules/stb_vorbis/resource_importer_ogg_vorbis.h b/modules/vorbis/resource_importer_ogg_vorbis.h index 60fe3381fb..acdc1a3d38 100644 --- a/modules/stb_vorbis/resource_importer_ogg_vorbis.h +++ b/modules/vorbis/resource_importer_ogg_vorbis.h @@ -28,25 +28,29 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef RESOURCEIMPORTEROGGVORBIS_H -#define RESOURCEIMPORTEROGGVORBIS_H +#ifndef RESOURCE_IMPORTER_OGG_VORBIS_H +#define RESOURCE_IMPORTER_OGG_VORBIS_H -#include "audio_stream_ogg_vorbis.h" #include "core/io/resource_importer.h" class ResourceImporterOGGVorbis : public ResourceImporter { GDCLASS(ResourceImporterOGGVorbis, ResourceImporter); + enum { + OGG_SYNC_BUFFER_SIZE = 8192, + }; + +private: + // virtual int get_samples_in_packet(Vector<uint8_t> p_packet) = 0; + public: - virtual String get_importer_name() const override; - virtual String get_visible_name() const override; virtual void get_recognized_extensions(List<String> *p_extensions) const override; virtual String get_save_extension() const override; virtual String get_resource_type() const override; - + virtual String get_importer_name() const override; + virtual String get_visible_name() const override; virtual int get_preset_count() const override; virtual String get_preset_name(int p_idx) const override; - virtual void get_import_options(List<ImportOption> *r_options, int p_preset = 0) const override; virtual bool get_option_visibility(const String &p_option, const Map<StringName, Variant> &p_options) const override; @@ -55,4 +59,4 @@ public: ResourceImporterOGGVorbis(); }; -#endif // RESOURCEIMPORTEROGGVORBIS_H +#endif // RESOURCE_IMPORTER_OGG_VORBIS_H diff --git a/modules/webrtc/doc_classes/WebRTCMultiplayerPeer.xml b/modules/webrtc/doc_classes/WebRTCMultiplayerPeer.xml index c53af22ae1..43a8e20ef7 100644 --- a/modules/webrtc/doc_classes/WebRTCMultiplayerPeer.xml +++ b/modules/webrtc/doc_classes/WebRTCMultiplayerPeer.xml @@ -4,7 +4,7 @@ A simple interface to create a peer-to-peer mesh network composed of [WebRTCPeerConnection] that is compatible with the [MultiplayerAPI]. </brief_description> <description> - This class constructs a full mesh of [WebRTCPeerConnection] (one connection for each peer) that can be used as a [member MultiplayerAPI.network_peer]. + This class constructs a full mesh of [WebRTCPeerConnection] (one connection for each peer) that can be used as a [member MultiplayerAPI.multiplayer_peer]. You can add each [WebRTCPeerConnection] via [method add_peer] or remove them via [method remove_peer]. Peers must be added in [constant WebRTCPeerConnection.STATE_NEW] state to allow it to create the appropriate channels. This class will not create offers nor set descriptions, it will only poll them, and notify connections and disconnections. [signal MultiplayerPeer.connection_succeeded] and [signal MultiplayerPeer.server_disconnected] will not be emitted unless [code]server_compatibility[/code] is [code]true[/code] in [method initialize]. Beside that data transfer works like in a [MultiplayerPeer]. </description> @@ -56,7 +56,7 @@ Initialize the multiplayer peer with the given [code]peer_id[/code] (must be between 1 and 2147483647). If [code]server_compatibilty[/code] is [code]false[/code] (default), the multiplayer peer will be immediately in state [constant MultiplayerPeer.CONNECTION_CONNECTED] and [signal MultiplayerPeer.connection_succeeded] will not be emitted. If [code]server_compatibilty[/code] is [code]true[/code] the peer will suppress all [signal MultiplayerPeer.peer_connected] signals until a peer with id [constant MultiplayerPeer.TARGET_PEER_SERVER] connects and then emit [signal MultiplayerPeer.connection_succeeded]. After that the signal [signal MultiplayerPeer.peer_connected] will be emitted for every already connected peer, and any new peer that might connect. If the server peer disconnects after that, signal [signal MultiplayerPeer.server_disconnected] will be emitted and state will become [constant MultiplayerPeer.CONNECTION_CONNECTED]. - You can optionally specify a [code]channels_config[/code] array of [enum MultiplayerPeer.TransferMode] which will be used to create extra channels (WebRTC only supports one transfer mode per channel). + You can optionally specify a [code]channels_config[/code] array of [enum TransferMode] which will be used to create extra channels (WebRTC only supports one transfer mode per channel). </description> </method> <method name="remove_peer"> @@ -69,7 +69,7 @@ </methods> <members> <member name="refuse_new_connections" type="bool" setter="set_refuse_new_connections" getter="is_refusing_new_connections" override="true" default="false" /> - <member name="transfer_mode" type="int" setter="set_transfer_mode" getter="get_transfer_mode" override="true" enum="MultiplayerPeer.TransferMode" default="2" /> + <member name="transfer_mode" type="int" setter="set_transfer_mode" getter="get_transfer_mode" override="true" enum="TransferMode" default="2" /> </members> <constants> </constants> diff --git a/modules/webrtc/webrtc_multiplayer_peer.cpp b/modules/webrtc/webrtc_multiplayer_peer.cpp index 95c8c13449..d60d694df1 100644 --- a/modules/webrtc/webrtc_multiplayer_peer.cpp +++ b/modules/webrtc/webrtc_multiplayer_peer.cpp @@ -51,11 +51,11 @@ int WebRTCMultiplayerPeer::get_transfer_channel() const { return transfer_channel; } -void WebRTCMultiplayerPeer::set_transfer_mode(TransferMode p_mode) { +void WebRTCMultiplayerPeer::set_transfer_mode(Multiplayer::TransferMode p_mode) { transfer_mode = p_mode; } -MultiplayerPeer::TransferMode WebRTCMultiplayerPeer::get_transfer_mode() const { +Multiplayer::TransferMode WebRTCMultiplayerPeer::get_transfer_mode() const { return transfer_mode; } @@ -204,7 +204,7 @@ Error WebRTCMultiplayerPeer::initialize(int p_self_id, bool p_server_compat, Arr ERR_FAIL_COND_V(p_self_id < 1 || p_self_id > ~(1 << 31), ERR_INVALID_PARAMETER); channels_config.clear(); for (int i = 0; i < p_channels_config.size(); i++) { - ERR_FAIL_COND_V_MSG(p_channels_config[i].get_type() != Variant::INT, ERR_INVALID_PARAMETER, "The 'channels_config' array must contain only enum values from 'MultiplayerPeer.TransferMode'"); + ERR_FAIL_COND_V_MSG(p_channels_config[i].get_type() != Variant::INT, ERR_INVALID_PARAMETER, "The 'channels_config' array must contain only enum values from 'MultiplayerPeer.Multiplayer::TransferMode'"); int mode = p_channels_config[i].operator int(); // Initialize data channel configurations. Dictionary cfg; @@ -213,17 +213,17 @@ Error WebRTCMultiplayerPeer::initialize(int p_self_id, bool p_server_compat, Arr cfg["ordered"] = true; switch (mode) { - case TRANSFER_MODE_UNRELIABLE_ORDERED: + case Multiplayer::TRANSFER_MODE_ORDERED: cfg["maxPacketLifetime"] = 1; break; - case TRANSFER_MODE_UNRELIABLE: + case Multiplayer::TRANSFER_MODE_UNRELIABLE: cfg["maxPacketLifetime"] = 1; cfg["ordered"] = false; break; - case TRANSFER_MODE_RELIABLE: + case Multiplayer::TRANSFER_MODE_RELIABLE: break; default: - ERR_FAIL_V_MSG(ERR_INVALID_PARAMETER, vformat("The 'channels_config' array must contain only enum values from 'MultiplayerPeer.TransferMode'. Got: %d", mode)); + ERR_FAIL_V_MSG(ERR_INVALID_PARAMETER, vformat("The 'channels_config' array must contain only enum values from 'MultiplayerPeer.Multiplayer::TransferMode'. Got: %d", mode)); } channels_config.push_back(cfg); } @@ -355,13 +355,13 @@ Error WebRTCMultiplayerPeer::put_packet(const uint8_t *p_buffer, int p_buffer_si int ch = transfer_channel; if (ch == 0) { switch (transfer_mode) { - case TRANSFER_MODE_RELIABLE: + case Multiplayer::TRANSFER_MODE_RELIABLE: ch = CH_RELIABLE; break; - case TRANSFER_MODE_UNRELIABLE_ORDERED: + case Multiplayer::TRANSFER_MODE_ORDERED: ch = CH_ORDERED; break; - case TRANSFER_MODE_UNRELIABLE: + case Multiplayer::TRANSFER_MODE_UNRELIABLE: ch = CH_UNRELIABLE; break; } diff --git a/modules/webrtc/webrtc_multiplayer_peer.h b/modules/webrtc/webrtc_multiplayer_peer.h index ef4fe1678c..80a6491492 100644 --- a/modules/webrtc/webrtc_multiplayer_peer.h +++ b/modules/webrtc/webrtc_multiplayer_peer.h @@ -31,7 +31,7 @@ #ifndef WEBRTC_MULTIPLAYER_H #define WEBRTC_MULTIPLAYER_H -#include "core/io/multiplayer_peer.h" +#include "core/multiplayer/multiplayer_peer.h" #include "webrtc_peer_connection.h" class WebRTCMultiplayerPeer : public MultiplayerPeer { @@ -68,7 +68,7 @@ private: bool refuse_connections = false; ConnectionStatus connection_status = CONNECTION_DISCONNECTED; int transfer_channel = 0; - TransferMode transfer_mode = TRANSFER_MODE_RELIABLE; + Multiplayer::TransferMode transfer_mode = Multiplayer::TRANSFER_MODE_RELIABLE; int next_packet_peer = 0; bool server_compat = false; @@ -99,8 +99,8 @@ public: // MultiplayerPeer void set_transfer_channel(int p_channel) override; int get_transfer_channel() const override; - void set_transfer_mode(TransferMode p_mode) override; - TransferMode get_transfer_mode() const override; + void set_transfer_mode(Multiplayer::TransferMode p_mode) override; + Multiplayer::TransferMode get_transfer_mode() const override; void set_target_peer(int p_peer_id) override; int get_unique_id() const override; diff --git a/modules/websocket/doc_classes/WebSocketClient.xml b/modules/websocket/doc_classes/WebSocketClient.xml index 1549a907b4..b5202469f1 100644 --- a/modules/websocket/doc_classes/WebSocketClient.xml +++ b/modules/websocket/doc_classes/WebSocketClient.xml @@ -5,7 +5,7 @@ </brief_description> <description> This class implements a WebSocket client compatible with any RFC 6455-compliant WebSocket server. - This client can be optionally used as a network peer for the [MultiplayerAPI]. + This client can be optionally used as a multiplayer peer for the [MultiplayerAPI]. After starting the client ([method connect_to_url]), you will need to [method MultiplayerPeer.poll] it at regular intervals (e.g. inside [method Node._process]). You will receive appropriate signals when connecting, disconnecting, or when new data is available. </description> @@ -20,7 +20,7 @@ <argument index="3" name="custom_headers" type="PackedStringArray" default="PackedStringArray()" /> <description> Connects to the given URL requesting one of the given [code]protocols[/code] as sub-protocol. If the list empty (default), no sub-protocol will be requested. - If [code]true[/code] is passed as [code]gd_mp_api[/code], the client will behave like a network peer for the [MultiplayerAPI], connections to non-Godot servers will not work, and [signal data_received] will not be emitted. + If [code]true[/code] is passed as [code]gd_mp_api[/code], the client will behave like a multiplayer peer for the [MultiplayerAPI], connections to non-Godot servers will not work, and [signal data_received] will not be emitted. If [code]false[/code] is passed instead (default), you must call [PacketPeer] functions ([code]put_packet[/code], [code]get_packet[/code], etc.) on the [WebSocketPeer] returned via [code]get_peer(1)[/code] and not on this object directly (e.g. [code]get_peer(1).put_packet(data)[/code]). You can optionally pass a list of [code]custom_headers[/code] to be added to the handshake HTTP request. [b]Note:[/b] To avoid mixed content warnings or errors in HTML5, you may have to use a [code]url[/code] that starts with [code]wss://[/code] (secure) instead of [code]ws://[/code]. When doing so, make sure to use the fully qualified domain name that matches the one defined in the server's SSL certificate. Do not connect directly via the IP address for [code]wss://[/code] connections, as it won't match with the SSL certificate. diff --git a/modules/websocket/doc_classes/WebSocketMultiplayerPeer.xml b/modules/websocket/doc_classes/WebSocketMultiplayerPeer.xml index cd41e9a1fb..c7a0ca100f 100644 --- a/modules/websocket/doc_classes/WebSocketMultiplayerPeer.xml +++ b/modules/websocket/doc_classes/WebSocketMultiplayerPeer.xml @@ -4,7 +4,7 @@ Base class for WebSocket server and client. </brief_description> <description> - Base class for WebSocket server and client, allowing them to be used as network peer for the [MultiplayerAPI]. + Base class for WebSocket server and client, allowing them to be used as multiplayer peer for the [MultiplayerAPI]. </description> <tutorials> </tutorials> @@ -32,7 +32,7 @@ </methods> <members> <member name="refuse_new_connections" type="bool" setter="set_refuse_new_connections" getter="is_refusing_new_connections" override="true" default="false" /> - <member name="transfer_mode" type="int" setter="set_transfer_mode" getter="get_transfer_mode" override="true" enum="MultiplayerPeer.TransferMode" default="2" /> + <member name="transfer_mode" type="int" setter="set_transfer_mode" getter="get_transfer_mode" override="true" enum="TransferMode" default="2" /> </members> <signals> <signal name="peer_packet"> diff --git a/modules/websocket/doc_classes/WebSocketServer.xml b/modules/websocket/doc_classes/WebSocketServer.xml index 90182de4c2..b66a1054ab 100644 --- a/modules/websocket/doc_classes/WebSocketServer.xml +++ b/modules/websocket/doc_classes/WebSocketServer.xml @@ -55,7 +55,7 @@ <description> Starts listening on the given port. You can specify the desired subprotocols via the "protocols" array. If the list empty (default), no sub-protocol will be requested. - If [code]true[/code] is passed as [code]gd_mp_api[/code], the server will behave like a network peer for the [MultiplayerAPI], connections from non-Godot clients will not work, and [signal data_received] will not be emitted. + If [code]true[/code] is passed as [code]gd_mp_api[/code], the server will behave like a multiplayer peer for the [MultiplayerAPI], connections from non-Godot clients will not work, and [signal data_received] will not be emitted. If [code]false[/code] is passed instead (default), you must call [PacketPeer] functions ([code]put_packet[/code], [code]get_packet[/code], etc.), on the [WebSocketPeer] returned via [code]get_peer(id)[/code] to communicate with the peer with given [code]id[/code] (e.g. [code]get_peer(id).get_available_packet_count[/code]). </description> </method> diff --git a/modules/websocket/websocket_multiplayer_peer.cpp b/modules/websocket/websocket_multiplayer_peer.cpp index 163cc7706b..7464cf2bf5 100644 --- a/modules/websocket/websocket_multiplayer_peer.cpp +++ b/modules/websocket/websocket_multiplayer_peer.cpp @@ -113,13 +113,13 @@ int WebSocketMultiplayerPeer::get_transfer_channel() const { return 0; } -void WebSocketMultiplayerPeer::set_transfer_mode(TransferMode p_mode) { +void WebSocketMultiplayerPeer::set_transfer_mode(Multiplayer::TransferMode p_mode) { // Websocket uses TCP, reliable } -MultiplayerPeer::TransferMode WebSocketMultiplayerPeer::get_transfer_mode() const { +Multiplayer::TransferMode WebSocketMultiplayerPeer::get_transfer_mode() const { // Websocket uses TCP, reliable - return TRANSFER_MODE_RELIABLE; + return Multiplayer::TRANSFER_MODE_RELIABLE; } void WebSocketMultiplayerPeer::set_target_peer(int p_target_peer) { diff --git a/modules/websocket/websocket_multiplayer_peer.h b/modules/websocket/websocket_multiplayer_peer.h index 0fee196f41..d97a599fe9 100644 --- a/modules/websocket/websocket_multiplayer_peer.h +++ b/modules/websocket/websocket_multiplayer_peer.h @@ -32,7 +32,7 @@ #define WEBSOCKET_MULTIPLAYER_PEER_H #include "core/error/error_list.h" -#include "core/io/multiplayer_peer.h" +#include "core/multiplayer/multiplayer_peer.h" #include "core/templates/list.h" #include "websocket_peer.h" @@ -80,8 +80,8 @@ public: /* MultiplayerPeer */ void set_transfer_channel(int p_channel) override; int get_transfer_channel() const override; - void set_transfer_mode(TransferMode p_mode) override; - TransferMode get_transfer_mode() const override; + void set_transfer_mode(Multiplayer::TransferMode p_mode) override; + Multiplayer::TransferMode get_transfer_mode() const override; void set_target_peer(int p_target_peer) override; int get_packet_peer() const override; int get_unique_id() const override; diff --git a/platform/android/README.md b/platform/android/README.md new file mode 100644 index 0000000000..343e588553 --- /dev/null +++ b/platform/android/README.md @@ -0,0 +1,14 @@ +# Android platform port + +This folder contains the Java and C++ (JNI) code for the Android platform port, +using [Gradle](https://gradle.org/) as a build system. + +## Artwork license + +[`logo.png`](logo.png) and [`run_icon.png`](run_icon.png) are licensed under +[Creative Commons Attribution 3.0 Unported](https://developer.android.com/distribute/marketing-tools/brand-guidelines#android_robot) +per the Android logo usage guidelines: + +> The Android robot is reproduced or modified from work created and shared by +> Google and used according to terms described in the Creative Commons 3.0 +> Attribution License. diff --git a/platform/android/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp index 6b4a326ba7..e5422a28af 100644 --- a/platform/android/export/export_plugin.cpp +++ b/platform/android/export/export_plugin.cpp @@ -2942,7 +2942,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP void EditorExportPlatformAndroid::get_platform_features(List<String> *r_features) { r_features->push_back("mobile"); - r_features->push_back("Android"); + r_features->push_back("android"); } void EditorExportPlatformAndroid::resolve_platform_feature_priorities(const Ref<EditorExportPreset> &p_preset, Set<String> &p_features) { diff --git a/platform/android/java/app/build.gradle b/platform/android/java/app/build.gradle index 18e07c3762..9640887399 100644 --- a/platform/android/java/app/build.gradle +++ b/platform/android/java/app/build.gradle @@ -34,9 +34,8 @@ allprojects { } dependencies { - implementation libraries.supportCoreUtils implementation libraries.kotlinStdLib - implementation libraries.v4Support + implementation libraries.androidxFragment if (rootProject.findProject(":lib")) { implementation project(":lib") diff --git a/platform/android/java/app/config.gradle b/platform/android/java/app/config.gradle index fad64c675f..fcee54e493 100644 --- a/platform/android/java/app/config.gradle +++ b/platform/android/java/app/config.gradle @@ -4,9 +4,8 @@ ext.versions = [ minSdk : 19, targetSdk : 30, buildTools : '30.0.3', - supportCoreUtils : '1.0.0', kotlinVersion : '1.5.10', - v4Support : '1.0.0', + fragmentVersion : '1.3.6', javaVersion : 1.8, ndkVersion : '21.4.7075529' // Also update 'platform/android/detect.py#get_project_ndk_version()' when this is updated. @@ -14,10 +13,9 @@ ext.versions = [ ext.libraries = [ androidGradlePlugin: "com.android.tools.build:gradle:$versions.androidGradlePlugin", - supportCoreUtils : "androidx.legacy:legacy-support-core-utils:$versions.supportCoreUtils", kotlinGradlePlugin : "org.jetbrains.kotlin:kotlin-gradle-plugin:$versions.kotlinVersion", kotlinStdLib : "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$versions.kotlinVersion", - v4Support : "androidx.legacy:legacy-support-v4:$versions.v4Support" + androidxFragment : "androidx.fragment:fragment:$versions.fragmentVersion", ] ext.getExportPackageName = { -> diff --git a/platform/android/java/lib/build.gradle b/platform/android/java/lib/build.gradle index 663ba73d40..fbed4ed078 100644 --- a/platform/android/java/lib/build.gradle +++ b/platform/android/java/lib/build.gradle @@ -2,9 +2,8 @@ apply plugin: 'com.android.library' apply plugin: 'kotlin-android' dependencies { - implementation libraries.supportCoreUtils implementation libraries.kotlinStdLib - implementation libraries.v4Support + implementation libraries.androidxFragment } def pathToRootDir = "../../../../" diff --git a/platform/android/logo.png b/platform/android/logo.png Binary files differindex f44d360a25..9c8be93646 100644 --- a/platform/android/logo.png +++ b/platform/android/logo.png diff --git a/platform/iphone/export/export_plugin.h b/platform/iphone/export/export_plugin.h index 8d3af6e057..359f855d86 100644 --- a/platform/iphone/export/export_plugin.h +++ b/platform/iphone/export/export_plugin.h @@ -204,7 +204,7 @@ public: virtual void get_platform_features(List<String> *r_features) override { r_features->push_back("mobile"); - r_features->push_back("iOS"); + r_features->push_back("ios"); } virtual void resolve_platform_feature_priorities(const Ref<EditorExportPreset> &p_preset, Set<String> &p_features) override { diff --git a/platform/javascript/README.md b/platform/javascript/README.md new file mode 100644 index 0000000000..f181bea9e0 --- /dev/null +++ b/platform/javascript/README.md @@ -0,0 +1,15 @@ +# HTML5 platform port + +This folder contains the C++ and JavaScript code for the HTML5/WebAssembly platform port, +compiled using [Emscripten](https://emscripten.org/). + +It also contains a ESLint linting setup (see [`package.json`](package.json)). + +See also [`misc/dist/html`](/misc/dist/html) folder for files used by this platform +such as the HTML5 shell. + +## Artwork license + +[`logo.png`](logo.png) and [`run_icon.png`](run_icon.png) are licensed under +[Creative Commons Attribution 3.0 Unported](https://www.w3.org/html/logo/faq.html#how-licenced) +per the HTML5 logo usage guidelines. diff --git a/platform/javascript/export/export_plugin.h b/platform/javascript/export/export_plugin.h index 736edfe3a8..bdd1235259 100644 --- a/platform/javascript/export/export_plugin.h +++ b/platform/javascript/export/export_plugin.h @@ -134,7 +134,7 @@ public: virtual void get_platform_features(List<String> *r_features) override { r_features->push_back("web"); - r_features->push_back(get_os_name()); + r_features->push_back(get_os_name().to_lower()); } virtual void resolve_platform_feature_priorities(const Ref<EditorExportPreset> &p_preset, Set<String> &p_features) override { diff --git a/platform/javascript/os_javascript.cpp b/platform/javascript/os_javascript.cpp index 76102d941b..95c5909d50 100644 --- a/platform/javascript/os_javascript.cpp +++ b/platform/javascript/os_javascript.cpp @@ -137,12 +137,12 @@ int OS_JavaScript::get_processor_count() const { } bool OS_JavaScript::_check_internal_feature_support(const String &p_feature) { - if (p_feature == "HTML5" || p_feature == "web") { + if (p_feature == "html5" || p_feature == "web") { return true; } #ifdef JAVASCRIPT_EVAL_ENABLED - if (p_feature == "JavaScript") { + if (p_feature == "javascript") { return true; } #endif diff --git a/platform/linuxbsd/README.md b/platform/linuxbsd/README.md new file mode 100644 index 0000000000..0d3fb37be5 --- /dev/null +++ b/platform/linuxbsd/README.md @@ -0,0 +1,11 @@ +# Linux/*BSD platform port + +This folder contains the C++ code for the Linux/*BSD platform port. + +## Artwork license + +[`logo.png`](logo.png) is derived from the [Linux logo](https://isc.tamu.edu/~lewing/linux/): + +> Permission to use and/or modify this image is granted provided you acknowledge me + <lewing@isc.tamu.edu> and [The GIMP](https://isc.tamu.edu/~lewing/gimp/) + if someone asks. diff --git a/platform/linuxbsd/export/export.cpp b/platform/linuxbsd/export/export.cpp index 5c6be2d7d4..965a38ef4e 100644 --- a/platform/linuxbsd/export/export.cpp +++ b/platform/linuxbsd/export/export.cpp @@ -53,7 +53,7 @@ void register_linuxbsd_exporter() { platform->set_debug_32("linux_x11_32_debug"); platform->set_release_64("linux_x11_64_release"); platform->set_debug_64("linux_x11_64_debug"); - platform->set_os_name("X11"); + platform->set_os_name("LinuxBSD"); platform->set_chmod_flags(0755); platform->set_fixup_embedded_pck_func(&fixup_embedded_pck); diff --git a/platform/osx/display_server_osx.mm b/platform/osx/display_server_osx.mm index 43b7d7c1e0..f037d75fbd 100644 --- a/platform/osx/display_server_osx.mm +++ b/platform/osx/display_server_osx.mm @@ -190,6 +190,8 @@ static NSCursor *_cursorFromSelector(SEL selector, SEL fallback = nil) { [wd.window_object setContentMinSize:NSMakeSize(0, 0)]; [wd.window_object setContentMaxSize:NSMakeSize(FLT_MAX, FLT_MAX)]; + // Force window resize event. + [self windowDidResize:notification]; } - (void)windowDidExitFullScreen:(NSNotification *)notification { @@ -217,6 +219,8 @@ static NSCursor *_cursorFromSelector(SEL selector, SEL fallback = nil) { if (wd.on_top) { [wd.window_object setLevel:NSFloatingWindowLevel]; } + // Force window resize event. + [self windowDidResize:notification]; } - (void)windowDidChangeBackingProperties:(NSNotification *)notification { diff --git a/platform/osx/export/export_plugin.h b/platform/osx/export/export_plugin.h index cd85ce2aad..ca5086622e 100644 --- a/platform/osx/export/export_plugin.h +++ b/platform/osx/export/export_plugin.h @@ -115,7 +115,7 @@ public: virtual void get_platform_features(List<String> *r_features) override { r_features->push_back("pc"); r_features->push_back("s3tc"); - r_features->push_back("macOS"); + r_features->push_back("macos"); } virtual void resolve_platform_feature_priorities(const Ref<EditorExportPreset> &p_preset, Set<String> &p_features) override { diff --git a/platform/uwp/export/export_plugin.cpp b/platform/uwp/export/export_plugin.cpp index 5bd00b1549..a54b85a803 100644 --- a/platform/uwp/export/export_plugin.cpp +++ b/platform/uwp/export/export_plugin.cpp @@ -485,7 +485,7 @@ Error EditorExportPlatformUWP::export_project(const Ref<EditorExportPreset> &p_p void EditorExportPlatformUWP::get_platform_features(List<String> *r_features) { r_features->push_back("pc"); - r_features->push_back("UWP"); + r_features->push_back("uwp"); } void EditorExportPlatformUWP::resolve_platform_feature_priorities(const Ref<EditorExportPreset> &p_preset, Set<String> &p_features) { diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp index cf51caa6d5..1723026849 100644 --- a/platform/windows/display_server_windows.cpp +++ b/platform/windows/display_server_windows.cpp @@ -80,7 +80,7 @@ String DisplayServerWindows::get_name() const { } void DisplayServerWindows::_set_mouse_mode_impl(MouseMode p_mode) { - if (p_mode == MOUSE_MODE_CAPTURED || p_mode == MOUSE_MODE_CONFINED || p_mode == MOUSE_MODE_CONFINED_HIDDEN) { + if (windows.has(MAIN_WINDOW_ID) && (p_mode == MOUSE_MODE_CAPTURED || p_mode == MOUSE_MODE_CONFINED || p_mode == MOUSE_MODE_CONFINED_HIDDEN)) { // Mouse is grabbed (captured or confined). WindowData &wd = windows[MAIN_WINDOW_ID]; @@ -118,8 +118,10 @@ void DisplayServerWindows::_set_mouse_mode_impl(MouseMode p_mode) { void DisplayServerWindows::mouse_set_mode(MouseMode p_mode) { _THREAD_SAFE_METHOD_ - if (mouse_mode == p_mode) + if (mouse_mode == p_mode) { + // Already in the same mode; do nothing. return; + } mouse_mode = p_mode; @@ -134,7 +136,7 @@ void DisplayServerWindows::mouse_warp_to_position(const Point2i &p_to) { _THREAD_SAFE_METHOD_ if (!windows.has(last_focused_window)) { - return; //no window focused? + return; // No focused window? } if (mouse_mode == MOUSE_MODE_CAPTURED) { @@ -154,7 +156,6 @@ Point2i DisplayServerWindows::mouse_get_position() const { POINT p; GetCursorPos(&p); return Point2i(p.x, p.y); - //return Point2(old_x, old_y); } MouseButton DisplayServerWindows::mouse_get_button_state() const { @@ -165,12 +166,12 @@ void DisplayServerWindows::clipboard_set(const String &p_text) { _THREAD_SAFE_METHOD_ if (!windows.has(last_focused_window)) { - return; //no window focused? + return; // No focused window? } - // Convert LF line endings to CRLF in clipboard content - // Otherwise, line endings won't be visible when pasted in other software - String text = p_text.replace("\r\n", "\n").replace("\n", "\r\n"); // avoid \r\r\n + // Convert LF line endings to CRLF in clipboard content. + // Otherwise, line endings won't be visible when pasted in other software. + String text = p_text.replace("\r\n", "\n").replace("\n", "\r\n"); // Avoid \r\r\n. if (!OpenClipboard(windows[last_focused_window].hWnd)) { ERR_FAIL_MSG("Unable to open clipboard."); @@ -187,7 +188,7 @@ void DisplayServerWindows::clipboard_set(const String &p_text) { SetClipboardData(CF_UNICODETEXT, mem); - // set the CF_TEXT version (not needed?) + // Set the CF_TEXT version (not needed?). CharString utf8 = text.utf8(); mem = GlobalAlloc(GMEM_MOVEABLE, utf8.length() + 1); ERR_FAIL_COND_MSG(mem == nullptr, "Unable to allocate memory for clipboard contents."); @@ -206,7 +207,7 @@ String DisplayServerWindows::clipboard_get() const { _THREAD_SAFE_METHOD_ if (!windows.has(last_focused_window)) { - return String(); //no window focused? + return String(); // No focused window? } String ret; @@ -498,16 +499,18 @@ DisplayServer::WindowID DisplayServerWindows::create_sub_window(WindowMode p_mod } void DisplayServerWindows::show_window(WindowID p_id) { + ERR_FAIL_COND(!windows.has(p_id)); + WindowData &wd = windows[p_id]; if (p_id != MAIN_WINDOW_ID) { _update_window_style(p_id); } - ShowWindow(wd.hWnd, wd.no_focus ? SW_SHOWNOACTIVATE : SW_SHOW); // Show The Window + ShowWindow(wd.hWnd, wd.no_focus ? SW_SHOWNOACTIVATE : SW_SHOW); // Show the window. if (!wd.no_focus) { - SetForegroundWindow(wd.hWnd); // Slightly Higher Priority - SetFocus(wd.hWnd); // Sets Keyboard Focus To + SetForegroundWindow(wd.hWnd); // Slightly higher priority. + SetFocus(wd.hWnd); // Set keyboard focus. } } @@ -606,6 +609,8 @@ void DisplayServerWindows::window_set_mouse_passthrough(const Vector<Vector2> &p } void DisplayServerWindows::_update_window_mouse_passthrough(WindowID p_window) { + ERR_FAIL_COND(!windows.has(p_window)); + if (windows[p_window].mpath.size() == 0) { SetWindowRgn(windows[p_window].hWnd, nullptr, TRUE); } else { @@ -664,16 +669,11 @@ Point2i DisplayServerWindows::window_get_position(WindowID p_window) const { ClientToScreen(wd.hWnd, &point); return Point2i(point.x, point.y); - -#if 0 - //do not use this method, as it includes windows decorations - RECT r; - GetWindowRect(wd.hWnd, &r); - return Point2(r.left, r.top); -#endif } void DisplayServerWindows::_update_real_mouse_position(WindowID p_window) { + ERR_FAIL_COND(!windows.has(p_window)); + POINT mouse_pos; if (GetCursorPos(&mouse_pos) && ScreenToClient(windows[p_window].hWnd, &mouse_pos)) { if (mouse_pos.x > 0 && mouse_pos.y > 0 && mouse_pos.x <= windows[p_window].width && mouse_pos.y <= windows[p_window].height) { @@ -691,14 +691,9 @@ void DisplayServerWindows::window_set_position(const Point2i &p_position, Window ERR_FAIL_COND(!windows.has(p_window)); WindowData &wd = windows[p_window]; - if (wd.fullscreen) + if (wd.fullscreen) { return; -#if 0 - //wrong needs to account properly for decorations - RECT r; - GetWindowRect(wd.hWnd, &r); - MoveWindow(wd.hWnd, p_position.x, p_position.y, r.right - r.left, r.bottom - r.top, TRUE); -#else + } RECT rc; rc.left = p_position.x; @@ -711,8 +706,8 @@ void DisplayServerWindows::window_set_position(const Point2i &p_position, Window AdjustWindowRectEx(&rc, style, false, exStyle); MoveWindow(wd.hWnd, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, TRUE); -#endif - // Don't let the mouse leave the window when moved + + // Don't let the mouse leave the window when moved. if (mouse_mode == MOUSE_MODE_CONFINED || mouse_mode == MOUSE_MODE_CONFINED_HIDDEN) { RECT rect; GetClientRect(wd.hWnd, &rect); @@ -729,16 +724,15 @@ void DisplayServerWindows::window_set_transient(WindowID p_window, WindowID p_pa _THREAD_SAFE_METHOD_ ERR_FAIL_COND(p_window == p_parent); - ERR_FAIL_COND(!windows.has(p_window)); + WindowData &wd_window = windows[p_window]; ERR_FAIL_COND(wd_window.transient_parent == p_parent); - ERR_FAIL_COND_MSG(wd_window.always_on_top, "Windows with the 'on top' can't become transient."); if (p_parent == INVALID_WINDOW_ID) { - //remove transient + // Remove transient. ERR_FAIL_COND(wd_window.transient_parent == INVALID_WINDOW_ID); ERR_FAIL_COND(!windows.has(wd_window.transient_parent)); @@ -838,7 +832,7 @@ void DisplayServerWindows::window_set_size(const Size2i p_size, WindowID p_windo MoveWindow(wd.hWnd, rect.left, rect.top, w, h, TRUE); - // Don't let the mouse leave the window when resizing to a smaller resolution + // Don't let the mouse leave the window when resizing to a smaller resolution. if (mouse_mode == MOUSE_MODE_CONFINED || mouse_mode == MOUSE_MODE_CONFINED_HIDDEN) { RECT crect; GetClientRect(wd.hWnd, &crect); @@ -860,7 +854,7 @@ Size2i DisplayServerWindows::window_get_size(WindowID p_window) const { } RECT r; - if (GetClientRect(wd.hWnd, &r)) { // Retrieves area inside of window border. + if (GetClientRect(wd.hWnd, &r)) { // Retrieves area inside of window border, including decoration. return Size2(r.right - r.left, r.bottom - r.top); } return Size2(); @@ -873,13 +867,17 @@ Size2i DisplayServerWindows::window_get_real_size(WindowID p_window) const { const WindowData &wd = windows[p_window]; RECT r; - if (GetWindowRect(wd.hWnd, &r)) { // Includes area of the window border + if (GetWindowRect(wd.hWnd, &r)) { // Retrieves area inside of window border, including decoration. return Size2(r.right - r.left, r.bottom - r.top); } return Size2(); } void DisplayServerWindows::_get_window_style(bool p_main_window, bool p_fullscreen, bool p_borderless, bool p_resizable, bool p_maximized, bool p_no_activate_focus, DWORD &r_style, DWORD &r_style_ex) { + // Windows docs for window styles: + // https://docs.microsoft.com/en-us/windows/win32/winmsg/window-styles + // https://docs.microsoft.com/en-us/windows/win32/winmsg/extended-window-styles + r_style = 0; r_style_ex = WS_EX_WINDOWEDGE; if (p_main_window) { @@ -887,10 +885,7 @@ void DisplayServerWindows::_get_window_style(bool p_main_window, bool p_fullscre } if (p_fullscreen || p_borderless) { - r_style |= WS_POPUP; - //if (p_borderless) { - // r_style_ex |= WS_EX_TOOLWINDOW; - //} + r_style |= WS_POPUP; // p_borderless was WS_EX_TOOLWINDOW in the past. } else { if (p_resizable) { if (p_maximized) { @@ -1026,7 +1021,7 @@ bool DisplayServerWindows::window_is_maximize_allowed(WindowID p_window) const { // FIXME: Implement this, or confirm that it should always be true. - return true; //no idea + return true; } void DisplayServerWindows::window_set_flag(WindowFlags p_flag, bool p_enabled, WindowID p_window) { @@ -1171,8 +1166,9 @@ void DisplayServerWindows::window_set_ime_position(const Point2i &p_pos, WindowI void DisplayServerWindows::console_set_visible(bool p_enabled) { _THREAD_SAFE_METHOD_ - if (console_visible == p_enabled) + if (console_visible == p_enabled) { return; + } ShowWindow(GetConsoleWindow(), p_enabled ? SW_SHOW : SW_HIDE); console_visible = p_enabled; } @@ -1186,8 +1182,9 @@ void DisplayServerWindows::cursor_set_shape(CursorShape p_shape) { ERR_FAIL_INDEX(p_shape, CURSOR_MAX); - if (cursor_shape == p_shape) + if (cursor_shape == p_shape) { return; + } if (mouse_mode != MOUSE_MODE_VISIBLE && mouse_mode != MOUSE_MODE_CONFINED) { cursor_shape = p_shape; @@ -1197,7 +1194,7 @@ void DisplayServerWindows::cursor_set_shape(CursorShape p_shape) { static const LPCTSTR win_cursors[CURSOR_MAX] = { IDC_ARROW, IDC_IBEAM, - IDC_HAND, //finger + IDC_HAND, // Finger. IDC_CROSS, IDC_WAIT, IDC_APPSTARTING, @@ -1228,49 +1225,49 @@ DisplayServer::CursorShape DisplayServerWindows::cursor_get_shape() const { } void DisplayServerWindows::GetMaskBitmaps(HBITMAP hSourceBitmap, COLORREF clrTransparent, OUT HBITMAP &hAndMaskBitmap, OUT HBITMAP &hXorMaskBitmap) { - // Get the system display DC + // Get the system display DC. HDC hDC = GetDC(nullptr); - // Create helper DC + // Create helper DC. HDC hMainDC = CreateCompatibleDC(hDC); HDC hAndMaskDC = CreateCompatibleDC(hDC); HDC hXorMaskDC = CreateCompatibleDC(hDC); - // Get the dimensions of the source bitmap + // Get the dimensions of the source bitmap. BITMAP bm; GetObject(hSourceBitmap, sizeof(BITMAP), &bm); - // Create the mask bitmaps - hAndMaskBitmap = CreateCompatibleBitmap(hDC, bm.bmWidth, bm.bmHeight); // color - hXorMaskBitmap = CreateCompatibleBitmap(hDC, bm.bmWidth, bm.bmHeight); // color + // Create the mask bitmaps. + hAndMaskBitmap = CreateCompatibleBitmap(hDC, bm.bmWidth, bm.bmHeight); // Color. + hXorMaskBitmap = CreateCompatibleBitmap(hDC, bm.bmWidth, bm.bmHeight); // Color. - // Release the system display DC + // Release the system display DC. ReleaseDC(nullptr, hDC); - // Select the bitmaps to helper DC + // Select the bitmaps to helper DC. HBITMAP hOldMainBitmap = (HBITMAP)SelectObject(hMainDC, hSourceBitmap); HBITMAP hOldAndMaskBitmap = (HBITMAP)SelectObject(hAndMaskDC, hAndMaskBitmap); HBITMAP hOldXorMaskBitmap = (HBITMAP)SelectObject(hXorMaskDC, hXorMaskBitmap); // Assign the monochrome AND mask bitmap pixels so that the pixels of the source bitmap - // with 'clrTransparent' will be white pixels of the monochrome bitmap + // with 'clrTransparent' will be white pixels of the monochrome bitmap. SetBkColor(hMainDC, clrTransparent); BitBlt(hAndMaskDC, 0, 0, bm.bmWidth, bm.bmHeight, hMainDC, 0, 0, SRCCOPY); // Assign the color XOR mask bitmap pixels so that the pixels of the source bitmap // with 'clrTransparent' will be black and rest the pixels same as corresponding - // pixels of the source bitmap + // pixels of the source bitmap. SetBkColor(hXorMaskDC, RGB(0, 0, 0)); SetTextColor(hXorMaskDC, RGB(255, 255, 255)); BitBlt(hXorMaskDC, 0, 0, bm.bmWidth, bm.bmHeight, hAndMaskDC, 0, 0, SRCCOPY); BitBlt(hXorMaskDC, 0, 0, bm.bmWidth, bm.bmHeight, hMainDC, 0, 0, SRCAND); - // Deselect bitmaps from the helper DC + // Deselect bitmaps from the helper DC. SelectObject(hMainDC, hOldMainBitmap); SelectObject(hAndMaskDC, hOldAndMaskBitmap); SelectObject(hXorMaskDC, hOldXorMaskBitmap); - // Delete the helper DC + // Delete the helper DC. DeleteDC(hXorMaskDC); DeleteDC(hAndMaskDC); DeleteDC(hMainDC); @@ -1327,7 +1324,7 @@ void DisplayServerWindows::cursor_set_custom_image(const RES &p_cursor, CursorSh UINT image_size = texture_size.width * texture_size.height; - // Create the BITMAP with alpha channel + // Create the BITMAP with alpha channel. COLORREF *buffer = (COLORREF *)memalloc(sizeof(COLORREF) * image_size); for (UINT index = 0; index < image_size; index++) { @@ -1342,11 +1339,11 @@ void DisplayServerWindows::cursor_set_custom_image(const RES &p_cursor, CursorSh *(buffer + index) = image->get_pixel(column_index, row_index).to_argb32(); } - // Using 4 channels, so 4 * 8 bits + // Using 4 channels, so 4 * 8 bits. HBITMAP bitmap = CreateBitmap(texture_size.width, texture_size.height, 1, 4 * 8, buffer); COLORREF clrTransparent = -1; - // Create the AND and XOR masks for the bitmap + // Create the AND and XOR masks for the bitmap. HBITMAP hAndMask = nullptr; HBITMAP hXorMask = nullptr; @@ -1358,7 +1355,7 @@ void DisplayServerWindows::cursor_set_custom_image(const RES &p_cursor, CursorSh return; } - // Finally, create the icon + // Finally, create the icon. ICONINFO iconinfo; iconinfo.fIcon = FALSE; iconinfo.xHotspot = p_hotspot.x; @@ -1366,8 +1363,9 @@ void DisplayServerWindows::cursor_set_custom_image(const RES &p_cursor, CursorSh iconinfo.hbmMask = hAndMask; iconinfo.hbmColor = hXorMask; - if (cursors[p_shape]) + if (cursors[p_shape]) { DestroyIcon(cursors[p_shape]); + } cursors[p_shape] = CreateIconIndirect(&iconinfo); @@ -1393,7 +1391,7 @@ void DisplayServerWindows::cursor_set_custom_image(const RES &p_cursor, CursorSh memfree(buffer); DeleteObject(bitmap); } else { - // Reset to default system cursor + // Reset to default system cursor. if (cursors[p_shape]) { DestroyIcon(cursors[p_shape]); cursors[p_shape] = nullptr; @@ -1575,9 +1573,9 @@ void DisplayServerWindows::set_native_icon(const String &p_filename) { icon_dir = (ICONDIR *)memrealloc(icon_dir, 3 * sizeof(WORD) + icon_dir->idCount * sizeof(ICONDIRENTRY)); f->get_buffer((uint8_t *)&icon_dir->idEntries[0], icon_dir->idCount * sizeof(ICONDIRENTRY)); - int small_icon_index = -1; // Select 16x16 with largest color count + int small_icon_index = -1; // Select 16x16 with largest color count. int small_icon_cc = 0; - int big_icon_index = -1; // Select largest + int big_icon_index = -1; // Select largest. int big_icon_width = 16; int big_icon_cc = 0; @@ -1607,7 +1605,7 @@ void DisplayServerWindows::set_native_icon(const String &p_filename) { small_icon_cc = big_icon_cc; } - // Read the big icon + // Read the big icon. DWORD bytecount_big = icon_dir->idEntries[big_icon_index].dwBytesInRes; Vector<uint8_t> data_big; data_big.resize(bytecount_big); @@ -1617,7 +1615,7 @@ void DisplayServerWindows::set_native_icon(const String &p_filename) { HICON icon_big = CreateIconFromResource((PBYTE)&data_big.write[0], bytecount_big, TRUE, 0x00030000); ERR_FAIL_COND_MSG(!icon_big, "Could not create " + itos(big_icon_width) + "x" + itos(big_icon_width) + " @" + itos(big_icon_cc) + " icon, error: " + format_error_message(GetLastError()) + "."); - // Read the small icon + // Read the small icon. DWORD bytecount_small = icon_dir->idEntries[small_icon_index].dwBytesInRes; Vector<uint8_t> data_small; data_small.resize(bytecount_small); @@ -1627,7 +1625,7 @@ void DisplayServerWindows::set_native_icon(const String &p_filename) { HICON icon_small = CreateIconFromResource((PBYTE)&data_small.write[0], bytecount_small, TRUE, 0x00030000); ERR_FAIL_COND_MSG(!icon_small, "Could not create 16x16 @" + itos(small_icon_cc) + " icon, error: " + format_error_message(GetLastError()) + "."); - // Online tradition says to be sure last error is cleared and set the small icon first + // Online tradition says to be sure last error is cleared and set the small icon first. int err = 0; SetLastError(err); @@ -1648,12 +1646,13 @@ void DisplayServerWindows::set_icon(const Ref<Image> &p_icon) { ERR_FAIL_COND(!p_icon.is_valid()); Ref<Image> icon = p_icon->duplicate(); - if (icon->get_format() != Image::FORMAT_RGBA8) + if (icon->get_format() != Image::FORMAT_RGBA8) { icon->convert(Image::FORMAT_RGBA8); + } int w = icon->get_width(); int h = icon->get_height(); - /* Create temporary bitmap buffer */ + // Create temporary bitmap buffer. int icon_len = 40 + h * w * 4; Vector<BYTE> v; v.resize(icon_len); @@ -1687,10 +1686,10 @@ void DisplayServerWindows::set_icon(const Ref<Image> &p_icon) { HICON hicon = CreateIconFromResource(icon_bmp, icon_len, TRUE, 0x00030000); - /* Set the icon for the window */ + // Set the icon for the window. SendMessage(windows[MAIN_WINDOW_ID].hWnd, WM_SETICON, ICON_SMALL, (LPARAM)hicon); - /* Set the icon in the task manager (should we do this?) */ + // Set the icon in the task manager (should we do this?). SendMessage(windows[MAIN_WINDOW_ID].hWnd, WM_SETICON, ICON_BIG, (LPARAM)hicon); } @@ -1718,13 +1717,13 @@ void DisplayServerWindows::set_context(Context p_context) { // Keeping the name suggested by Microsoft, but this macro really answers: // Is this mouse event emulated from touch or pen input? #define IsPenEvent(dw) (((dw)&SIGNATURE_MASK) == MI_WP_SIGNATURE) -// This one tells whether the event comes from touchscreen (and not from pen) +// This one tells whether the event comes from touchscreen (and not from pen). #define IsTouchEvent(dw) (IsPenEvent(dw) && ((dw)&0x80)) void DisplayServerWindows::_touch_event(WindowID p_window, bool p_pressed, float p_x, float p_y, int idx) { - // Defensive - if (touch_state.has(idx) == p_pressed) + if (touch_state.has(idx) == p_pressed) { return; + } if (p_pressed) { touch_state.insert(idx, Vector2(p_x, p_y)); @@ -1744,12 +1743,13 @@ void DisplayServerWindows::_touch_event(WindowID p_window, bool p_pressed, float void DisplayServerWindows::_drag_event(WindowID p_window, float p_x, float p_y, int idx) { Map<int, Vector2>::Element *curr = touch_state.find(idx); - // Defensive - if (!curr) + if (!curr) { return; + } - if (curr->get() == Vector2(p_x, p_y)) + if (curr->get() == Vector2(p_x, p_y)) { return; + } Ref<InputEventScreenDrag> event; event.instantiate(); @@ -1791,7 +1791,7 @@ void DisplayServerWindows::_dispatch_input_event(const Ref<InputEvent> &p_event) Ref<InputEventFromWindow> event_from_window = p_event; if (event_from_window.is_valid() && event_from_window->get_window_id() != INVALID_WINDOW_ID) { - //send to a window + // Send to a single window. if (!windows.has(event_from_window->get_window_id())) { in_dispatch_input_event = false; ERR_FAIL_MSG("DisplayServerWindows: Invalid window id in input event."); @@ -1803,7 +1803,7 @@ void DisplayServerWindows::_dispatch_input_event(const Ref<InputEvent> &p_event) } callable.call((const Variant **)&evp, 1, ret, ce); } else { - //send to all windows + // Send to all windows. for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) { Callable callable = E->get().input_event_callback; if (callable.is_null()) { @@ -1816,6 +1816,9 @@ void DisplayServerWindows::_dispatch_input_event(const Ref<InputEvent> &p_event) in_dispatch_input_event = false; } +// Our default window procedure to handle processing of window-related system messages/events. +// Also known as DefProc or DefWindowProc. +// See: https://docs.microsoft.com/en-us/windows/win32/winmsg/window-procedures LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { if (drop_events) { if (user_proc) { @@ -1828,6 +1831,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA WindowID window_id = INVALID_WINDOW_ID; bool window_created = false; + // Check whether window exists. for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) { if (E->get().hWnd == hWnd) { window_id = E->key(); @@ -1836,19 +1840,19 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA } } + // Window doesn't exist or creation in progress, don't handle messages yet. if (!window_created) { - // Window creation in progress. window_id = window_id_counter; ERR_FAIL_COND_V(!windows.has(window_id), 0); } - switch (uMsg) // Check For Windows Messages - { + // Process window messages. + switch (uMsg) { case WM_SETFOCUS: { windows[window_id].window_has_focus = true; last_focused_window = window_id; - // Restore mouse mode + // Restore mouse mode. _set_mouse_mode_impl(mouse_mode); if (!app_focused) { @@ -1857,16 +1861,15 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA } app_focused = true; } - break; - } + } break; case WM_KILLFOCUS: { windows[window_id].window_has_focus = false; last_focused_window = window_id; - // Release capture unconditionally because it can be set due to dragging, in addition to captured mode + // Release capture unconditionally because it can be set due to dragging, in addition to captured mode. ReleaseCapture(); - // Release every touch to avoid sticky points + // Release every touch to avoid sticky points. for (Map<int, Vector2>::Element *E = touch_state.front(); E; E = E->next()) { _touch_event(window_id, false, E->get().x, E->get().y, E->key()); } @@ -1884,10 +1887,8 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA } app_focused = false; } - - break; - } - case WM_ACTIVATE: { // Watch For Window Activate Message + } break; + case WM_ACTIVATE: { // Watch for window activate message. if (!windows[window_id].window_focused) { _process_activate_event(window_id, wParam, lParam); } else { @@ -1897,8 +1898,8 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA // Run a timer to prevent event catching warning if the focused window is closing. windows[window_id].focus_timer_id = SetTimer(windows[window_id].hWnd, 2, USER_TIMER_MINIMUM, (TIMERPROC) nullptr); } - return 0; // Return To The Message Loop - } + return 0; // Return to the message loop. + } break; case WM_GETMINMAXINFO: { if (windows[window_id].resizable && !windows[window_id].fullscreen) { // Size of window decorations. @@ -1914,37 +1915,31 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA min_max_info->ptMaxTrackSize.y = windows[window_id].max_size.y + decor.y; } return 0; - } else { - break; } - } - case WM_PAINT: - + } break; + case WM_PAINT: { Main::force_redraw(); - break; - - case WM_SYSCOMMAND: // Intercept System Commands + } break; + case WM_SYSCOMMAND: // Intercept system commands. { - switch (wParam) // Check System Calls + switch (wParam) // Check system calls. { - case SC_SCREENSAVE: // Screensaver Trying To Start? - case SC_MONITORPOWER: // Monitor Trying To Enter Powersave? - return 0; // Prevent From Happening + case SC_SCREENSAVE: // Screensaver trying to start? + case SC_MONITORPOWER: // Monitor trying to enter powersave? + return 0; // Prevent from happening. case SC_KEYMENU: if ((lParam >> 16) <= 0) return 0; } - break; // Exit - } - - case WM_CLOSE: // Did We Receive A Close Message? + } break; + case WM_CLOSE: // Did we receive a close message? { if (windows[window_id].focus_timer_id != 0U) { KillTimer(windows[window_id].hWnd, windows[window_id].focus_timer_id); } _send_window_event(windows[window_id], WINDOW_EVENT_CLOSE_REQUEST); - return 0; // Jump Back + return 0; // Jump back. } case WM_MOUSELEAVE: { old_invalid = true; @@ -1986,7 +1981,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA Point2i c(windows[window_id].width / 2, windows[window_id].height / 2); - // centering just so it works as before + // Centering just so it works as before. POINT pos = { (int)c.x, (int)c.y }; ClientToScreen(windows[window_id].hWnd, &pos); SetCursorPos(pos.x, pos.y); @@ -2009,7 +2004,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA (double(raw->data.mouse.lLastX) - 65536.0 / (nScreenWidth)) * nScreenWidth / 65536.0 + nScreenLeft, (double(raw->data.mouse.lLastY) - 65536.0 / (nScreenHeight)) * nScreenHeight / 65536.0 + nScreenTop); - POINT coords; //client coords + POINT coords; // Client coords. coords.x = abs_pos.x; coords.y = abs_pos.y; @@ -2018,14 +2013,11 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA mm->set_relative(Vector2(coords.x - old_x, coords.y - old_y)); old_x = coords.x; old_y = coords.y; - - /*Input.mi.dx = (int)((((double)(pos.x)-nScreenLeft) * 65536) / nScreenWidth + 65536 / (nScreenWidth)); - Input.mi.dy = (int)((((double)(pos.y)-nScreenTop) * 65536) / nScreenHeight + 65536 / (nScreenHeight)); - */ } - if (windows[window_id].window_has_focus && mm->get_relative() != Vector2()) + if (windows[window_id].window_has_focus && mm->get_relative() != Vector2()) { Input::get_singleton()->parse_input_event(mm); + } } delete[] lpb; } break; @@ -2170,7 +2162,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA } if (Input::get_singleton()->is_emulating_mouse_from_touch()) { - // Universal translation enabled; ignore OS translation + // Universal translation enabled; ignore OS translation. LPARAM extra = GetMessageExtraInfo(); if (IsTouchEvent(extra)) { break; @@ -2178,7 +2170,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA } if (outside) { - //mouse enter + // Mouse enter. if (mouse_mode != MOUSE_MODE_CAPTURED) { _send_window_event(windows[window_id], WINDOW_EVENT_MOUSE_ENTER); @@ -2189,7 +2181,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA cursor_set_shape(c); outside = false; - //Once-Off notification, must call again.... + // Once-off notification, must call again. TRACKMOUSEEVENT tme; tme.cbSize = sizeof(TRACKMOUSEEVENT); tme.dwFlags = TME_LEAVE; @@ -2222,7 +2214,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA mm->set_button_mask(last_button_state); - POINT coords; //client coords + POINT coords; // Client coords. coords.x = GET_X_LPARAM(lParam); coords.y = GET_Y_LPARAM(lParam); @@ -2264,7 +2256,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA Input::get_singleton()->parse_input_event(mm); } - return 0; //Pointer event handled return 0 to avoid duplicate WM_MOUSEMOVE event + return 0; // Pointer event handled return 0 to avoid duplicate WM_MOUSEMOVE event. } break; case WM_MOUSEMOVE: { if (windows[window_id].block_mm) { @@ -2276,7 +2268,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA } if (Input::get_singleton()->is_emulating_mouse_from_touch()) { - // Universal translation enabled; ignore OS translation + // Universal translation enabled; ignore OS translation. LPARAM extra = GetMessageExtraInfo(); if (IsTouchEvent(extra)) { break; @@ -2284,7 +2276,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA } if (outside) { - //mouse enter + // Mouse enter. if (mouse_mode != MOUSE_MODE_CAPTURED) { _send_window_event(windows[window_id], WINDOW_EVENT_MOUSE_ENTER); @@ -2295,7 +2287,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA cursor_set_shape(c); outside = false; - //Once-Off notification, must call again.... + // Once-off notification, must call again. TRACKMOUSEEVENT tme; tme.cbSize = sizeof(TRACKMOUSEEVENT); tme.dwFlags = TME_LEAVE; @@ -2373,7 +2365,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA case WM_LBUTTONDOWN: case WM_LBUTTONUP: if (Input::get_singleton()->is_emulating_mouse_from_touch()) { - // Universal translation enabled; ignore OS translations for left button + // Universal translation enabled; ignore OS translations for left button. LPARAM extra = GetMessageExtraInfo(); if (IsTouchEvent(extra)) { break; @@ -2497,7 +2489,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA mb->set_ctrl_pressed((wParam & MK_CONTROL) != 0); mb->set_shift_pressed((wParam & MK_SHIFT) != 0); mb->set_alt_pressed(alt_mem); - //mb->is_alt_pressed()=(wParam&MK_MENU)!=0; + // mb->is_alt_pressed()=(wParam&MK_MENU)!=0; if (mb->is_pressed()) { last_button_state |= MouseButton(1 << (mb->get_button_index() - 1)); } else { @@ -2524,7 +2516,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA } } } else { - // for reasons unknown to mankind, wheel comes in screen coordinates + // For reasons unknown to mankind, wheel comes in screen coordinates. POINT coords; coords.x = mb->get_position().x; coords.y = mb->get_position().y; @@ -2538,7 +2530,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA Input::get_singleton()->parse_input_event(mb); if (mb->is_pressed() && mb->get_button_index() > 3 && mb->get_button_index() < 8) { - //send release for mouse wheel + // Send release for mouse wheel. Ref<InputEventMouseButton> mbd = mb->duplicate(); mbd->set_window_id(window_id); last_button_state &= (MouseButton) ~(1 << (mbd->get_button_index() - 1)); @@ -2548,7 +2540,6 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA } } break; - case WM_MOVE: { if (!IsIconic(windows[window_id].hWnd)) { int x = int16_t(LOWORD(lParam)); @@ -2564,7 +2555,6 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA } } } break; - case WM_SIZE: { // Ignore window size change when a SIZE_MINIMIZED event is triggered. if (wParam != SIZE_MINIMIZED) { @@ -2642,7 +2632,6 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA } #endif } break; - case WM_ENTERSIZEMOVE: { Input::get_singleton()->release_pressed_events(); windows[window_id].move_timer_id = SetTimer(windows[window_id].hWnd, 1, USER_TIMER_MINIMUM, (TIMERPROC) nullptr); @@ -2662,7 +2651,6 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA windows[window_id].focus_timer_id = 0U; } } break; - case WM_SYSKEYDOWN: case WM_SYSKEYUP: case WM_KEYUP: @@ -2714,7 +2702,6 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA case WM_INPUTLANGCHANGEREQUEST: { // FIXME: Do something? } break; - case WM_TOUCH: { BOOL bHandled = FALSE; UINT cInputs = LOWORD(wParam); @@ -2728,7 +2715,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA TOUCH_COORD_TO_PIXEL(ti.y), }; ScreenToClient(hWnd, &touch_pos); - //do something with each touch input entry + // Do something with each touch input entry. if (ti.dwFlags & TOUCHEVENTF_MOVE) { _drag_event(window_id, touch_pos.x, touch_pos.y, ti.dwID); } else if (ti.dwFlags & (TOUCHEVENTF_UP | TOUCHEVENTF_DOWN)) { @@ -2737,11 +2724,11 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA } bHandled = TRUE; } else { - /* handle the error here */ + // TODO: Handle the error here. } memdelete_arr(pInputs); } else { - /* handle the error here, probably out of memory */ + // TODO: Handle the error here, probably out of memory. } if (bHandled) { CloseTouchInputHandle((HTOUCHINPUT)lParam); @@ -2749,14 +2736,13 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA }; } break; - case WM_DEVICECHANGE: { joypad->probe_joypads(); } break; case WM_SETCURSOR: { if (LOWORD(lParam) == HTCLIENT) { if (windows[window_id].window_has_focus && (mouse_mode == MOUSE_MODE_HIDDEN || mouse_mode == MOUSE_MODE_CAPTURED || mouse_mode == MOUSE_MODE_CONFINED_HIDDEN)) { - //Hide the cursor + // Hide the cursor. if (hCursor == nullptr) { hCursor = SetCursor(nullptr); } else { @@ -2771,7 +2757,6 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA } } } - } break; case WM_DROPFILES: { HDROP hDropInfo = (HDROP)wParam; @@ -2795,9 +2780,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA Callable::CallError ce; windows[window_id].drop_files_callback.call((const Variant **)&vp, 1, ret, ce); } - } break; - default: { if (user_proc) { return CallWindowProcW(user_proc, hWnd, uMsg, wParam, lParam); @@ -2823,7 +2806,7 @@ void DisplayServerWindows::_process_activate_event(WindowID p_window_id, WPARAM alt_mem = false; control_mem = false; shift_mem = false; - } else { // WM_INACTIVE + } else { // WM_INACTIVE. Input::get_singleton()->release_pressed_events(); _send_window_event(windows[p_window_id], WINDOW_EVENT_FOCUS_OUT); windows[p_window_id].window_focused = false; @@ -2840,7 +2823,7 @@ void DisplayServerWindows::_process_key_events() { KeyEvent &ke = key_event_buffer[i]; switch (ke.uMsg) { case WM_CHAR: { - // extended keys should only be processed as WM_KEYDOWN message. + // Extended keys should only be processed as WM_KEYDOWN message. if (!KeyMappingWindows::is_extended_key(ke.wParam) && ((i == 0 && ke.uMsg == WM_CHAR) || (i > 0 && key_event_buffer[i - 1].uMsg == WM_CHAR))) { static char32_t prev_wc = 0; char32_t unicode = ke.wParam; @@ -2881,9 +2864,9 @@ void DisplayServerWindows::_process_key_events() { k->set_unicode(0); Input::get_singleton()->parse_input_event(k); + } else { + // Do nothing. } - - //do nothing } break; case WM_KEYUP: case WM_KEYDOWN: { @@ -2899,7 +2882,7 @@ void DisplayServerWindows::_process_key_events() { k->set_pressed(ke.uMsg == WM_KEYDOWN); if ((ke.lParam & (1 << 24)) && (ke.wParam == VK_RETURN)) { - // Special case for Numpad Enter key + // Special case for Numpad Enter key. k->set_keycode(KEY_KP_ENTER); } else { k->set_keycode((Key)KeyMappingWindows::get_keysym(ke.wParam)); @@ -3045,8 +3028,8 @@ DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode, if (p_mode != WINDOW_MODE_FULLSCREEN) { wd.pre_fs_valid = true; } -#ifdef VULKAN_ENABLED +#ifdef VULKAN_ENABLED if (rendering_driver == "vulkan") { if (context_vulkan->window_create(id, p_vsync_mode, wd.hWnd, hInstance, WindowRect.right - WindowRect.left, WindowRect.bottom - WindowRect.top) == -1) { memdelete(context_vulkan); @@ -3101,7 +3084,7 @@ DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode, wd.last_pressure_update = 0; wd.last_tilt = Vector2(); - // IME + // IME. wd.im_himc = ImmGetContext(wd.hWnd); ImmReleaseContext(wd.hWnd, wd.im_himc); @@ -3116,7 +3099,7 @@ DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode, return id; } -// WinTab API +// WinTab API. bool DisplayServerWindows::wintab_available = false; WTOpenPtr DisplayServerWindows::wintab_WTOpen = nullptr; WTClosePtr DisplayServerWindows::wintab_WTClose = nullptr; @@ -3124,7 +3107,7 @@ WTInfoPtr DisplayServerWindows::wintab_WTInfo = nullptr; WTPacketPtr DisplayServerWindows::wintab_WTPacket = nullptr; WTEnablePtr DisplayServerWindows::wintab_WTEnable = nullptr; -// Windows Ink API +// Windows Ink API. bool DisplayServerWindows::winink_available = false; GetPointerTypePtr DisplayServerWindows::win8p_GetPointerType = nullptr; GetPointerPenInfoPtr DisplayServerWindows::win8p_GetPointerPenInfo = nullptr; @@ -3187,7 +3170,7 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win outside = true; - //Note: Wacom WinTab driver API for pen input, for devices incompatible with Windows Ink. + // Note: Wacom WinTab driver API for pen input, for devices incompatible with Windows Ink. HMODULE wintab_lib = LoadLibraryW(L"wintab32.dll"); if (wintab_lib) { wintab_WTOpen = (WTOpenPtr)GetProcAddress(wintab_lib, "WTOpenW"); @@ -3203,7 +3186,7 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win tablet_drivers.push_back("wintab"); } - //Note: Windows Ink API for pen input, available on Windows 8+ only. + // Note: Windows Ink API for pen input, available on Windows 8+ only. HMODULE user32_lib = LoadLibraryW(L"user32.dll"); if (user32_lib) { win8p_GetPointerType = (GetPointerTypePtr)GetProcAddress(user32_lib, "GetPointerType"); @@ -3236,7 +3219,6 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win wc.lpfnWndProc = (WNDPROC)::WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; - //wc.hInstance = hInstance; wc.hInstance = hInstance ? hInstance : GetModuleHandle(nullptr); wc.hIcon = LoadIcon(nullptr, IDI_WINLOGO); wc.hCursor = nullptr; //LoadCursor(nullptr, IDC_ARROW); @@ -3260,7 +3242,7 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win Rid[0].hwndTarget = 0; if (RegisterRawInputDevices(Rid, 1, sizeof(Rid[0])) == FALSE) { - //registration failed. + // Registration failed. use_raw_input = false; } @@ -3277,6 +3259,7 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win } } #endif + #if defined(OPENGL_ENABLED) if (rendering_driver_index == VIDEO_DRIVER_GLES2) { context_gles2 = memnew(ContextGL_Windows(hWnd, false)); @@ -3299,6 +3282,7 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win } } #endif + Point2i window_position( (screen_get_size(0).width - p_resolution.width) / 2, (screen_get_size(0).height - p_resolution.height) / 2); @@ -3327,7 +3311,6 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win //set_ime_active(false); if (!OS::get_singleton()->is_in_low_processor_usage_mode()) { - //SetPriorityClass(GetCurrentProcess(), ABOVE_NORMAL_PRIORITY_CLASS); SetPriorityClass(GetCurrentProcess(), ABOVE_NORMAL_PRIORITY_CLASS); DWORD index = 0; HANDLE handle = AvSetMmThreadCharacteristics("Games", &index); @@ -3335,7 +3318,7 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win AvSetMmThreadPriority(handle, AVRT_PRIORITY_CRITICAL); // This is needed to make sure that background work does not starve the main thread. - // This is only setting priority of this thread, not the whole process. + // This is only setting the priority of this thread, not the whole process. SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL); } diff --git a/scene/2d/audio_stream_player_2d.cpp b/scene/2d/audio_stream_player_2d.cpp index ea491e8b0e..8401909384 100644 --- a/scene/2d/audio_stream_player_2d.cpp +++ b/scene/2d/audio_stream_player_2d.cpp @@ -31,6 +31,7 @@ #include "audio_stream_player_2d.h" #include "scene/2d/area_2d.h" +#include "scene/2d/listener_2d.h" #include "scene/main/window.h" void AudioStreamPlayer2D::_notification(int p_what) { @@ -59,33 +60,47 @@ void AudioStreamPlayer2D::_notification(int p_what) { if (p_what == NOTIFICATION_INTERNAL_PHYSICS_PROCESS) { //update anything related to position first, if possible of course + if (setplay.get() > 0 || (active.is_set() && last_mix_count != AudioServer::get_singleton()->get_mix_count())) { + _update_panning(); + } - if (!stream_playback.is_valid()) { - return; + if (setplay.get() >= 0 && stream.is_valid()) { + active.set(); + Ref<AudioStreamPlayback> new_playback = stream->instance_playback(); + ERR_FAIL_COND_MSG(new_playback.is_null(), "Failed to instantiate playback."); + AudioServer::get_singleton()->start_playback_stream(new_playback, _get_actual_bus(), volume_vector, setplay.get()); + stream_playbacks.push_back(new_playback); + setplay.set(-1); } - if (setplay.get() >= 0 || (active.is_set() && last_mix_count != AudioServer::get_singleton()->get_mix_count())) { - _update_panning(); - if (setplay.get() >= 0) { - active.set(); - AudioServer::get_singleton()->start_playback_stream(stream_playback, _get_actual_bus(), volume_vector, setplay.get()); - setplay.set(-1); + + if (!stream_playbacks.is_empty() && active.is_set()) { + // Stop playing if no longer active. + Vector<Ref<AudioStreamPlayback>> playbacks_to_remove; + for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { + if (playback.is_valid() && !AudioServer::get_singleton()->is_playback_active(playback) && !AudioServer::get_singleton()->is_playback_paused(playback)) { + emit_signal(SNAME("finished")); + playbacks_to_remove.push_back(playback); + } + } + // Now go through and remove playbacks that have finished. Removing elements from a Vector in a range based for is asking for trouble. + for (Ref<AudioStreamPlayback> &playback : playbacks_to_remove) { + stream_playbacks.erase(playback); + } + if (!playbacks_to_remove.is_empty() && stream_playbacks.is_empty()) { + // This node is no longer actively playing audio. + active.clear(); + set_physics_process_internal(false); } } - // Stop playing if no longer active. - if (active.is_set() && !AudioServer::get_singleton()->is_playback_active(stream_playback)) { - active.clear(); - set_physics_process_internal(false); - emit_signal(SNAME("finished")); + while (stream_playbacks.size() > max_polyphony) { + AudioServer::get_singleton()->stop_playback_stream(stream_playbacks[0]); + stream_playbacks.remove(0); } } } StringName AudioStreamPlayer2D::_get_actual_bus() { - if (!stream_playback.is_valid()) { - return SNAME("Master"); - } - Vector2 global_pos = get_global_position(); //check if any area is diverting sound into a bus @@ -113,12 +128,10 @@ StringName AudioStreamPlayer2D::_get_actual_bus() { } void AudioStreamPlayer2D::_update_panning() { - if (!stream_playback.is_valid()) { + if (!active.is_set() || stream.is_null()) { return; } - last_mix_count = AudioServer::get_singleton()->get_mix_count(); - Ref<World2D> world_2d = get_world_2d(); ERR_FAIL_COND(world_2d.is_null()); @@ -138,13 +151,22 @@ void AudioStreamPlayer2D::_update_panning() { continue; } //compute matrix to convert to screen - Transform2D to_screen = vp->get_global_canvas_transform() * vp->get_canvas_transform(); Vector2 screen_size = vp->get_visible_rect().size; + Vector2 listener_in_global; + Vector2 relative_to_listener; //screen in global is used for attenuation - Vector2 screen_in_global = to_screen.affine_inverse().xform(screen_size * 0.5); + Listener2D *listener = vp->get_listener_2d(); + if (listener) { + listener_in_global = listener->get_global_position(); + relative_to_listener = global_pos - listener_in_global; + } else { + Transform2D to_listener = vp->get_global_canvas_transform() * vp->get_canvas_transform(); + listener_in_global = to_listener.affine_inverse().xform(screen_size * 0.5); + relative_to_listener = to_listener.xform(global_pos) - screen_size * 0.5; + } - float dist = global_pos.distance_to(screen_in_global); //distance to screen center + float dist = global_pos.distance_to(listener_in_global); // Distance to listener, or screen if none. if (dist > max_distance) { continue; //can't hear this sound in this viewport @@ -153,10 +175,7 @@ void AudioStreamPlayer2D::_update_panning() { float multiplier = Math::pow(1.0f - dist / max_distance, attenuation); multiplier *= Math::db2linear(volume_db); //also apply player volume! - //point in screen is used for panning - Vector2 point_in_screen = to_screen.xform(global_pos); - - float pan = CLAMP(point_in_screen.x / screen_size.width, 0.0, 1.0); + float pan = CLAMP((relative_to_listener.x + screen_size.x * 0.5) / screen_size.x, 0.0, 1.0); float l = 1.0 - pan; float r = pan; @@ -164,28 +183,20 @@ void AudioStreamPlayer2D::_update_panning() { volume_vector.write[0] = AudioFrame(l, r) * multiplier; } - AudioServer::get_singleton()->set_playback_bus_exclusive(stream_playback, _get_actual_bus(), volume_vector); -} - -void AudioStreamPlayer2D::set_stream(Ref<AudioStream> p_stream) { - if (stream_playback.is_valid()) { - stop(); + for (const Ref<AudioStreamPlayback> &playback : stream_playbacks) { + AudioServer::get_singleton()->set_playback_bus_exclusive(playback, _get_actual_bus(), volume_vector); } - stream_playback.unref(); - stream.unref(); - if (p_stream.is_valid()) { - stream_playback = p_stream->instance_playback(); - if (stream_playback.is_valid()) { - stream = p_stream; - } else { - stream.unref(); - } + for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { + AudioServer::get_singleton()->set_playback_pitch_scale(playback, pitch_scale); } - if (p_stream.is_valid() && stream_playback.is_null()) { - stream.unref(); - } + last_mix_count = AudioServer::get_singleton()->get_mix_count(); +} + +void AudioStreamPlayer2D::set_stream(Ref<AudioStream> p_stream) { + stop(); + stream = p_stream; } Ref<AudioStream> AudioStreamPlayer2D::get_stream() const { @@ -203,8 +214,8 @@ float AudioStreamPlayer2D::get_volume_db() const { void AudioStreamPlayer2D::set_pitch_scale(float p_pitch_scale) { ERR_FAIL_COND(p_pitch_scale <= 0.0); pitch_scale = p_pitch_scale; - if (stream_playback.is_valid()) { - AudioServer::get_singleton()->set_playback_pitch_scale(stream_playback, p_pitch_scale); + for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { + AudioServer::get_singleton()->set_playback_pitch_scale(playback, p_pitch_scale); } } @@ -213,44 +224,50 @@ float AudioStreamPlayer2D::get_pitch_scale() const { } void AudioStreamPlayer2D::play(float p_from_pos) { - stop(); - if (stream.is_valid()) { - stream_playback = stream->instance_playback(); + if (stream.is_null()) { + return; } - if (stream_playback.is_valid()) { - setplay.set(p_from_pos); - set_physics_process_internal(true); + ERR_FAIL_COND_MSG(!is_inside_tree(), "Playback can only happen when a node is inside the scene tree"); + if (stream->is_monophonic() && is_playing()) { + stop(); } + + setplay.set(p_from_pos); + active.set(); + set_physics_process_internal(true); } void AudioStreamPlayer2D::seek(float p_seconds) { - if (stream_playback.is_valid() && active.is_set()) { + if (is_playing()) { + stop(); play(p_seconds); } } void AudioStreamPlayer2D::stop() { - if (stream_playback.is_valid()) { - active.clear(); - AudioServer::get_singleton()->stop_playback_stream(stream_playback); - set_physics_process_internal(false); - setplay.set(-1); + setplay.set(-1); + for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { + AudioServer::get_singleton()->stop_playback_stream(playback); } + stream_playbacks.clear(); + active.clear(); + set_physics_process_internal(false); } bool AudioStreamPlayer2D::is_playing() const { - if (stream_playback.is_valid()) { - return AudioServer::get_singleton()->is_playback_active(stream_playback); + for (const Ref<AudioStreamPlayback> &playback : stream_playbacks) { + if (AudioServer::get_singleton()->is_playback_active(playback)) { + return true; + } } - return false; } float AudioStreamPlayer2D::get_playback_position() { - if (stream_playback.is_valid()) { - return AudioServer::get_singleton()->get_playback_position(stream_playback); + // Return the playback position of the most recently started playback stream. + if (!stream_playbacks.is_empty()) { + return AudioServer::get_singleton()->get_playback_position(stream_playbacks[stream_playbacks.size() - 1]); } - return 0; } @@ -284,11 +301,7 @@ void AudioStreamPlayer2D::_set_playing(bool p_enable) { } bool AudioStreamPlayer2D::_is_active() const { - if (stream_playback.is_valid()) { - // TODO make sure this doesn't change any behavior w.r.t. pauses. Is a paused stream active? - return AudioServer::get_singleton()->is_playback_active(stream_playback); - } - return false; + return active.is_set(); } void AudioStreamPlayer2D::_validate_property(PropertyInfo &property) const { @@ -336,21 +349,35 @@ uint32_t AudioStreamPlayer2D::get_area_mask() const { } void AudioStreamPlayer2D::set_stream_paused(bool p_pause) { - // TODO this does not have perfect recall, fix that maybe? If the stream isn't set, we can't persist this bool. - if (stream_playback.is_valid()) { - AudioServer::get_singleton()->set_playback_paused(stream_playback, p_pause); + // TODO this does not have perfect recall, fix that maybe? If there are zero playbacks registered with the AudioServer, this bool isn't persisted. + for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { + AudioServer::get_singleton()->set_playback_paused(playback, p_pause); } } bool AudioStreamPlayer2D::get_stream_paused() const { - if (stream_playback.is_valid()) { - return AudioServer::get_singleton()->is_playback_paused(stream_playback); + // There's currently no way to pause some playback streams but not others. Check the first and don't bother looking at the rest. + if (!stream_playbacks.is_empty()) { + return AudioServer::get_singleton()->is_playback_paused(stream_playbacks[0]); } return false; } Ref<AudioStreamPlayback> AudioStreamPlayer2D::get_stream_playback() { - return stream_playback; + if (!stream_playbacks.is_empty()) { + return stream_playbacks[stream_playbacks.size() - 1]; + } + return nullptr; +} + +void AudioStreamPlayer2D::set_max_polyphony(int p_max_polyphony) { + if (p_max_polyphony > 0) { + max_polyphony = p_max_polyphony; + } +} + +int AudioStreamPlayer2D::get_max_polyphony() const { + return max_polyphony; } void AudioStreamPlayer2D::_bind_methods() { @@ -391,6 +418,9 @@ void AudioStreamPlayer2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_stream_paused", "pause"), &AudioStreamPlayer2D::set_stream_paused); ClassDB::bind_method(D_METHOD("get_stream_paused"), &AudioStreamPlayer2D::get_stream_paused); + ClassDB::bind_method(D_METHOD("set_max_polyphony", "max_polyphony"), &AudioStreamPlayer2D::set_max_polyphony); + ClassDB::bind_method(D_METHOD("get_max_polyphony"), &AudioStreamPlayer2D::get_max_polyphony); + ClassDB::bind_method(D_METHOD("get_stream_playback"), &AudioStreamPlayer2D::get_stream_playback); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "stream", PROPERTY_HINT_RESOURCE_TYPE, "AudioStream"), "set_stream", "get_stream"); @@ -401,6 +431,7 @@ void AudioStreamPlayer2D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "stream_paused", PROPERTY_HINT_NONE, ""), "set_stream_paused", "get_stream_paused"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "max_distance", PROPERTY_HINT_RANGE, "1,4096,1,or_greater,exp"), "set_max_distance", "get_max_distance"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "attenuation", PROPERTY_HINT_EXP_EASING, "attenuation"), "set_attenuation", "get_attenuation"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "max_polyphony", PROPERTY_HINT_NONE, ""), "set_max_polyphony", "get_max_polyphony"); ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "bus", PROPERTY_HINT_ENUM, ""), "set_bus", "get_bus"); ADD_PROPERTY(PropertyInfo(Variant::INT, "area_mask", PROPERTY_HINT_LAYERS_2D_PHYSICS), "set_area_mask", "get_area_mask"); diff --git a/scene/2d/audio_stream_player_2d.h b/scene/2d/audio_stream_player_2d.h index 6428fbe017..5360fd4934 100644 --- a/scene/2d/audio_stream_player_2d.h +++ b/scene/2d/audio_stream_player_2d.h @@ -51,10 +51,10 @@ private: Viewport *viewport = nullptr; //pointer only used for reference to previous mix }; - Ref<AudioStreamPlayback> stream_playback; + Vector<Ref<AudioStreamPlayback>> stream_playbacks; Ref<AudioStream> stream; - SafeFlag active; + SafeFlag active{ false }; SafeNumeric<float> setplay{ -1.0 }; Vector<AudioFrame> volume_vector; @@ -64,7 +64,8 @@ private: float volume_db = 0.0; float pitch_scale = 1.0; bool autoplay = false; - StringName default_bus = "Master"; + StringName default_bus = SNAME("Master"); + int max_polyphony = 1; void _set_playing(bool p_enable); bool _is_active() const; @@ -119,6 +120,9 @@ public: void set_stream_paused(bool p_pause); bool get_stream_paused() const; + void set_max_polyphony(int p_max_polyphony); + int get_max_polyphony() const; + Ref<AudioStreamPlayback> get_stream_playback(); AudioStreamPlayer2D(); diff --git a/scene/2d/listener_2d.cpp b/scene/2d/listener_2d.cpp new file mode 100644 index 0000000000..444f05f2b1 --- /dev/null +++ b/scene/2d/listener_2d.cpp @@ -0,0 +1,112 @@ +/*************************************************************************/ +/* listener_2d.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "listener_2d.h" + +bool Listener2D::_set(const StringName &p_name, const Variant &p_value) { + if (p_name == "current") { + if (p_value.operator bool()) { + make_current(); + } else { + clear_current(); + } + } else { + return false; + } + return true; +} + +bool Listener2D::_get(const StringName &p_name, Variant &r_ret) const { + if (p_name == "current") { + if (is_inside_tree() && get_tree()->is_node_being_edited(this)) { + r_ret = current; + } else { + r_ret = is_current(); + } + } else { + return false; + } + return true; +} + +void Listener2D::_get_property_list(List<PropertyInfo> *p_list) const { + p_list->push_back(PropertyInfo(Variant::BOOL, "current")); +} + +void Listener2D::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + if (!get_tree()->is_node_being_edited(this) && current) { + make_current(); + } + } break; + case NOTIFICATION_EXIT_TREE: { + if (!get_tree()->is_node_being_edited(this)) { + if (is_current()) { + clear_current(); + current = true; // Keep it true. + } else { + current = false; + } + } + } break; + } +} + +void Listener2D::make_current() { + current = true; + if (!is_inside_tree()) { + return; + } + get_viewport()->_listener_2d_set(this); +} + +void Listener2D::clear_current() { + current = false; + if (!is_inside_tree()) { + return; + } + get_viewport()->_listener_2d_remove(this); +} + +bool Listener2D::is_current() const { + if (is_inside_tree() && !get_tree()->is_node_being_edited(this)) { + return get_viewport()->get_listener_2d() == this; + } else { + return current; + } + return false; +} + +void Listener2D::_bind_methods() { + ClassDB::bind_method(D_METHOD("make_current"), &Listener2D::make_current); + ClassDB::bind_method(D_METHOD("clear_current"), &Listener2D::clear_current); + ClassDB::bind_method(D_METHOD("is_current"), &Listener2D::is_current); +} diff --git a/modules/stb_vorbis/register_types.cpp b/scene/2d/listener_2d.h index bdb1cf69cf..0289a8087d 100644 --- a/modules/stb_vorbis/register_types.cpp +++ b/scene/2d/listener_2d.h @@ -1,5 +1,5 @@ /*************************************************************************/ -/* register_types.cpp */ +/* listener_2d.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,25 +28,34 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#include "register_types.h" +#ifndef LISTENER_2D_H +#define LISTENER_2D_H -#include "audio_stream_ogg_vorbis.h" +#include "scene/2d/node_2d.h" +#include "scene/main/window.h" -#ifdef TOOLS_ENABLED -#include "core/config/engine.h" -#include "resource_importer_ogg_vorbis.h" -#endif +class Listener2D : public Node2D { + GDCLASS(Listener2D, Node2D); -void register_stb_vorbis_types() { -#ifdef TOOLS_ENABLED - if (Engine::get_singleton()->is_editor_hint()) { - Ref<ResourceImporterOGGVorbis> ogg_import; - ogg_import.instantiate(); - ResourceFormatImporter::get_singleton()->add_importer(ogg_import); - } -#endif - GDREGISTER_CLASS(AudioStreamOGGVorbis); -} +private: + bool current = false; + + friend class Viewport; + +protected: + void _update_listener(); -void unregister_stb_vorbis_types() { -} + bool _set(const StringName &p_name, const Variant &p_value); + bool _get(const StringName &p_name, Variant &r_ret) const; + void _get_property_list(List<PropertyInfo> *p_list) const; + void _notification(int p_what); + + static void _bind_methods(); + +public: + void make_current(); + void clear_current(); + bool is_current() const; +}; + +#endif diff --git a/scene/2d/physics_body_2d.cpp b/scene/2d/physics_body_2d.cpp index 3518d434c3..30f012c7aa 100644 --- a/scene/2d/physics_body_2d.cpp +++ b/scene/2d/physics_body_2d.cpp @@ -165,21 +165,13 @@ void PhysicsBody2D::remove_collision_exception_with(Node *p_node) { void StaticBody2D::set_constant_linear_velocity(const Vector2 &p_vel) { constant_linear_velocity = p_vel; - if (kinematic_motion) { - _update_kinematic_motion(); - } else { - PhysicsServer2D::get_singleton()->body_set_state(get_rid(), PhysicsServer2D::BODY_STATE_LINEAR_VELOCITY, constant_linear_velocity); - } + PhysicsServer2D::get_singleton()->body_set_state(get_rid(), PhysicsServer2D::BODY_STATE_LINEAR_VELOCITY, constant_linear_velocity); } void StaticBody2D::set_constant_angular_velocity(real_t p_vel) { constant_angular_velocity = p_vel; - if (kinematic_motion) { - _update_kinematic_motion(); - } else { - PhysicsServer2D::get_singleton()->body_set_state(get_rid(), PhysicsServer2D::BODY_STATE_ANGULAR_VELOCITY, constant_angular_velocity); - } + PhysicsServer2D::get_singleton()->body_set_state(get_rid(), PhysicsServer2D::BODY_STATE_ANGULAR_VELOCITY, constant_angular_velocity); } Vector2 StaticBody2D::get_constant_linear_velocity() const { @@ -209,62 +201,72 @@ Ref<PhysicsMaterial> StaticBody2D::get_physics_material_override() const { return physics_material_override; } -void StaticBody2D::set_kinematic_motion_enabled(bool p_enabled) { - if (p_enabled == kinematic_motion) { - return; - } - - kinematic_motion = p_enabled; +void StaticBody2D::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_constant_linear_velocity", "vel"), &StaticBody2D::set_constant_linear_velocity); + ClassDB::bind_method(D_METHOD("set_constant_angular_velocity", "vel"), &StaticBody2D::set_constant_angular_velocity); + ClassDB::bind_method(D_METHOD("get_constant_linear_velocity"), &StaticBody2D::get_constant_linear_velocity); + ClassDB::bind_method(D_METHOD("get_constant_angular_velocity"), &StaticBody2D::get_constant_angular_velocity); - if (kinematic_motion) { - set_body_mode(PhysicsServer2D::BODY_MODE_KINEMATIC); - } else { - set_body_mode(PhysicsServer2D::BODY_MODE_STATIC); - } + ClassDB::bind_method(D_METHOD("set_physics_material_override", "physics_material_override"), &StaticBody2D::set_physics_material_override); + ClassDB::bind_method(D_METHOD("get_physics_material_override"), &StaticBody2D::get_physics_material_override); -#ifdef TOOLS_ENABLED - if (Engine::get_singleton()->is_editor_hint()) { - update_configuration_warnings(); - return; - } -#endif + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "physics_material_override", PROPERTY_HINT_RESOURCE_TYPE, "PhysicsMaterial"), "set_physics_material_override", "get_physics_material_override"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "constant_linear_velocity"), "set_constant_linear_velocity", "get_constant_linear_velocity"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "constant_angular_velocity"), "set_constant_angular_velocity", "get_constant_angular_velocity"); +} - _update_kinematic_motion(); +StaticBody2D::StaticBody2D(PhysicsServer2D::BodyMode p_mode) : + PhysicsBody2D(p_mode) { } -bool StaticBody2D::is_kinematic_motion_enabled() const { - return kinematic_motion; +void StaticBody2D::_reload_physics_characteristics() { + if (physics_material_override.is_null()) { + PhysicsServer2D::get_singleton()->body_set_param(get_rid(), PhysicsServer2D::BODY_PARAM_BOUNCE, 0); + PhysicsServer2D::get_singleton()->body_set_param(get_rid(), PhysicsServer2D::BODY_PARAM_FRICTION, 1); + } else { + PhysicsServer2D::get_singleton()->body_set_param(get_rid(), PhysicsServer2D::BODY_PARAM_BOUNCE, physics_material_override->computed_bounce()); + PhysicsServer2D::get_singleton()->body_set_param(get_rid(), PhysicsServer2D::BODY_PARAM_FRICTION, physics_material_override->computed_friction()); + } } -void StaticBody2D::set_sync_to_physics(bool p_enable) { +void AnimatableBody2D::set_sync_to_physics(bool p_enable) { if (sync_to_physics == p_enable) { return; } sync_to_physics = p_enable; + _update_kinematic_motion(); +} + +bool AnimatableBody2D::is_sync_to_physics_enabled() const { + return sync_to_physics; +} + +void AnimatableBody2D::_update_kinematic_motion() { #ifdef TOOLS_ENABLED if (Engine::get_singleton()->is_editor_hint()) { - update_configuration_warnings(); return; } #endif - if (kinematic_motion) { - _update_kinematic_motion(); + if (sync_to_physics) { + PhysicsServer2D::get_singleton()->body_set_state_sync_callback(get_rid(), this, _body_state_changed_callback); + set_only_update_transform_changes(true); + set_notify_local_transform(true); + } else { + PhysicsServer2D::get_singleton()->body_set_state_sync_callback(get_rid(), nullptr, nullptr); + set_only_update_transform_changes(false); + set_notify_local_transform(false); } } -bool StaticBody2D::is_sync_to_physics_enabled() const { - return sync_to_physics; -} - -void StaticBody2D::_body_state_changed_callback(void *p_instance, PhysicsDirectBodyState2D *p_state) { - StaticBody2D *body = (StaticBody2D *)p_instance; +void AnimatableBody2D::_body_state_changed_callback(void *p_instance, PhysicsDirectBodyState2D *p_state) { + AnimatableBody2D *body = (AnimatableBody2D *)p_instance; body->_body_state_changed(p_state); } -void StaticBody2D::_body_state_changed(PhysicsDirectBodyState2D *p_state) { +void AnimatableBody2D::_body_state_changed(PhysicsDirectBodyState2D *p_state) { if (!sync_to_physics) { return; } @@ -275,17 +277,7 @@ void StaticBody2D::_body_state_changed(PhysicsDirectBodyState2D *p_state) { set_notify_local_transform(true); } -TypedArray<String> StaticBody2D::get_configuration_warnings() const { - TypedArray<String> warnings = PhysicsBody2D::get_configuration_warnings(); - - if (sync_to_physics && !kinematic_motion) { - warnings.push_back(TTR("Sync to physics works only when kinematic motion is enabled.")); - } - - return warnings; -} - -void StaticBody2D::_notification(int p_what) { +void AnimatableBody2D::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: { last_valid_transform = get_global_transform(); @@ -295,10 +287,6 @@ void StaticBody2D::_notification(int p_what) { // Used by sync to physics, send the new transform to the physics... Transform2D new_transform = get_global_transform(); - double delta_time = get_physics_process_delta_time(); - new_transform.translate(constant_linear_velocity * delta_time); - new_transform.set_rotation(new_transform.get_rotation() + constant_angular_velocity * delta_time); - PhysicsServer2D::get_singleton()->body_set_state(get_rid(), PhysicsServer2D::BODY_STATE_TRANSFORM, new_transform); // ... but then revert changes. @@ -306,98 +294,19 @@ void StaticBody2D::_notification(int p_what) { set_global_transform(last_valid_transform); set_notify_local_transform(true); } break; - - case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { -#ifdef TOOLS_ENABLED - if (Engine::get_singleton()->is_editor_hint()) { - return; - } -#endif - - ERR_FAIL_COND(!kinematic_motion); - - Transform2D new_transform = get_global_transform(); - - double delta_time = get_physics_process_delta_time(); - new_transform.translate(constant_linear_velocity * delta_time); - new_transform.set_rotation(new_transform.get_rotation() + constant_angular_velocity * delta_time); - - if (sync_to_physics) { - // Propagate transform change to node. - set_global_transform(new_transform); - } else { - PhysicsServer2D::get_singleton()->body_set_state(get_rid(), PhysicsServer2D::BODY_STATE_TRANSFORM, new_transform); - - // Propagate transform change to node. - set_block_transform_notify(true); - set_global_transform(new_transform); - set_block_transform_notify(false); - } - } break; } } -void StaticBody2D::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_constant_linear_velocity", "vel"), &StaticBody2D::set_constant_linear_velocity); - ClassDB::bind_method(D_METHOD("set_constant_angular_velocity", "vel"), &StaticBody2D::set_constant_angular_velocity); - ClassDB::bind_method(D_METHOD("get_constant_linear_velocity"), &StaticBody2D::get_constant_linear_velocity); - ClassDB::bind_method(D_METHOD("get_constant_angular_velocity"), &StaticBody2D::get_constant_angular_velocity); - - ClassDB::bind_method(D_METHOD("set_kinematic_motion_enabled", "enabled"), &StaticBody2D::set_kinematic_motion_enabled); - ClassDB::bind_method(D_METHOD("is_kinematic_motion_enabled"), &StaticBody2D::is_kinematic_motion_enabled); - - ClassDB::bind_method(D_METHOD("set_physics_material_override", "physics_material_override"), &StaticBody2D::set_physics_material_override); - ClassDB::bind_method(D_METHOD("get_physics_material_override"), &StaticBody2D::get_physics_material_override); - - ClassDB::bind_method(D_METHOD("set_sync_to_physics", "enable"), &StaticBody2D::set_sync_to_physics); - ClassDB::bind_method(D_METHOD("is_sync_to_physics_enabled"), &StaticBody2D::is_sync_to_physics_enabled); +void AnimatableBody2D::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_sync_to_physics", "enable"), &AnimatableBody2D::set_sync_to_physics); + ClassDB::bind_method(D_METHOD("is_sync_to_physics_enabled"), &AnimatableBody2D::is_sync_to_physics_enabled); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "physics_material_override", PROPERTY_HINT_RESOURCE_TYPE, "PhysicsMaterial"), "set_physics_material_override", "get_physics_material_override"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "constant_linear_velocity"), "set_constant_linear_velocity", "get_constant_linear_velocity"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "constant_angular_velocity"), "set_constant_angular_velocity", "get_constant_angular_velocity"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "kinematic_motion"), "set_kinematic_motion_enabled", "is_kinematic_motion_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "sync_to_physics"), "set_sync_to_physics", "is_sync_to_physics_enabled"); } -StaticBody2D::StaticBody2D() : - PhysicsBody2D(PhysicsServer2D::BODY_MODE_STATIC) { -} - -void StaticBody2D::_reload_physics_characteristics() { - if (physics_material_override.is_null()) { - PhysicsServer2D::get_singleton()->body_set_param(get_rid(), PhysicsServer2D::BODY_PARAM_BOUNCE, 0); - PhysicsServer2D::get_singleton()->body_set_param(get_rid(), PhysicsServer2D::BODY_PARAM_FRICTION, 1); - } else { - PhysicsServer2D::get_singleton()->body_set_param(get_rid(), PhysicsServer2D::BODY_PARAM_BOUNCE, physics_material_override->computed_bounce()); - PhysicsServer2D::get_singleton()->body_set_param(get_rid(), PhysicsServer2D::BODY_PARAM_FRICTION, physics_material_override->computed_friction()); - } -} - -void StaticBody2D::_update_kinematic_motion() { -#ifdef TOOLS_ENABLED - if (Engine::get_singleton()->is_editor_hint()) { - return; - } -#endif - - if (kinematic_motion && sync_to_physics) { - PhysicsServer2D::get_singleton()->body_set_state_sync_callback(get_rid(), this, _body_state_changed_callback); - set_only_update_transform_changes(true); - set_notify_local_transform(true); - } else { - PhysicsServer2D::get_singleton()->body_set_state_sync_callback(get_rid(), nullptr, nullptr); - set_only_update_transform_changes(false); - set_notify_local_transform(false); - } - - bool needs_physics_process = false; - if (kinematic_motion) { - if (!Math::is_zero_approx(constant_angular_velocity) || !constant_linear_velocity.is_equal_approx(Vector2())) { - needs_physics_process = true; - } - } - - set_physics_process_internal(needs_physics_process); +AnimatableBody2D::AnimatableBody2D() : + StaticBody2D(PhysicsServer2D::BODY_MODE_KINEMATIC) { + _update_kinematic_motion(); } void RigidBody2D::_body_enter_tree(ObjectID p_id) { @@ -651,11 +560,53 @@ real_t RigidBody2D::get_mass() const { void RigidBody2D::set_inertia(real_t p_inertia) { ERR_FAIL_COND(p_inertia < 0); - PhysicsServer2D::get_singleton()->body_set_param(get_rid(), PhysicsServer2D::BODY_PARAM_INERTIA, p_inertia); + inertia = p_inertia; + PhysicsServer2D::get_singleton()->body_set_param(get_rid(), PhysicsServer2D::BODY_PARAM_INERTIA, inertia); } real_t RigidBody2D::get_inertia() const { - return PhysicsServer2D::get_singleton()->body_get_param(get_rid(), PhysicsServer2D::BODY_PARAM_INERTIA); + return inertia; +} + +void RigidBody2D::set_center_of_mass_mode(CenterOfMassMode p_mode) { + if (center_of_mass_mode == p_mode) { + return; + } + + center_of_mass_mode = p_mode; + + switch (center_of_mass_mode) { + case CENTER_OF_MASS_MODE_AUTO: { + center_of_mass = Vector2(); + PhysicsServer2D::get_singleton()->body_reset_mass_properties(get_rid()); + if (inertia != 0.0) { + PhysicsServer2D::get_singleton()->body_set_param(get_rid(), PhysicsServer2D::BODY_PARAM_INERTIA, inertia); + } + } break; + + case CENTER_OF_MASS_MODE_CUSTOM: { + PhysicsServer2D::get_singleton()->body_set_param(get_rid(), PhysicsServer2D::BODY_PARAM_CENTER_OF_MASS, center_of_mass); + } break; + } +} + +RigidBody2D::CenterOfMassMode RigidBody2D::get_center_of_mass_mode() const { + return center_of_mass_mode; +} + +void RigidBody2D::set_center_of_mass(const Vector2 &p_center_of_mass) { + if (center_of_mass == p_center_of_mass) { + return; + } + + ERR_FAIL_COND(center_of_mass_mode != CENTER_OF_MASS_MODE_CUSTOM); + center_of_mass = p_center_of_mass; + + PhysicsServer2D::get_singleton()->body_set_param(get_rid(), PhysicsServer2D::BODY_PARAM_CENTER_OF_MASS, center_of_mass); +} + +const Vector2 &RigidBody2D::get_center_of_mass() const { + return center_of_mass; } void RigidBody2D::set_physics_material_override(const Ref<PhysicsMaterial> &p_physics_material_override) { @@ -909,6 +860,12 @@ void RigidBody2D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_inertia"), &RigidBody2D::get_inertia); ClassDB::bind_method(D_METHOD("set_inertia", "inertia"), &RigidBody2D::set_inertia); + ClassDB::bind_method(D_METHOD("set_center_of_mass_mode", "mode"), &RigidBody2D::set_center_of_mass_mode); + ClassDB::bind_method(D_METHOD("get_center_of_mass_mode"), &RigidBody2D::get_center_of_mass_mode); + + ClassDB::bind_method(D_METHOD("set_center_of_mass", "center_of_mass"), &RigidBody2D::set_center_of_mass); + ClassDB::bind_method(D_METHOD("get_center_of_mass"), &RigidBody2D::get_center_of_mass); + ClassDB::bind_method(D_METHOD("set_physics_material_override", "physics_material_override"), &RigidBody2D::set_physics_material_override); ClassDB::bind_method(D_METHOD("get_physics_material_override"), &RigidBody2D::get_physics_material_override); @@ -965,8 +922,11 @@ void RigidBody2D::_bind_methods() { GDVIRTUAL_BIND(_integrate_forces, "state"); ADD_PROPERTY(PropertyInfo(Variant::INT, "mode", PROPERTY_HINT_ENUM, "Dynamic,Static,DynamicLocked,Kinematic"), "set_mode", "get_mode"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "mass", PROPERTY_HINT_RANGE, "0.01,65535,0.01,exp"), "set_mass", "get_mass"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "inertia", PROPERTY_HINT_RANGE, "0.01,65535,0.01,exp", PROPERTY_USAGE_NONE), "set_inertia", "get_inertia"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "mass", PROPERTY_HINT_RANGE, "0.01,1000,0.01,or_greater,exp"), "set_mass", "get_mass"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "inertia", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater,exp"), "set_inertia", "get_inertia"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "center_of_mass_mode", PROPERTY_HINT_ENUM, "Auto,Custom", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), "set_center_of_mass_mode", "get_center_of_mass_mode"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "center_of_mass", PROPERTY_HINT_RANGE, "-10,10,0.01,or_lesser,or_greater"), "set_center_of_mass", "get_center_of_mass"); + ADD_LINKED_PROPERTY("center_of_mass_mode", "center_of_mass"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "physics_material_override", PROPERTY_HINT_RESOURCE_TYPE, "PhysicsMaterial"), "set_physics_material_override", "get_physics_material_override"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "gravity_scale", PROPERTY_HINT_RANGE, "-128,128,0.01"), "set_gravity_scale", "get_gravity_scale"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "custom_integrator"), "set_use_custom_integrator", "is_using_custom_integrator"); @@ -996,11 +956,22 @@ void RigidBody2D::_bind_methods() { BIND_ENUM_CONSTANT(MODE_DYNAMIC_LOCKED); BIND_ENUM_CONSTANT(MODE_KINEMATIC); + BIND_ENUM_CONSTANT(CENTER_OF_MASS_MODE_AUTO); + BIND_ENUM_CONSTANT(CENTER_OF_MASS_MODE_CUSTOM); + BIND_ENUM_CONSTANT(CCD_MODE_DISABLED); BIND_ENUM_CONSTANT(CCD_MODE_CAST_RAY); BIND_ENUM_CONSTANT(CCD_MODE_CAST_SHAPE); } +void RigidBody2D::_validate_property(PropertyInfo &property) const { + if (center_of_mass_mode != CENTER_OF_MASS_MODE_CUSTOM) { + if (property.name == "center_of_mass") { + property.usage = PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL; + } + } +} + RigidBody2D::RigidBody2D() : PhysicsBody2D(PhysicsServer2D::BODY_MODE_DYNAMIC) { PhysicsServer2D::get_singleton()->body_set_state_sync_callback(get_rid(), this, _body_state_changed_callback); diff --git a/scene/2d/physics_body_2d.h b/scene/2d/physics_body_2d.h index 1bf53ea53c..1d6437a3ad 100644 --- a/scene/2d/physics_body_2d.h +++ b/scene/2d/physics_body_2d.h @@ -63,21 +63,13 @@ public: class StaticBody2D : public PhysicsBody2D { GDCLASS(StaticBody2D, PhysicsBody2D); +private: Vector2 constant_linear_velocity; real_t constant_angular_velocity = 0.0; Ref<PhysicsMaterial> physics_material_override; - bool kinematic_motion = false; - bool sync_to_physics = false; - - Transform2D last_valid_transform; - - static void _body_state_changed_callback(void *p_instance, PhysicsDirectBodyState2D *p_state); - void _body_state_changed(PhysicsDirectBodyState2D *p_state); - protected: - void _notification(int p_what); static void _bind_methods(); public: @@ -90,17 +82,32 @@ public: Vector2 get_constant_linear_velocity() const; real_t get_constant_angular_velocity() const; - virtual TypedArray<String> get_configuration_warnings() const override; - - StaticBody2D(); + StaticBody2D(PhysicsServer2D::BodyMode p_mode = PhysicsServer2D::BODY_MODE_STATIC); private: void _reload_physics_characteristics(); +}; - void _update_kinematic_motion(); +class AnimatableBody2D : public StaticBody2D { + GDCLASS(AnimatableBody2D, StaticBody2D); + +private: + bool sync_to_physics = false; + + Transform2D last_valid_transform; - void set_kinematic_motion_enabled(bool p_enabled); - bool is_kinematic_motion_enabled() const; + static void _body_state_changed_callback(void *p_instance, PhysicsDirectBodyState2D *p_state); + void _body_state_changed(PhysicsDirectBodyState2D *p_state); + +protected: + void _notification(int p_what); + static void _bind_methods(); + +public: + AnimatableBody2D(); + +private: + void _update_kinematic_motion(); void set_sync_to_physics(bool p_enable); bool is_sync_to_physics_enabled() const; @@ -117,6 +124,11 @@ public: MODE_KINEMATIC, }; + enum CenterOfMassMode { + CENTER_OF_MASS_MODE_AUTO, + CENTER_OF_MASS_MODE_CUSTOM, + }; + enum CCDMode { CCD_MODE_DISABLED, CCD_MODE_CAST_RAY, @@ -128,6 +140,10 @@ private: Mode mode = MODE_DYNAMIC; real_t mass = 1.0; + real_t inertia = 0.0; + CenterOfMassMode center_of_mass_mode = CENTER_OF_MASS_MODE_AUTO; + Vector2 center_of_mass; + Ref<PhysicsMaterial> physics_material_override; real_t gravity_scale = 1.0; real_t linear_damp = -1.0; @@ -191,6 +207,8 @@ protected: void _notification(int p_what); static void _bind_methods(); + virtual void _validate_property(PropertyInfo &property) const override; + GDVIRTUAL1(_integrate_forces, PhysicsDirectBodyState2D *) public: @@ -203,6 +221,12 @@ public: void set_inertia(real_t p_inertia); real_t get_inertia() const; + void set_center_of_mass_mode(CenterOfMassMode p_mode); + CenterOfMassMode get_center_of_mass_mode() const; + + void set_center_of_mass(const Vector2 &p_center_of_mass); + const Vector2 &get_center_of_mass() const; + void set_physics_material_override(const Ref<PhysicsMaterial> &p_physics_material_override); Ref<PhysicsMaterial> get_physics_material_override() const; @@ -267,6 +291,7 @@ private: }; VARIANT_ENUM_CAST(RigidBody2D::Mode); +VARIANT_ENUM_CAST(RigidBody2D::CenterOfMassMode); VARIANT_ENUM_CAST(RigidBody2D::CCDMode); class CharacterBody2D : public PhysicsBody2D { diff --git a/scene/2d/tile_map.cpp b/scene/2d/tile_map.cpp index 13f1d258a8..0eb424b32c 100644 --- a/scene/2d/tile_map.cpp +++ b/scene/2d/tile_map.cpp @@ -236,6 +236,8 @@ Vector2i TileMap::transform_coords_layout(Vector2i p_coords, TileSet::TileOffset } int TileMap::get_effective_quadrant_size(int p_layer) const { + ERR_FAIL_INDEX_V(p_layer, (int)layers.size(), 1); + // When using YSort, the quadrant size is reduced to 1 to have one CanvasItem per quadrant if (is_y_sort_enabled() && layers[p_layer].y_sort_enabled) { return 1; @@ -314,16 +316,38 @@ int TileMap::get_quadrant_size() const { return quadrant_size; } -void TileMap::set_layers_count(int p_layers_count) { - ERR_FAIL_COND(p_layers_count < 0); - _clear_internals(); +int TileMap::get_layers_count() const { + return layers.size(); +} + +void TileMap::add_layer(int p_to_pos) { + if (p_to_pos < 0) { + p_to_pos = layers.size(); + } + + ERR_FAIL_INDEX(p_to_pos, (int)layers.size() + 1); + + layers.insert(p_to_pos, TileMapLayer()); + _recreate_internals(); + notify_property_list_changed(); + + emit_signal(SNAME("changed")); + + update_configuration_warnings(); +} + +void TileMap::move_layer(int p_layer, int p_to_pos) { + ERR_FAIL_INDEX(p_layer, (int)layers.size()); + ERR_FAIL_INDEX(p_to_pos, (int)layers.size() + 1); - layers.resize(p_layers_count); + TileMapLayer tl = layers[p_layer]; + layers.insert(p_to_pos, tl); + layers.remove(p_to_pos < p_layer ? p_layer + 1 : p_layer); _recreate_internals(); notify_property_list_changed(); - if (selected_layer >= p_layers_count) { - selected_layer = -1; + if (selected_layer == p_layer) { + selected_layer = p_to_pos < p_layer ? p_to_pos - 1 : p_to_pos; } emit_signal(SNAME("changed")); @@ -331,8 +355,20 @@ void TileMap::set_layers_count(int p_layers_count) { update_configuration_warnings(); } -int TileMap::get_layers_count() const { - return layers.size(); +void TileMap::remove_layer(int p_layer) { + ERR_FAIL_INDEX(p_layer, (int)layers.size()); + + layers.remove(p_layer); + _recreate_internals(); + notify_property_list_changed(); + + if (selected_layer >= p_layer) { + selected_layer -= 1; + } + + emit_signal(SNAME("changed")); + + update_configuration_warnings(); } void TileMap::set_layer_name(int p_layer, String p_name) { @@ -781,7 +817,7 @@ void TileMap::_rendering_update_dirty_quadrants(SelfList<TileMapQuadrant>::List if (atlas_source) { // Get the tile data. TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(c.get_atlas_coords(), c.alternative_tile)); - Ref<ShaderMaterial> mat = tile_data->tile_get_material(); + Ref<ShaderMaterial> mat = tile_data->get_material(); int z_index = tile_data->get_z_index(); // Quandrant pos. @@ -1147,7 +1183,7 @@ void TileMap::_physics_create_quadrant(TileMapQuadrant *p_quadrant) { PhysicsServer2D::get_singleton()->body_set_space(body, space); Transform2D xform; - xform.set_origin(map_to_world(p_quadrant->coords * get_effective_quadrant_size(layer))); + xform.set_origin(map_to_world(p_quadrant->coords * get_effective_quadrant_size(p_quadrant->layer))); xform = global_transform * xform; PhysicsServer2D::get_singleton()->body_set_state(body, PhysicsServer2D::BODY_STATE_TRANSFORM, xform); } @@ -2896,8 +2932,10 @@ void TileMap::_bind_methods() { ClassDB::bind_method(D_METHOD("set_quadrant_size", "size"), &TileMap::set_quadrant_size); ClassDB::bind_method(D_METHOD("get_quadrant_size"), &TileMap::get_quadrant_size); - ClassDB::bind_method(D_METHOD("set_layers_count", "layers_count"), &TileMap::set_layers_count); ClassDB::bind_method(D_METHOD("get_layers_count"), &TileMap::get_layers_count); + ClassDB::bind_method(D_METHOD("add_layer", "to_position"), &TileMap::add_layer); + ClassDB::bind_method(D_METHOD("move_layer", "layer", "to_position"), &TileMap::move_layer); + ClassDB::bind_method(D_METHOD("remove_layer", "layer"), &TileMap::remove_layer); ClassDB::bind_method(D_METHOD("set_layer_name", "layer", "name"), &TileMap::set_layer_name); ClassDB::bind_method(D_METHOD("get_layer_name", "layer"), &TileMap::get_layer_name); ClassDB::bind_method(D_METHOD("set_layer_enabled", "layer", "enabled"), &TileMap::set_layer_enabled); @@ -2907,7 +2945,7 @@ void TileMap::_bind_methods() { ClassDB::bind_method(D_METHOD("set_layer_y_sort_origin", "layer", "y_sort_origin"), &TileMap::set_layer_y_sort_origin); ClassDB::bind_method(D_METHOD("get_layer_y_sort_origin", "layer"), &TileMap::get_layer_y_sort_origin); ClassDB::bind_method(D_METHOD("set_layer_z_index", "layer", "z_index"), &TileMap::set_layer_z_index); - ClassDB::bind_method(D_METHOD("get_layer_z_indexd", "layer"), &TileMap::get_layer_z_index); + ClassDB::bind_method(D_METHOD("get_layer_z_index", "layer"), &TileMap::get_layer_z_index); ClassDB::bind_method(D_METHOD("set_collision_visibility_mode", "collision_visibility_mode"), &TileMap::set_collision_visibility_mode); ClassDB::bind_method(D_METHOD("get_collision_visibility_mode"), &TileMap::get_collision_visibility_mode); @@ -2942,9 +2980,7 @@ void TileMap::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "collision_visibility_mode", PROPERTY_HINT_ENUM, "Default,Force Show,Force Hide"), "set_collision_visibility_mode", "get_collision_visibility_mode"); ADD_PROPERTY(PropertyInfo(Variant::INT, "navigation_visibility_mode", PROPERTY_HINT_ENUM, "Default,Force Show,Force Hide"), "set_navigation_visibility_mode", "get_navigation_visibility_mode"); - ADD_GROUP("Layers", ""); - ADD_PROPERTY(PropertyInfo(Variant::INT, "layers_count"), "set_layers_count", "get_layers_count"); - ADD_PROPERTY_DEFAULT("layers_count", 1); + ADD_ARRAY("layers", "layer_"); ADD_PROPERTY_DEFAULT("format", FORMAT_1); diff --git a/scene/2d/tile_map.h b/scene/2d/tile_map.h index 4e2d76a7b7..3ac50fc7cc 100644 --- a/scene/2d/tile_map.h +++ b/scene/2d/tile_map.h @@ -308,8 +308,10 @@ public: static void draw_tile(RID p_canvas_item, Vector2i p_position, const Ref<TileSet> p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile, Color p_modulation = Color(1.0, 1.0, 1.0, 1.0)); // Layers management. - void set_layers_count(int p_layers_count); int get_layers_count() const; + void add_layer(int p_to_pos); + void move_layer(int p_layer, int p_to_pos); + void remove_layer(int p_layer); void set_layer_name(int p_layer, String p_name); String get_layer_name(int p_layer) const; void set_layer_enabled(int p_layer, bool p_visible); diff --git a/scene/3d/audio_stream_player_3d.cpp b/scene/3d/audio_stream_player_3d.cpp index 16d5b9da3e..907c6cd03a 100644 --- a/scene/3d/audio_stream_player_3d.cpp +++ b/scene/3d/audio_stream_player_3d.cpp @@ -271,28 +271,45 @@ void AudioStreamPlayer3D::_notification(int p_what) { if (p_what == NOTIFICATION_INTERNAL_PHYSICS_PROCESS) { //update anything related to position first, if possible of course - - if (!stream_playback.is_valid()) { - return; + Vector<AudioFrame> volume_vector; + if (setplay.get() > 0 || (active.is_set() && last_mix_count != AudioServer::get_singleton()->get_mix_count())) { + volume_vector = _update_panning(); } - //start playing if requested - if (setplay.get() >= 0) { - Vector<AudioFrame> volume_vector = _update_panning(); - AudioServer::get_singleton()->start_playback_stream(stream_playback, _get_actual_bus(), volume_vector, setplay.get()); + if (setplay.get() >= 0 && stream.is_valid()) { active.set(); + Ref<AudioStreamPlayback> new_playback = stream->instance_playback(); + ERR_FAIL_COND_MSG(new_playback.is_null(), "Failed to instantiate playback."); + Map<StringName, Vector<AudioFrame>> bus_map; + bus_map[_get_actual_bus()] = volume_vector; + AudioServer::get_singleton()->start_playback_stream(new_playback, bus_map, setplay.get(), linear_attenuation, attenuation_filter_cutoff_hz, actual_pitch_scale); + stream_playbacks.push_back(new_playback); setplay.set(-1); } - if (active.is_set() && last_mix_count != AudioServer::get_singleton()->get_mix_count()) { - _update_panning(); - last_mix_count = AudioServer::get_singleton()->get_mix_count(); + if (!stream_playbacks.is_empty() && active.is_set()) { + // Stop playing if no longer active. + Vector<Ref<AudioStreamPlayback>> playbacks_to_remove; + for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { + if (playback.is_valid() && !AudioServer::get_singleton()->is_playback_active(playback) && !AudioServer::get_singleton()->is_playback_paused(playback)) { + emit_signal(SNAME("finished")); + playbacks_to_remove.push_back(playback); + } + } + // Now go through and remove playbacks that have finished. Removing elements from a Vector in a range based for is asking for trouble. + for (Ref<AudioStreamPlayback> &playback : playbacks_to_remove) { + stream_playbacks.erase(playback); + } + if (!playbacks_to_remove.is_empty() && stream_playbacks.is_empty()) { + // This node is no longer actively playing audio. + active.clear(); + set_physics_process_internal(false); + } } - // Stop playing if no longer active. - if (!active.is_set()) { - set_physics_process_internal(false); - emit_signal(SNAME("finished")); + while (stream_playbacks.size() > max_polyphony) { + AudioServer::get_singleton()->stop_playback_stream(stream_playbacks[0]); + stream_playbacks.remove(0); } } } @@ -330,9 +347,6 @@ Area3D *AudioStreamPlayer3D::_get_overriding_area() { } StringName AudioStreamPlayer3D::_get_actual_bus() { - if (!stream_playback.is_valid()) { - return SNAME("Master"); - } Area3D *overriding_area = _get_overriding_area(); if (overriding_area && overriding_area->is_overriding_audio_bus() && !overriding_area->is_using_reverb_bus()) { return overriding_area->get_audio_bus_name(); @@ -347,7 +361,9 @@ Vector<AudioFrame> AudioStreamPlayer3D::_update_panning() { frame = AudioFrame(0, 0); } - ERR_FAIL_COND_V(stream_playback.is_null(), output_volume_vector); + if (!active.is_set() || stream.is_null()) { + return output_volume_vector; + } Vector3 linear_velocity; @@ -422,7 +438,10 @@ Vector<AudioFrame> AudioStreamPlayer3D::_update_panning() { } } - AudioServer::get_singleton()->set_playback_highshelf_params(stream_playback, Math::db2linear(db_att), attenuation_filter_cutoff_hz); + linear_attenuation = Math::db2linear(db_att); + for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { + AudioServer::get_singleton()->set_playback_highshelf_params(playback, linear_attenuation, attenuation_filter_cutoff_hz); + } //TODO: The lower the second parameter (tightness) the more the sound will "enclose" the listener (more undirected / playing from // speakers not facing the source) - this could be made distance dependent. _calc_output_vol(local_pos.normalized(), 4.0, output_volume_vector); @@ -447,7 +466,10 @@ Vector<AudioFrame> AudioStreamPlayer3D::_update_panning() { } else { bus_volumes[bus] = output_volume_vector; } - AudioServer::get_singleton()->set_playback_bus_volumes_linear(stream_playback, bus_volumes); + + for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { + AudioServer::get_singleton()->set_playback_bus_volumes_linear(playback, bus_volumes); + } if (doppler_tracking != DOPPLER_TRACKING_DISABLED) { Vector3 listener_velocity; @@ -458,9 +480,7 @@ Vector<AudioFrame> AudioStreamPlayer3D::_update_panning() { Vector3 local_velocity = listener_node->get_global_transform().orthonormalized().basis.xform_inv(linear_velocity - listener_velocity); - if (local_velocity == Vector3()) { - AudioServer::get_singleton()->set_playback_pitch_scale(stream_playback, pitch_scale); - } else { + if (local_velocity != Vector3()) { float approaching = local_pos.normalized().dot(local_velocity.normalized()); float velocity = local_velocity.length(); float speed_of_sound = 343.0; @@ -468,34 +488,23 @@ Vector<AudioFrame> AudioStreamPlayer3D::_update_panning() { float doppler_pitch_scale = pitch_scale * speed_of_sound / (speed_of_sound + velocity * approaching); doppler_pitch_scale = CLAMP(doppler_pitch_scale, (1 / 8.0), 8.0); //avoid crazy stuff - AudioServer::get_singleton()->set_playback_pitch_scale(stream_playback, doppler_pitch_scale); + actual_pitch_scale = doppler_pitch_scale; + } else { + actual_pitch_scale = pitch_scale; } } else { - AudioServer::get_singleton()->set_playback_pitch_scale(stream_playback, pitch_scale); + actual_pitch_scale = pitch_scale; + } + for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { + AudioServer::get_singleton()->set_playback_pitch_scale(playback, actual_pitch_scale); } } return output_volume_vector; } void AudioStreamPlayer3D::set_stream(Ref<AudioStream> p_stream) { - if (stream_playback.is_valid()) { - stop(); - stream_playback.unref(); - stream.unref(); - } - - if (p_stream.is_valid()) { - stream_playback = p_stream->instance_playback(); - if (stream_playback.is_valid()) { - stream = p_stream; - } else { - stream.unref(); - } - } - - if (p_stream.is_valid() && stream_playback.is_null()) { - stream.unref(); - } + stop(); + stream = p_stream; } Ref<AudioStream> AudioStreamPlayer3D::get_stream() const { @@ -536,40 +545,47 @@ float AudioStreamPlayer3D::get_pitch_scale() const { } void AudioStreamPlayer3D::play(float p_from_pos) { - if (stream_playback.is_valid()) { - setplay.set(p_from_pos); - set_physics_process_internal(true); + if (stream.is_null()) { + return; + } + ERR_FAIL_COND_MSG(!is_inside_tree(), "Playback can only happen when a node is inside the scene tree"); + if (stream->is_monophonic() && is_playing()) { + stop(); } + setplay.set(p_from_pos); + active.set(); + set_physics_process_internal(true); } void AudioStreamPlayer3D::seek(float p_seconds) { - if (stream_playback.is_valid() && active.is_set()) { - play(p_seconds); - } + stop(); + play(p_seconds); } void AudioStreamPlayer3D::stop() { - if (stream_playback.is_valid()) { - active.clear(); - AudioServer::get_singleton()->stop_playback_stream(stream_playback); - set_physics_process_internal(false); - setplay.set(-1); + setplay.set(-1); + for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { + AudioServer::get_singleton()->stop_playback_stream(playback); } + stream_playbacks.clear(); + active.clear(); + set_physics_process_internal(false); } bool AudioStreamPlayer3D::is_playing() const { - if (stream_playback.is_valid()) { - return active.is_set() || setplay.get() >= 0; + for (const Ref<AudioStreamPlayback> &playback : stream_playbacks) { + if (AudioServer::get_singleton()->is_playback_active(playback)) { + return true; + } } - return false; } float AudioStreamPlayer3D::get_playback_position() { - if (stream_playback.is_valid()) { - return AudioServer::get_singleton()->get_playback_position(stream_playback); + // Return the playback position of the most recently started playback stream. + if (!stream_playbacks.is_empty()) { + return AudioServer::get_singleton()->get_playback_position(stream_playbacks[stream_playbacks.size() - 1]); } - return 0; } @@ -729,20 +745,35 @@ AudioStreamPlayer3D::DopplerTracking AudioStreamPlayer3D::get_doppler_tracking() } void AudioStreamPlayer3D::set_stream_paused(bool p_pause) { - if (stream_playback.is_valid()) { - AudioServer::get_singleton()->set_playback_paused(stream_playback, p_pause); + // TODO this does not have perfect recall, fix that maybe? If there are zero playbacks registered with the AudioServer, this bool isn't persisted. + for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { + AudioServer::get_singleton()->set_playback_paused(playback, p_pause); } } bool AudioStreamPlayer3D::get_stream_paused() const { - if (stream_playback.is_valid()) { - return AudioServer::get_singleton()->is_playback_paused(stream_playback); + // There's currently no way to pause some playback streams but not others. Check the first and don't bother looking at the rest. + if (!stream_playbacks.is_empty()) { + return AudioServer::get_singleton()->is_playback_paused(stream_playbacks[0]); } return false; } Ref<AudioStreamPlayback> AudioStreamPlayer3D::get_stream_playback() { - return stream_playback; + if (!stream_playbacks.is_empty()) { + return stream_playbacks[stream_playbacks.size() - 1]; + } + return nullptr; +} + +void AudioStreamPlayer3D::set_max_polyphony(int p_max_polyphony) { + if (p_max_polyphony > 0) { + max_polyphony = p_max_polyphony; + } +} + +int AudioStreamPlayer3D::get_max_polyphony() const { + return max_polyphony; } void AudioStreamPlayer3D::_bind_methods() { @@ -810,6 +841,9 @@ void AudioStreamPlayer3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_stream_paused", "pause"), &AudioStreamPlayer3D::set_stream_paused); ClassDB::bind_method(D_METHOD("get_stream_paused"), &AudioStreamPlayer3D::get_stream_paused); + ClassDB::bind_method(D_METHOD("set_max_polyphony", "max_polyphony"), &AudioStreamPlayer3D::set_max_polyphony); + ClassDB::bind_method(D_METHOD("get_max_polyphony"), &AudioStreamPlayer3D::get_max_polyphony); + ClassDB::bind_method(D_METHOD("get_stream_playback"), &AudioStreamPlayer3D::get_stream_playback); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "stream", PROPERTY_HINT_RESOURCE_TYPE, "AudioStream"), "set_stream", "get_stream"); @@ -823,6 +857,7 @@ void AudioStreamPlayer3D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "stream_paused", PROPERTY_HINT_NONE, ""), "set_stream_paused", "get_stream_paused"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "max_distance", PROPERTY_HINT_RANGE, "0,4096,1,or_greater,exp"), "set_max_distance", "get_max_distance"); ADD_PROPERTY(PropertyInfo(Variant::INT, "out_of_range_mode", PROPERTY_HINT_ENUM, "Mix,Pause"), "set_out_of_range_mode", "get_out_of_range_mode"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "max_polyphony", PROPERTY_HINT_NONE, ""), "set_max_polyphony", "get_max_polyphony"); ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "bus", PROPERTY_HINT_ENUM, ""), "set_bus", "get_bus"); ADD_PROPERTY(PropertyInfo(Variant::INT, "area_mask", PROPERTY_HINT_LAYERS_2D_PHYSICS), "set_area_mask", "get_area_mask"); ADD_GROUP("Emission Angle", "emission_angle"); diff --git a/scene/3d/audio_stream_player_3d.h b/scene/3d/audio_stream_player_3d.h index f915abad2b..abd5a841b2 100644 --- a/scene/3d/audio_stream_player_3d.h +++ b/scene/3d/audio_stream_player_3d.h @@ -31,6 +31,7 @@ #ifndef AUDIO_STREAM_PLAYER_3D_H #define AUDIO_STREAM_PLAYER_3D_H +#include "core/os/mutex.h" #include "scene/3d/area_3d.h" #include "scene/3d/node_3d.h" #include "scene/3d/velocity_tracker_3d.h" @@ -68,10 +69,10 @@ private: }; - Ref<AudioStreamPlayback> stream_playback; + Vector<Ref<AudioStreamPlayback>> stream_playbacks; Ref<AudioStream> stream; - SafeFlag active; + SafeFlag active{ false }; SafeNumeric<float> setplay{ -1.0 }; AttenuationModel attenuation_model = ATTENUATION_INVERSE_DISTANCE; @@ -79,8 +80,11 @@ private: float unit_size = 10.0; float max_db = 3.0; float pitch_scale = 1.0; + // Internally used to take doppler tracking into account. + float actual_pitch_scale = 1.0; bool autoplay = false; - StringName bus = "Master"; + StringName bus = SNAME("Master"); + int max_polyphony = 1; uint64_t last_mix_count = -1; @@ -106,6 +110,8 @@ private: float attenuation_filter_cutoff_hz = 5000.0; float attenuation_filter_db = -24.0; + float linear_attenuation = 0; + float max_distance = 0.0; Ref<VelocityTracker3D> velocity_tracker; @@ -146,6 +152,9 @@ public: void set_bus(const StringName &p_bus); StringName get_bus() const; + void set_max_polyphony(int p_max_polyphony); + int get_max_polyphony() const; + void set_autoplay(bool p_enable); bool is_autoplay_enabled(); diff --git a/scene/3d/physics_body_3d.cpp b/scene/3d/physics_body_3d.cpp index 0356994cdb..00c6664e65 100644 --- a/scene/3d/physics_body_3d.cpp +++ b/scene/3d/physics_body_3d.cpp @@ -223,72 +223,98 @@ Ref<PhysicsMaterial> StaticBody3D::get_physics_material_override() const { return physics_material_override; } -void StaticBody3D::set_kinematic_motion_enabled(bool p_enabled) { - if (p_enabled == kinematic_motion) { - return; - } +void StaticBody3D::set_constant_linear_velocity(const Vector3 &p_vel) { + constant_linear_velocity = p_vel; - kinematic_motion = p_enabled; + PhysicsServer3D::get_singleton()->body_set_state(get_rid(), PhysicsServer3D::BODY_STATE_LINEAR_VELOCITY, constant_linear_velocity); +} - if (kinematic_motion) { - set_body_mode(PhysicsServer3D::BODY_MODE_KINEMATIC); - } else { - set_body_mode(PhysicsServer3D::BODY_MODE_STATIC); - } +void StaticBody3D::set_constant_angular_velocity(const Vector3 &p_vel) { + constant_angular_velocity = p_vel; -#ifdef TOOLS_ENABLED - if (Engine::get_singleton()->is_editor_hint()) { - update_configuration_warnings(); - return; - } -#endif + PhysicsServer3D::get_singleton()->body_set_state(get_rid(), PhysicsServer3D::BODY_STATE_ANGULAR_VELOCITY, constant_angular_velocity); +} - _update_kinematic_motion(); +Vector3 StaticBody3D::get_constant_linear_velocity() const { + return constant_linear_velocity; } -bool StaticBody3D::is_kinematic_motion_enabled() const { - return kinematic_motion; +Vector3 StaticBody3D::get_constant_angular_velocity() const { + return constant_angular_velocity; } -void StaticBody3D::set_constant_linear_velocity(const Vector3 &p_vel) { - constant_linear_velocity = p_vel; +void StaticBody3D::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_constant_linear_velocity", "vel"), &StaticBody3D::set_constant_linear_velocity); + ClassDB::bind_method(D_METHOD("set_constant_angular_velocity", "vel"), &StaticBody3D::set_constant_angular_velocity); + ClassDB::bind_method(D_METHOD("get_constant_linear_velocity"), &StaticBody3D::get_constant_linear_velocity); + ClassDB::bind_method(D_METHOD("get_constant_angular_velocity"), &StaticBody3D::get_constant_angular_velocity); + + ClassDB::bind_method(D_METHOD("set_physics_material_override", "physics_material_override"), &StaticBody3D::set_physics_material_override); + ClassDB::bind_method(D_METHOD("get_physics_material_override"), &StaticBody3D::get_physics_material_override); + + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "physics_material_override", PROPERTY_HINT_RESOURCE_TYPE, "PhysicsMaterial"), "set_physics_material_override", "get_physics_material_override"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "constant_linear_velocity"), "set_constant_linear_velocity", "get_constant_linear_velocity"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "constant_angular_velocity"), "set_constant_angular_velocity", "get_constant_angular_velocity"); +} + +StaticBody3D::StaticBody3D(PhysicsServer3D::BodyMode p_mode) : + PhysicsBody3D(p_mode) { +} - if (kinematic_motion) { - _update_kinematic_motion(); +void StaticBody3D::_reload_physics_characteristics() { + if (physics_material_override.is_null()) { + PhysicsServer3D::get_singleton()->body_set_param(get_rid(), PhysicsServer3D::BODY_PARAM_BOUNCE, 0); + PhysicsServer3D::get_singleton()->body_set_param(get_rid(), PhysicsServer3D::BODY_PARAM_FRICTION, 1); } else { - PhysicsServer3D::get_singleton()->body_set_state(get_rid(), PhysicsServer3D::BODY_STATE_LINEAR_VELOCITY, constant_linear_velocity); + PhysicsServer3D::get_singleton()->body_set_param(get_rid(), PhysicsServer3D::BODY_PARAM_BOUNCE, physics_material_override->computed_bounce()); + PhysicsServer3D::get_singleton()->body_set_param(get_rid(), PhysicsServer3D::BODY_PARAM_FRICTION, physics_material_override->computed_friction()); } } -void StaticBody3D::set_sync_to_physics(bool p_enable) { +Vector3 AnimatableBody3D::get_linear_velocity() const { + return linear_velocity; +} + +Vector3 AnimatableBody3D::get_angular_velocity() const { + return angular_velocity; +} + +void AnimatableBody3D::set_sync_to_physics(bool p_enable) { if (sync_to_physics == p_enable) { return; } sync_to_physics = p_enable; + _update_kinematic_motion(); +} + +bool AnimatableBody3D::is_sync_to_physics_enabled() const { + return sync_to_physics; +} + +void AnimatableBody3D::_update_kinematic_motion() { #ifdef TOOLS_ENABLED if (Engine::get_singleton()->is_editor_hint()) { - update_configuration_warnings(); return; } #endif - if (kinematic_motion) { - _update_kinematic_motion(); + if (sync_to_physics) { + set_only_update_transform_changes(true); + set_notify_local_transform(true); + } else { + set_only_update_transform_changes(false); + set_notify_local_transform(false); } } -bool StaticBody3D::is_sync_to_physics_enabled() const { - return sync_to_physics; -} - -void StaticBody3D::_body_state_changed_callback(void *p_instance, PhysicsDirectBodyState3D *p_state) { - StaticBody3D *body = (StaticBody3D *)p_instance; +void AnimatableBody3D::_body_state_changed_callback(void *p_instance, PhysicsDirectBodyState3D *p_state) { + AnimatableBody3D *body = (AnimatableBody3D *)p_instance; body->_body_state_changed(p_state); } -void StaticBody3D::_body_state_changed(PhysicsDirectBodyState3D *p_state) { +void AnimatableBody3D::_body_state_changed(PhysicsDirectBodyState3D *p_state) { linear_velocity = p_state->get_linear_velocity(); angular_velocity = p_state->get_angular_velocity(); @@ -303,43 +329,7 @@ void StaticBody3D::_body_state_changed(PhysicsDirectBodyState3D *p_state) { _on_transform_changed(); } -TypedArray<String> StaticBody3D::get_configuration_warnings() const { - TypedArray<String> warnings = PhysicsBody3D::get_configuration_warnings(); - - if (sync_to_physics && !kinematic_motion) { - warnings.push_back(TTR("Sync to physics works only when kinematic motion is enabled.")); - } - - return warnings; -} - -void StaticBody3D::set_constant_angular_velocity(const Vector3 &p_vel) { - constant_angular_velocity = p_vel; - - if (kinematic_motion) { - _update_kinematic_motion(); - } else { - PhysicsServer3D::get_singleton()->body_set_state(get_rid(), PhysicsServer3D::BODY_STATE_ANGULAR_VELOCITY, constant_angular_velocity); - } -} - -Vector3 StaticBody3D::get_constant_linear_velocity() const { - return constant_linear_velocity; -} - -Vector3 StaticBody3D::get_constant_angular_velocity() const { - return constant_angular_velocity; -} - -Vector3 StaticBody3D::get_linear_velocity() const { - return linear_velocity; -} - -Vector3 StaticBody3D::get_angular_velocity() const { - return angular_velocity; -} - -void StaticBody3D::_notification(int p_what) { +void AnimatableBody3D::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: { last_valid_transform = get_global_transform(); @@ -349,17 +339,6 @@ void StaticBody3D::_notification(int p_what) { // Used by sync to physics, send the new transform to the physics... Transform3D new_transform = get_global_transform(); - double delta_time = get_physics_process_delta_time(); - new_transform.origin += constant_linear_velocity * delta_time; - - real_t ang_vel = constant_angular_velocity.length(); - if (!Math::is_zero_approx(ang_vel)) { - Vector3 ang_vel_axis = constant_angular_velocity / ang_vel; - Basis rot(ang_vel_axis, ang_vel * delta_time); - new_transform.basis = rot * new_transform.basis; - new_transform.orthonormalize(); - } - PhysicsServer3D::get_singleton()->body_set_state(get_rid(), PhysicsServer3D::BODY_STATE_TRANSFORM, new_transform); // ... but then revert changes. @@ -368,108 +347,21 @@ void StaticBody3D::_notification(int p_what) { set_notify_local_transform(true); _on_transform_changed(); } break; - - case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { -#ifdef TOOLS_ENABLED - if (Engine::get_singleton()->is_editor_hint()) { - return; - } -#endif - - ERR_FAIL_COND(!kinematic_motion); - - Transform3D new_transform = get_global_transform(); - - double delta_time = get_physics_process_delta_time(); - new_transform.origin += constant_linear_velocity * delta_time; - - real_t ang_vel = constant_angular_velocity.length(); - if (!Math::is_zero_approx(ang_vel)) { - Vector3 ang_vel_axis = constant_angular_velocity / ang_vel; - Basis rot(ang_vel_axis, ang_vel * delta_time); - new_transform.basis = rot * new_transform.basis; - new_transform.orthonormalize(); - } - - if (sync_to_physics) { - // Propagate transform change to node. - set_global_transform(new_transform); - } else { - PhysicsServer3D::get_singleton()->body_set_state(get_rid(), PhysicsServer3D::BODY_STATE_TRANSFORM, new_transform); - - // Propagate transform change to node. - set_ignore_transform_notification(true); - set_global_transform(new_transform); - set_ignore_transform_notification(false); - _on_transform_changed(); - } - } break; } } -void StaticBody3D::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_constant_linear_velocity", "vel"), &StaticBody3D::set_constant_linear_velocity); - ClassDB::bind_method(D_METHOD("set_constant_angular_velocity", "vel"), &StaticBody3D::set_constant_angular_velocity); - ClassDB::bind_method(D_METHOD("get_constant_linear_velocity"), &StaticBody3D::get_constant_linear_velocity); - ClassDB::bind_method(D_METHOD("get_constant_angular_velocity"), &StaticBody3D::get_constant_angular_velocity); - - ClassDB::bind_method(D_METHOD("set_kinematic_motion_enabled", "enabled"), &StaticBody3D::set_kinematic_motion_enabled); - ClassDB::bind_method(D_METHOD("is_kinematic_motion_enabled"), &StaticBody3D::is_kinematic_motion_enabled); - - ClassDB::bind_method(D_METHOD("set_physics_material_override", "physics_material_override"), &StaticBody3D::set_physics_material_override); - ClassDB::bind_method(D_METHOD("get_physics_material_override"), &StaticBody3D::get_physics_material_override); - - ClassDB::bind_method(D_METHOD("set_sync_to_physics", "enable"), &StaticBody3D::set_sync_to_physics); - ClassDB::bind_method(D_METHOD("is_sync_to_physics_enabled"), &StaticBody3D::is_sync_to_physics_enabled); +void AnimatableBody3D::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_sync_to_physics", "enable"), &AnimatableBody3D::set_sync_to_physics); + ClassDB::bind_method(D_METHOD("is_sync_to_physics_enabled"), &AnimatableBody3D::is_sync_to_physics_enabled); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "physics_material_override", PROPERTY_HINT_RESOURCE_TYPE, "PhysicsMaterial"), "set_physics_material_override", "get_physics_material_override"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "constant_linear_velocity"), "set_constant_linear_velocity", "get_constant_linear_velocity"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "constant_angular_velocity"), "set_constant_angular_velocity", "get_constant_angular_velocity"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "kinematic_motion"), "set_kinematic_motion_enabled", "is_kinematic_motion_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "sync_to_physics"), "set_sync_to_physics", "is_sync_to_physics_enabled"); } -StaticBody3D::StaticBody3D() : - PhysicsBody3D(PhysicsServer3D::BODY_MODE_STATIC) { -} - -void StaticBody3D::_reload_physics_characteristics() { - if (physics_material_override.is_null()) { - PhysicsServer3D::get_singleton()->body_set_param(get_rid(), PhysicsServer3D::BODY_PARAM_BOUNCE, 0); - PhysicsServer3D::get_singleton()->body_set_param(get_rid(), PhysicsServer3D::BODY_PARAM_FRICTION, 1); - } else { - PhysicsServer3D::get_singleton()->body_set_param(get_rid(), PhysicsServer3D::BODY_PARAM_BOUNCE, physics_material_override->computed_bounce()); - PhysicsServer3D::get_singleton()->body_set_param(get_rid(), PhysicsServer3D::BODY_PARAM_FRICTION, physics_material_override->computed_friction()); - } -} - -void StaticBody3D::_update_kinematic_motion() { -#ifdef TOOLS_ENABLED - if (Engine::get_singleton()->is_editor_hint()) { - return; - } -#endif +AnimatableBody3D::AnimatableBody3D() : + StaticBody3D(PhysicsServer3D::BODY_MODE_KINEMATIC) { + PhysicsServer3D::get_singleton()->body_set_state_sync_callback(get_rid(), this, _body_state_changed_callback); - if (kinematic_motion && sync_to_physics) { - set_only_update_transform_changes(true); - set_notify_local_transform(true); - } else { - set_only_update_transform_changes(false); - set_notify_local_transform(false); - } - - bool needs_physics_process = false; - if (kinematic_motion) { - PhysicsServer3D::get_singleton()->body_set_state_sync_callback(get_rid(), this, &StaticBody3D::_body_state_changed_callback); - - if (!constant_angular_velocity.is_equal_approx(Vector3()) || !constant_linear_velocity.is_equal_approx(Vector3())) { - needs_physics_process = true; - } - } else { - PhysicsServer3D::get_singleton()->body_set_state_sync_callback(get_rid(), nullptr, nullptr); - } - - set_physics_process_internal(needs_physics_process); + _update_kinematic_motion(); } void RigidBody3D::_body_enter_tree(ObjectID p_id) { @@ -739,6 +631,60 @@ real_t RigidBody3D::get_mass() const { return mass; } +void RigidBody3D::set_inertia(const Vector3 &p_inertia) { + ERR_FAIL_COND(p_inertia.x < 0); + ERR_FAIL_COND(p_inertia.y < 0); + ERR_FAIL_COND(p_inertia.z < 0); + + inertia = p_inertia; + PhysicsServer3D::get_singleton()->body_set_param(get_rid(), PhysicsServer3D::BODY_PARAM_INERTIA, inertia); +} + +const Vector3 &RigidBody3D::get_inertia() const { + return inertia; +} + +void RigidBody3D::set_center_of_mass_mode(CenterOfMassMode p_mode) { + if (center_of_mass_mode == p_mode) { + return; + } + + center_of_mass_mode = p_mode; + + switch (center_of_mass_mode) { + case CENTER_OF_MASS_MODE_AUTO: { + center_of_mass = Vector3(); + PhysicsServer3D::get_singleton()->body_reset_mass_properties(get_rid()); + if (inertia != Vector3()) { + PhysicsServer3D::get_singleton()->body_set_param(get_rid(), PhysicsServer3D::BODY_PARAM_INERTIA, inertia); + } + } break; + + case CENTER_OF_MASS_MODE_CUSTOM: { + PhysicsServer3D::get_singleton()->body_set_param(get_rid(), PhysicsServer3D::BODY_PARAM_CENTER_OF_MASS, center_of_mass); + } break; + } +} + +RigidBody3D::CenterOfMassMode RigidBody3D::get_center_of_mass_mode() const { + return center_of_mass_mode; +} + +void RigidBody3D::set_center_of_mass(const Vector3 &p_center_of_mass) { + if (center_of_mass == p_center_of_mass) { + return; + } + + ERR_FAIL_COND(center_of_mass_mode != CENTER_OF_MASS_MODE_CUSTOM); + center_of_mass = p_center_of_mass; + + PhysicsServer3D::get_singleton()->body_set_param(get_rid(), PhysicsServer3D::BODY_PARAM_CENTER_OF_MASS, center_of_mass); +} + +const Vector3 &RigidBody3D::get_center_of_mass() const { + return center_of_mass; +} + void RigidBody3D::set_physics_material_override(const Ref<PhysicsMaterial> &p_physics_material_override) { if (physics_material_override.is_valid()) { if (physics_material_override->is_connected(CoreStringNames::get_singleton()->changed, callable_mp(this, &RigidBody3D::_reload_physics_characteristics))) { @@ -959,6 +905,15 @@ void RigidBody3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_mass", "mass"), &RigidBody3D::set_mass); ClassDB::bind_method(D_METHOD("get_mass"), &RigidBody3D::get_mass); + ClassDB::bind_method(D_METHOD("set_inertia", "inertia"), &RigidBody3D::set_inertia); + ClassDB::bind_method(D_METHOD("get_inertia"), &RigidBody3D::get_inertia); + + ClassDB::bind_method(D_METHOD("set_center_of_mass_mode", "mode"), &RigidBody3D::set_center_of_mass_mode); + ClassDB::bind_method(D_METHOD("get_center_of_mass_mode"), &RigidBody3D::get_center_of_mass_mode); + + ClassDB::bind_method(D_METHOD("set_center_of_mass", "center_of_mass"), &RigidBody3D::set_center_of_mass); + ClassDB::bind_method(D_METHOD("get_center_of_mass"), &RigidBody3D::get_center_of_mass); + ClassDB::bind_method(D_METHOD("set_physics_material_override", "physics_material_override"), &RigidBody3D::set_physics_material_override); ClassDB::bind_method(D_METHOD("get_physics_material_override"), &RigidBody3D::get_physics_material_override); @@ -1012,7 +967,11 @@ void RigidBody3D::_bind_methods() { GDVIRTUAL_BIND(_integrate_forces, "state"); ADD_PROPERTY(PropertyInfo(Variant::INT, "mode", PROPERTY_HINT_ENUM, "Dynamic,Static,DynamicLocked,Kinematic"), "set_mode", "get_mode"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "mass", PROPERTY_HINT_RANGE, "0.01,65535,0.01,exp"), "set_mass", "get_mass"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "mass", PROPERTY_HINT_RANGE, "0.01,1000,0.01,or_greater,exp"), "set_mass", "get_mass"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "inertia", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater,exp"), "set_inertia", "get_inertia"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "center_of_mass_mode", PROPERTY_HINT_ENUM, "Auto,Custom", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), "set_center_of_mass_mode", "get_center_of_mass_mode"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "center_of_mass", PROPERTY_HINT_RANGE, "-10,10,0.01,or_lesser,or_greater"), "set_center_of_mass", "get_center_of_mass"); + ADD_LINKED_PROPERTY("center_of_mass_mode", "center_of_mass"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "physics_material_override", PROPERTY_HINT_RESOURCE_TYPE, "PhysicsMaterial"), "set_physics_material_override", "get_physics_material_override"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "gravity_scale", PROPERTY_HINT_RANGE, "-128,128,0.01"), "set_gravity_scale", "get_gravity_scale"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "custom_integrator"), "set_use_custom_integrator", "is_using_custom_integrator"); @@ -1038,11 +997,22 @@ void RigidBody3D::_bind_methods() { BIND_ENUM_CONSTANT(MODE_STATIC); BIND_ENUM_CONSTANT(MODE_DYNAMIC_LOCKED); BIND_ENUM_CONSTANT(MODE_KINEMATIC); + + BIND_ENUM_CONSTANT(CENTER_OF_MASS_MODE_AUTO); + BIND_ENUM_CONSTANT(CENTER_OF_MASS_MODE_CUSTOM); +} + +void RigidBody3D::_validate_property(PropertyInfo &property) const { + if (center_of_mass_mode != CENTER_OF_MASS_MODE_CUSTOM) { + if (property.name == "center_of_mass") { + property.usage = PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL; + } + } } RigidBody3D::RigidBody3D() : PhysicsBody3D(PhysicsServer3D::BODY_MODE_DYNAMIC) { - PhysicsServer3D::get_singleton()->body_set_state_sync_callback(get_rid(), this, &RigidBody3D::_body_state_changed_callback); + PhysicsServer3D::get_singleton()->body_set_state_sync_callback(get_rid(), this, _body_state_changed_callback); } RigidBody3D::~RigidBody3D() { @@ -2315,7 +2285,7 @@ void PhysicalBone3D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM3D, "body_offset"), "set_body_offset", "get_body_offset"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "mass", PROPERTY_HINT_RANGE, "0.01,65535,0.01,exp"), "set_mass", "get_mass"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "mass", PROPERTY_HINT_RANGE, "0.01,1000,0.01,or_greater,exp"), "set_mass", "get_mass"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "friction", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_friction", "get_friction"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "bounce", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_bounce", "get_bounce"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "gravity_scale", PROPERTY_HINT_RANGE, "-10,10,0.01"), "set_gravity_scale", "get_gravity_scale"); @@ -2713,7 +2683,7 @@ void PhysicalBone3D::_start_physics_simulation() { set_body_mode(PhysicsServer3D::BODY_MODE_DYNAMIC); PhysicsServer3D::get_singleton()->body_set_collision_layer(get_rid(), get_collision_layer()); PhysicsServer3D::get_singleton()->body_set_collision_mask(get_rid(), get_collision_mask()); - PhysicsServer3D::get_singleton()->body_set_state_sync_callback(get_rid(), this, &PhysicalBone3D::_body_state_changed_callback); + PhysicsServer3D::get_singleton()->body_set_state_sync_callback(get_rid(), this, _body_state_changed_callback); set_as_top_level(true); _internal_simulate_physics = true; } diff --git a/scene/3d/physics_body_3d.h b/scene/3d/physics_body_3d.h index 8c09f77846..8e6463f838 100644 --- a/scene/3d/physics_body_3d.h +++ b/scene/3d/physics_body_3d.h @@ -73,24 +73,13 @@ public: class StaticBody3D : public PhysicsBody3D { GDCLASS(StaticBody3D, PhysicsBody3D); +private: Vector3 constant_linear_velocity; Vector3 constant_angular_velocity; - Vector3 linear_velocity; - Vector3 angular_velocity; - Ref<PhysicsMaterial> physics_material_override; - bool kinematic_motion = false; - bool sync_to_physics = false; - - Transform3D last_valid_transform; - - static void _body_state_changed_callback(void *p_instance, PhysicsDirectBodyState3D *p_state); - void _body_state_changed(PhysicsDirectBodyState3D *p_state); - protected: - void _notification(int p_what); static void _bind_methods(); public: @@ -103,20 +92,38 @@ public: Vector3 get_constant_linear_velocity() const; Vector3 get_constant_angular_velocity() const; - virtual Vector3 get_linear_velocity() const override; - virtual Vector3 get_angular_velocity() const override; + StaticBody3D(PhysicsServer3D::BodyMode p_mode = PhysicsServer3D::BODY_MODE_STATIC); - virtual TypedArray<String> get_configuration_warnings() const override; +private: + void _reload_physics_characteristics(); +}; - StaticBody3D(); +class AnimatableBody3D : public StaticBody3D { + GDCLASS(AnimatableBody3D, StaticBody3D); private: - void _reload_physics_characteristics(); + Vector3 linear_velocity; + Vector3 angular_velocity; - void _update_kinematic_motion(); + bool sync_to_physics = false; - void set_kinematic_motion_enabled(bool p_enabled); - bool is_kinematic_motion_enabled() const; + Transform3D last_valid_transform; + + static void _body_state_changed_callback(void *p_instance, PhysicsDirectBodyState3D *p_state); + void _body_state_changed(PhysicsDirectBodyState3D *p_state); + +protected: + void _notification(int p_what); + static void _bind_methods(); + +public: + virtual Vector3 get_linear_velocity() const override; + virtual Vector3 get_angular_velocity() const override; + + AnimatableBody3D(); + +private: + void _update_kinematic_motion(); void set_sync_to_physics(bool p_enable); bool is_sync_to_physics_enabled() const; @@ -133,6 +140,11 @@ public: MODE_KINEMATIC, }; + enum CenterOfMassMode { + CENTER_OF_MASS_MODE_AUTO, + CENTER_OF_MASS_MODE_CUSTOM, + }; + GDVIRTUAL1(_integrate_forces, PhysicsDirectBodyState3D *) protected: @@ -140,6 +152,10 @@ protected: Mode mode = MODE_DYNAMIC; real_t mass = 1.0; + Vector3 inertia; + CenterOfMassMode center_of_mass_mode = CENTER_OF_MASS_MODE_AUTO; + Vector3 center_of_mass; + Ref<PhysicsMaterial> physics_material_override; Vector3 linear_velocity; @@ -203,6 +219,8 @@ protected: void _notification(int p_what); static void _bind_methods(); + virtual void _validate_property(PropertyInfo &property) const override; + public: void set_mode(Mode p_mode); Mode get_mode() const; @@ -212,6 +230,15 @@ public: virtual real_t get_inverse_mass() const override { return 1.0 / mass; } + void set_inertia(const Vector3 &p_inertia); + const Vector3 &get_inertia() const; + + void set_center_of_mass_mode(CenterOfMassMode p_mode); + CenterOfMassMode get_center_of_mass_mode() const; + + void set_center_of_mass(const Vector3 &p_center_of_mass); + const Vector3 &get_center_of_mass() const; + void set_physics_material_override(const Ref<PhysicsMaterial> &p_physics_material_override); Ref<PhysicsMaterial> get_physics_material_override() const; @@ -272,6 +299,7 @@ private: }; VARIANT_ENUM_CAST(RigidBody3D::Mode); +VARIANT_ENUM_CAST(RigidBody3D::CenterOfMassMode); class KinematicCollision3D; diff --git a/scene/3d/skeleton_3d.cpp b/scene/3d/skeleton_3d.cpp index 94fb49ae81..0f5de621ea 100644 --- a/scene/3d/skeleton_3d.cpp +++ b/scene/3d/skeleton_3d.cpp @@ -896,19 +896,25 @@ Ref<SkinReference> Skeleton3D::register_skin(const Ref<Skin> &p_skin) { int len = bones.size(); // calculate global rests and invert them - Vector<int> bones_to_process = get_parentless_bones(); + LocalVector<int> bones_to_process; + bones_to_process = get_parentless_bones(); while (bones_to_process.size() > 0) { int current_bone_idx = bones_to_process[0]; - bones_to_process.erase(current_bone_idx); const Bone &b = bonesptr[current_bone_idx]; - - // Note: the code below may not work by default. May need to track an integer for the bone pose index order - // in the while loop, instead of using current_bone_idx. - if (b.parent >= 0) { - skin->set_bind_pose(current_bone_idx, skin->get_bind_pose(b.parent) * b.rest); - } else { + bones_to_process.erase(current_bone_idx); + LocalVector<int> child_bones_vector; + child_bones_vector = get_bone_children(current_bone_idx); + int child_bones_size = child_bones_vector.size(); + if (b.parent < 0) { skin->set_bind_pose(current_bone_idx, b.rest); } + for (int i = 0; i < child_bones_size; i++) { + int child_bone_idx = child_bones_vector[i]; + const Bone &cb = bonesptr[child_bone_idx]; + skin->set_bind_pose(child_bone_idx, skin->get_bind_pose(current_bone_idx) * cb.rest); + // Add the bone's children to the list of bones to be processed. + bones_to_process.push_back(child_bones_vector[i]); + } } for (int i = 0; i < len; i++) { diff --git a/scene/audio/audio_stream_player.cpp b/scene/audio/audio_stream_player.cpp index f7b7604fd5..0334ba667b 100644 --- a/scene/audio/audio_stream_player.cpp +++ b/scene/audio/audio_stream_player.cpp @@ -42,17 +42,29 @@ void AudioStreamPlayer::_notification(int p_what) { } if (p_what == NOTIFICATION_INTERNAL_PROCESS) { - if (stream_playback.is_valid() && active.is_set() && !AudioServer::get_singleton()->is_playback_active(stream_playback)) { + Vector<Ref<AudioStreamPlayback>> playbacks_to_remove; + for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { + if (playback.is_valid() && !AudioServer::get_singleton()->is_playback_active(playback) && !AudioServer::get_singleton()->is_playback_paused(playback)) { + emit_signal(SNAME("finished")); + playbacks_to_remove.push_back(playback); + } + } + // Now go through and remove playbacks that have finished. Removing elements from a Vector in a range based for is asking for trouble. + for (Ref<AudioStreamPlayback> &playback : playbacks_to_remove) { + stream_playbacks.erase(playback); + } + if (!playbacks_to_remove.is_empty() && stream_playbacks.is_empty()) { + // This node is no longer actively playing audio. active.clear(); set_process_internal(false); - emit_signal(SNAME("finished")); } } if (p_what == NOTIFICATION_EXIT_TREE) { - if (stream_playback.is_valid()) { - AudioServer::get_singleton()->stop_playback_stream(stream_playback); + for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { + AudioServer::get_singleton()->stop_playback_stream(playback); } + stream_playbacks.clear(); } if (p_what == NOTIFICATION_PAUSED) { @@ -68,24 +80,8 @@ void AudioStreamPlayer::_notification(int p_what) { } void AudioStreamPlayer::set_stream(Ref<AudioStream> p_stream) { - if (stream_playback.is_valid()) { - stop(); - stream_playback.unref(); - stream.unref(); - } - - if (p_stream.is_valid()) { - stream_playback = p_stream->instance_playback(); - if (stream_playback.is_valid()) { - stream = p_stream; - } else { - stream.unref(); - } - } - - if (p_stream.is_valid() && stream_playback.is_null()) { - stream.unref(); - } + stop(); + stream = p_stream; } Ref<AudioStream> AudioStreamPlayer::get_stream() const { @@ -95,7 +91,10 @@ Ref<AudioStream> AudioStreamPlayer::get_stream() const { void AudioStreamPlayer::set_volume_db(float p_volume) { volume_db = p_volume; - AudioServer::get_singleton()->set_playback_all_bus_volumes_linear(stream_playback, _get_volume_vector()); + Vector<AudioFrame> volume_vector = _get_volume_vector(); + for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { + AudioServer::get_singleton()->set_playback_all_bus_volumes_linear(playback, volume_vector); + } } float AudioStreamPlayer::get_volume_db() const { @@ -106,57 +105,83 @@ void AudioStreamPlayer::set_pitch_scale(float p_pitch_scale) { ERR_FAIL_COND(p_pitch_scale <= 0.0); pitch_scale = p_pitch_scale; - AudioServer::get_singleton()->set_playback_pitch_scale(stream_playback, pitch_scale); + for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { + AudioServer::get_singleton()->set_playback_pitch_scale(playback, pitch_scale); + } } float AudioStreamPlayer::get_pitch_scale() const { return pitch_scale; } +void AudioStreamPlayer::set_max_polyphony(int p_max_polyphony) { + if (p_max_polyphony > 0) { + max_polyphony = p_max_polyphony; + } +} + +int AudioStreamPlayer::get_max_polyphony() const { + return max_polyphony; +} + void AudioStreamPlayer::play(float p_from_pos) { - stop(); - if (stream.is_valid()) { - stream_playback = stream->instance_playback(); + if (stream.is_null()) { + return; + } + ERR_FAIL_COND_MSG(!is_inside_tree(), "Playback can only happen when a node is inside the scene tree"); + if (stream->is_monophonic() && is_playing()) { + stop(); } - if (stream_playback.is_valid()) { - AudioServer::get_singleton()->start_playback_stream(stream_playback, bus, _get_volume_vector(), p_from_pos); - active.set(); + Ref<AudioStreamPlayback> stream_playback = stream->instance_playback(); + ERR_FAIL_COND_MSG(stream_playback.is_null(), "Failed to instantiate playback."); + + AudioServer::get_singleton()->start_playback_stream(stream_playback, bus, _get_volume_vector(), p_from_pos); + stream_playbacks.push_back(stream_playback); + active.set(); + set_process_internal(true); + while (stream_playbacks.size() > max_polyphony) { + AudioServer::get_singleton()->stop_playback_stream(stream_playbacks[0]); + stream_playbacks.remove(0); } } void AudioStreamPlayer::seek(float p_seconds) { - if (stream_playback.is_valid() && active.is_set()) { + if (is_playing()) { + stop(); play(p_seconds); } } void AudioStreamPlayer::stop() { - if (stream_playback.is_valid()) { - active.clear(); - AudioServer::get_singleton()->stop_playback_stream(stream_playback); + for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { + AudioServer::get_singleton()->stop_playback_stream(playback); } + stream_playbacks.clear(); + active.clear(); + set_process_internal(false); } bool AudioStreamPlayer::is_playing() const { - if (stream_playback.is_valid()) { - return AudioServer::get_singleton()->is_playback_active(stream_playback); + for (const Ref<AudioStreamPlayback> &playback : stream_playbacks) { + if (AudioServer::get_singleton()->is_playback_active(playback)) { + return true; + } } - return false; } float AudioStreamPlayer::get_playback_position() { - if (stream_playback.is_valid()) { - return AudioServer::get_singleton()->get_playback_position(stream_playback); + // Return the playback position of the most recently started playback stream. + if (!stream_playbacks.is_empty()) { + return AudioServer::get_singleton()->get_playback_position(stream_playbacks[stream_playbacks.size() - 1]); } - return 0; } void AudioStreamPlayer::set_bus(const StringName &p_bus) { bus = p_bus; - if (stream_playback.is_valid()) { - AudioServer::get_singleton()->set_playback_bus_exclusive(stream_playback, p_bus, _get_volume_vector()); + for (const Ref<AudioStreamPlayback> &playback : stream_playbacks) { + AudioServer::get_singleton()->set_playback_bus_exclusive(playback, p_bus, _get_volume_vector()); } } @@ -166,7 +191,7 @@ StringName AudioStreamPlayer::get_bus() const { return bus; } } - return "Master"; + return SNAME("Master"); } void AudioStreamPlayer::set_autoplay(bool p_enable) { @@ -194,22 +219,25 @@ void AudioStreamPlayer::_set_playing(bool p_enable) { } bool AudioStreamPlayer::_is_active() const { - if (stream_playback.is_valid()) { - return AudioServer::get_singleton()->is_playback_active(stream_playback); + for (const Ref<AudioStreamPlayback> &playback : stream_playbacks) { + if (AudioServer::get_singleton()->is_playback_active(playback)) { + return true; + } } return false; } void AudioStreamPlayer::set_stream_paused(bool p_pause) { - // TODO this does not have perfect recall, fix that maybe? If the stream isn't set, we can't persist this bool. - if (stream_playback.is_valid()) { - AudioServer::get_singleton()->set_playback_paused(stream_playback, p_pause); + // TODO this does not have perfect recall, fix that maybe? If there are zero playbacks registered with the AudioServer, this bool isn't persisted. + for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { + AudioServer::get_singleton()->set_playback_paused(playback, p_pause); } } bool AudioStreamPlayer::get_stream_paused() const { - if (stream_playback.is_valid()) { - return AudioServer::get_singleton()->is_playback_paused(stream_playback); + // There's currently no way to pause some playback streams but not others. Check the first and don't bother looking at the rest. + if (!stream_playbacks.is_empty()) { + return AudioServer::get_singleton()->is_playback_paused(stream_playbacks[0]); } return false; } @@ -271,7 +299,10 @@ void AudioStreamPlayer::_bus_layout_changed() { } Ref<AudioStreamPlayback> AudioStreamPlayer::get_stream_playback() { - return stream_playback; + if (!stream_playbacks.is_empty()) { + return stream_playbacks[stream_playbacks.size() - 1]; + } + return nullptr; } void AudioStreamPlayer::_bind_methods() { @@ -306,6 +337,9 @@ void AudioStreamPlayer::_bind_methods() { ClassDB::bind_method(D_METHOD("set_stream_paused", "pause"), &AudioStreamPlayer::set_stream_paused); ClassDB::bind_method(D_METHOD("get_stream_paused"), &AudioStreamPlayer::get_stream_paused); + ClassDB::bind_method(D_METHOD("set_max_polyphony", "max_polyphony"), &AudioStreamPlayer::set_max_polyphony); + ClassDB::bind_method(D_METHOD("get_max_polyphony"), &AudioStreamPlayer::get_max_polyphony); + ClassDB::bind_method(D_METHOD("get_stream_playback"), &AudioStreamPlayer::get_stream_playback); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "stream", PROPERTY_HINT_RESOURCE_TYPE, "AudioStream"), "set_stream", "get_stream"); @@ -315,6 +349,7 @@ void AudioStreamPlayer::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "autoplay"), "set_autoplay", "is_autoplay_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "stream_paused", PROPERTY_HINT_NONE, ""), "set_stream_paused", "get_stream_paused"); ADD_PROPERTY(PropertyInfo(Variant::INT, "mix_target", PROPERTY_HINT_ENUM, "Stereo,Surround,Center"), "set_mix_target", "get_mix_target"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "max_polyphony", PROPERTY_HINT_NONE, ""), "set_max_polyphony", "get_max_polyphony"); ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "bus", PROPERTY_HINT_ENUM, ""), "set_bus", "get_bus"); ADD_SIGNAL(MethodInfo("finished")); diff --git a/scene/audio/audio_stream_player.h b/scene/audio/audio_stream_player.h index 76b6698c9d..7205fce204 100644 --- a/scene/audio/audio_stream_player.h +++ b/scene/audio/audio_stream_player.h @@ -46,7 +46,7 @@ public: }; private: - Ref<AudioStreamPlayback> stream_playback; + Vector<Ref<AudioStreamPlayback>> stream_playbacks; Ref<AudioStream> stream; SafeFlag active; @@ -54,7 +54,8 @@ private: float pitch_scale = 1.0; float volume_db = 0.0; bool autoplay = false; - StringName bus = "Master"; + StringName bus = SNAME("Master"); + int max_polyphony = 1; MixTarget mix_target = MIX_TARGET_STEREO; @@ -85,6 +86,9 @@ public: void set_pitch_scale(float p_pitch_scale); float get_pitch_scale() const; + void set_max_polyphony(int p_max_polyphony); + int get_max_polyphony() const; + void play(float p_from_pos = 0.0); void seek(float p_seconds); void stop(); diff --git a/scene/gui/check_box.cpp b/scene/gui/check_box.cpp index d93107df2d..411fb2e1f0 100644 --- a/scene/gui/check_box.cpp +++ b/scene/gui/check_box.cpp @@ -34,11 +34,13 @@ Size2 CheckBox::get_icon_size() const { Ref<Texture2D> checked = Control::get_theme_icon(SNAME("checked")); - Ref<Texture2D> checked_disabled = Control::get_theme_icon(SNAME("checked_disabled")); Ref<Texture2D> unchecked = Control::get_theme_icon(SNAME("unchecked")); - Ref<Texture2D> unchecked_disabled = Control::get_theme_icon(SNAME("unchecked_disabled")); Ref<Texture2D> radio_checked = Control::get_theme_icon(SNAME("radio_checked")); Ref<Texture2D> radio_unchecked = Control::get_theme_icon(SNAME("radio_unchecked")); + Ref<Texture2D> checked_disabled = Control::get_theme_icon(SNAME("checked_disabled")); + Ref<Texture2D> unchecked_disabled = Control::get_theme_icon(SNAME("unchecked_disabled")); + Ref<Texture2D> radio_checked_disabled = Control::get_theme_icon(SNAME("radio_checked_disabled")); + Ref<Texture2D> radio_unchecked_disabled = Control::get_theme_icon(SNAME("radio_unchecked_disabled")); Size2 tex_size = Size2(0, 0); if (!checked.is_null()) { @@ -53,6 +55,18 @@ Size2 CheckBox::get_icon_size() const { if (!radio_unchecked.is_null()) { tex_size = Size2(MAX(tex_size.width, radio_unchecked->get_width()), MAX(tex_size.height, radio_unchecked->get_height())); } + if (!checked_disabled.is_null()) { + tex_size = Size2(MAX(tex_size.width, checked_disabled->get_width()), MAX(tex_size.height, checked_disabled->get_height())); + } + if (!unchecked_disabled.is_null()) { + tex_size = Size2(MAX(tex_size.width, unchecked_disabled->get_width()), MAX(tex_size.height, unchecked_disabled->get_height())); + } + if (!radio_checked_disabled.is_null()) { + tex_size = Size2(MAX(tex_size.width, radio_checked_disabled->get_width()), MAX(tex_size.height, radio_checked_disabled->get_height())); + } + if (!radio_unchecked_disabled.is_null()) { + tex_size = Size2(MAX(tex_size.width, radio_unchecked_disabled->get_width()), MAX(tex_size.height, radio_unchecked_disabled->get_height())); + } return tex_size; } diff --git a/scene/gui/code_edit.cpp b/scene/gui/code_edit.cpp index ca884f56e4..3beff57027 100644 --- a/scene/gui/code_edit.cpp +++ b/scene/gui/code_edit.cpp @@ -1400,15 +1400,21 @@ void CodeEdit::fold_line(int p_line) { } /* Find the last line to be hidden. */ - int end_line = get_line_count(); + const int line_count = get_line_count() - 1; + int end_line = line_count; int in_comment = is_in_comment(p_line); int in_string = (in_comment == -1) ? is_in_string(p_line) : -1; if (in_string != -1 || in_comment != -1) { end_line = get_delimiter_end_position(p_line, get_line(p_line).size() - 1).y; - /* End line is the same therefore we have a block. */ + /* End line is the same therefore we have a block of single line delimiters. */ if (end_line == p_line) { - for (int i = p_line + 1; i < get_line_count(); i++) { + for (int i = p_line + 1; i <= line_count; i++) { + if (i == line_count) { + end_line = line_count; + break; + } + if ((in_string != -1 && is_in_string(i) == -1) || (in_comment != -1 && is_in_comment(i) == -1)) { end_line = i - 1; break; @@ -1417,14 +1423,27 @@ void CodeEdit::fold_line(int p_line) { } } else { int start_indent = get_indent_level(p_line); - for (int i = p_line + 1; i < get_line_count(); i++) { + for (int i = p_line + 1; i <= line_count; i++) { if (get_line(p_line).strip_edges().size() == 0 || is_in_string(i) != -1 || is_in_comment(i) != -1) { end_line = i; continue; } - if (get_indent_level(i) <= start_indent && get_line(i).strip_edges().size() != 0) { + if (i == line_count) { + /* Do not fold empty last line of script if any */ + end_line = i; + if (get_line(i).strip_edges().size() == 0) { + end_line--; + } + break; + } + + if ((get_indent_level(i) <= start_indent && get_line(i).strip_edges().size() != 0)) { + /* Keep an empty line unfolded if any */ end_line = i - 1; + if (get_line(i - 1).strip_edges().size() == 0 && i - 2 > p_line) { + end_line = i - 2; + } break; } } @@ -1603,7 +1622,8 @@ Point2 CodeEdit::get_delimiter_start_position(int p_line, int p_column) const { } /* Region was found on this line and is not a multiline continuation. */ - if (start_position.x != -1 && start_position.x != get_line(p_line).length() + 1) { + int line_length = get_line(p_line).length(); + if (start_position.x != -1 && line_length > 0 && start_position.x != line_length + 1) { start_position.y = p_line; return start_position; } @@ -1622,7 +1642,8 @@ Point2 CodeEdit::get_delimiter_start_position(int p_line, int p_column) const { start_position.x = delimiter_cache[i].back()->key(); /* Make sure it's not a multiline continuation. */ - if (start_position.x != get_line(i).length() + 1) { + line_length = get_line(i).length(); + if (line_length > 0 && start_position.x != line_length + 1) { break; } } @@ -1937,7 +1958,7 @@ void CodeEdit::confirm_code_completion(bool p_replace) { if (pre_brace_pair == -1 && post_brace_pair == -1 && get_caret_column() > 0 && get_caret_column() < get_line(caret_line).length()) { pre_brace_pair = _get_auto_brace_pair_open_at_pos(caret_line, get_caret_column() + 1); - if (pre_brace_pair == _get_auto_brace_pair_close_at_pos(caret_line, get_caret_column() - 1)) { + if (pre_brace_pair != -1 && pre_brace_pair == _get_auto_brace_pair_close_at_pos(caret_line, get_caret_column() - 1)) { remove_text(caret_line, get_caret_column() - 2, caret_line, get_caret_column()); if (_get_auto_brace_pair_close_at_pos(caret_line, get_caret_column() - 1) != pre_brace_pair) { set_caret_column(get_caret_column() - 1); @@ -2548,7 +2569,10 @@ int CodeEdit::_is_in_delimiter(int p_line, int p_column, DelimiterType p_type) c } void CodeEdit::_add_delimiter(const String &p_start_key, const String &p_end_key, bool p_line_only, DelimiterType p_type) { - if (p_start_key.length() > 0) { + // If we are the editor allow "null" as a valid start key, otherwise users cannot add delimiters via the inspector. + if (!(Engine::get_singleton()->is_editor_hint() && p_start_key == "null")) { + ERR_FAIL_COND_MSG(p_start_key.is_empty(), "delimiter start key cannot be empty"); + for (int i = 0; i < p_start_key.length(); i++) { ERR_FAIL_COND_MSG(!is_symbol(p_start_key[i]), "delimiter must start with a symbol"); } @@ -2613,7 +2637,11 @@ void CodeEdit::_set_delimiters(const TypedArray<String> &p_delimiters, Delimiter _clear_delimiters(p_type); for (int i = 0; i < p_delimiters.size(); i++) { - String key = p_delimiters[i].is_null() ? "" : p_delimiters[i]; + String key = p_delimiters[i]; + + if (key.is_empty()) { + continue; + } const String start_key = key.get_slice(" ", 0); const String end_key = key.get_slice_count(" ") > 1 ? key.get_slice(" ", 1) : String(); @@ -2744,7 +2772,7 @@ void CodeEdit::_filter_code_completion_candidates_impl() { /* If we have a space, previous word might be a keyword. eg "func |". */ } else if (cofs > 0 && line[cofs - 1] == ' ') { int ofs = cofs - 1; - while (ofs >= 0 && line[ofs] == ' ') { + while (ofs > 0 && line[ofs] == ' ') { ofs--; } prev_is_word = _is_char(line[ofs]); diff --git a/scene/gui/code_edit.h b/scene/gui/code_edit.h index 4fbb5194e6..740548d559 100644 --- a/scene/gui/code_edit.h +++ b/scene/gui/code_edit.h @@ -248,7 +248,6 @@ private: void _text_changed(); protected: - void gui_input(const Ref<InputEvent> &p_gui_input) override; void _notification(int p_what); static void _bind_methods(); @@ -265,6 +264,7 @@ protected: public: /* General overrides */ + virtual void gui_input(const Ref<InputEvent> &p_gui_input) override; virtual CursorShape get_cursor_shape(const Point2 &p_pos = Point2i()) const override; /* Indent management */ diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp index 4ac6a58d36..81411d5844 100644 --- a/scene/gui/control.cpp +++ b/scene/gui/control.cpp @@ -30,6 +30,7 @@ #include "control.h" +#include "container.h" #include "core/config/project_settings.h" #include "core/math/geometry_2d.h" #include "core/object/message_queue.h" @@ -168,6 +169,20 @@ Size2 Control::_edit_get_minimum_size() const { } #endif +String Control::properties_managed_by_container[] = { + "offset_left", + "offset_top", + "offset_right", + "offset_bottom", + "anchor_left", + "anchor_top", + "anchor_right", + "anchor_bottom", + "rect_position", + "rect_scale", + "rect_size" +}; + void Control::accept_event() { if (is_inside_tree()) { get_viewport()->_gui_accept_event(); @@ -442,6 +457,20 @@ void Control::_validate_property(PropertyInfo &property) const { property.hint_string = hint_string; } + if (!Object::cast_to<Container>(get_parent())) { + return; + } + // Disable the property if it's managed by the parent container. + bool property_is_managed_by_container = false; + for (unsigned i = 0; i < properties_managed_by_container_count; i++) { + property_is_managed_by_container = properties_managed_by_container[i] == property.name; + if (property_is_managed_by_container) { + break; + } + } + if (property_is_managed_by_container) { + property.usage |= PROPERTY_USAGE_READ_ONLY; + } } Control *Control::get_parent_control() const { diff --git a/scene/gui/control.h b/scene/gui/control.h index 0faa617f8d..9cec5d6e8d 100644 --- a/scene/gui/control.h +++ b/scene/gui/control.h @@ -230,6 +230,9 @@ private: } data; + static constexpr unsigned properties_managed_by_container_count = 11; + static String properties_managed_by_container[properties_managed_by_container_count]; + // used internally Control *_find_control_at_pos(CanvasItem *p_node, const Point2 &p_pos, const Transform2D &p_xform, Transform2D &r_inv_xform); diff --git a/scene/gui/label.cpp b/scene/gui/label.cpp index 3f87003423..5600816b2d 100644 --- a/scene/gui/label.cpp +++ b/scene/gui/label.cpp @@ -227,7 +227,15 @@ void Label::_update_visible() { } } -inline void draw_glyph(const TextServer::Glyph &p_gl, const RID &p_canvas, const Color &p_font_color, const Color &p_font_shadow_color, const Color &p_font_outline_color, const int &p_shadow_outline_size, const int &p_outline_size, const Vector2 &p_ofs, const Vector2 &shadow_ofs) { +inline void draw_glyph(const TextServer::Glyph &p_gl, const RID &p_canvas, const Color &p_font_color, const Vector2 &p_ofs) { + if (p_gl.font_rid != RID()) { + TS->font_draw_glyph(p_gl.font_rid, p_canvas, p_gl.font_size, p_ofs + Vector2(p_gl.x_off, p_gl.y_off), p_gl.index, p_font_color); + } else { + TS->draw_hex_code_box(p_canvas, p_gl.font_size, p_ofs + Vector2(p_gl.x_off, p_gl.y_off), p_gl.index, p_font_color); + } +} + +inline void draw_glyph_outline(const TextServer::Glyph &p_gl, const RID &p_canvas, const Color &p_font_color, const Color &p_font_shadow_color, const Color &p_font_outline_color, const int &p_shadow_outline_size, const int &p_outline_size, const Vector2 &p_ofs, const Vector2 &shadow_ofs) { if (p_gl.font_rid != RID()) { if (p_font_shadow_color.a > 0) { TS->font_draw_glyph(p_gl.font_rid, p_canvas, p_gl.font_size, p_ofs + Vector2(p_gl.x_off, p_gl.y_off) + shadow_ofs, p_gl.index, p_font_shadow_color); @@ -240,9 +248,6 @@ inline void draw_glyph(const TextServer::Glyph &p_gl, const RID &p_canvas, const if (p_font_outline_color.a != 0.0 && p_outline_size > 0) { TS->font_draw_glyph_outline(p_gl.font_rid, p_canvas, p_gl.font_size, p_outline_size, p_ofs + Vector2(p_gl.x_off, p_gl.y_off), p_gl.index, p_font_outline_color); } - TS->font_draw_glyph(p_gl.font_rid, p_canvas, p_gl.font_size, p_ofs + Vector2(p_gl.x_off, p_gl.y_off), p_gl.index, p_font_color); - } else { - TS->draw_hex_code_box(p_canvas, p_gl.font_size, p_ofs + Vector2(p_gl.x_off, p_gl.y_off), p_gl.index, p_font_color); } } @@ -385,12 +390,70 @@ void Label::_notification(int p_what) { int gl_size = visual.size(); TextServer::TrimData trim_data = TS->shaped_text_get_trim_data(lines_rid[i]); + // Draw outline. Note: Do not merge this into the single loop with the main text, to prevent overlaps. + if (font_shadow_color.a > 0 || (font_outline_color.a != 0.0 && outline_size > 0)) { + Vector2 offset = ofs; + // Draw RTL ellipsis string when necessary. + if (rtl && trim_data.ellipsis_pos >= 0) { + for (int gl_idx = trim_data.ellipsis_glyph_buf.size() - 1; gl_idx >= 0; gl_idx--) { + for (int j = 0; j < trim_data.ellipsis_glyph_buf[gl_idx].repeat; j++) { + //Draw glyph outlines and shadow. + draw_glyph_outline(trim_data.ellipsis_glyph_buf[gl_idx], ci, font_color, font_shadow_color, font_outline_color, shadow_outline_size, outline_size, offset, shadow_ofs); + offset.x += trim_data.ellipsis_glyph_buf[gl_idx].advance; + } + } + } + + // Draw main text. + for (int j = 0; j < gl_size; j++) { + for (int k = 0; k < glyphs[j].repeat; k++) { + if (visible_glyphs != -1) { + if ((glyphs[j].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) { + if (glyhps_drawn >= visible_glyphs) { + return; + } + } + } + + // Trim when necessary. + if (trim_data.trim_pos >= 0) { + if (rtl) { + if (j < trim_data.trim_pos && (glyphs[j].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) { + continue; + } + } else { + if (j >= trim_data.trim_pos && (glyphs[j].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) { + break; + } + } + } + + // Draw glyph outlines and shadow. + draw_glyph_outline(glyphs[j], ci, font_color, font_shadow_color, font_outline_color, shadow_outline_size, outline_size, offset, shadow_ofs); + offset.x += glyphs[j].advance; + glyhps_drawn++; + } + } + // Draw LTR ellipsis string when necessary. + if (!rtl && trim_data.ellipsis_pos >= 0) { + for (int gl_idx = 0; gl_idx < trim_data.ellipsis_glyph_buf.size(); gl_idx++) { + for (int j = 0; j < trim_data.ellipsis_glyph_buf[gl_idx].repeat; j++) { + //Draw glyph outlines and shadow. + draw_glyph_outline(trim_data.ellipsis_glyph_buf[gl_idx], ci, font_color, font_shadow_color, font_outline_color, shadow_outline_size, outline_size, offset, shadow_ofs); + offset.x += trim_data.ellipsis_glyph_buf[gl_idx].advance; + } + } + } + } + + // Draw main text. Note: Do not merge this into the single loop with the outline, to prevent overlaps. + // Draw RTL ellipsis string when necessary. if (rtl && trim_data.ellipsis_pos >= 0) { for (int gl_idx = trim_data.ellipsis_glyph_buf.size() - 1; gl_idx >= 0; gl_idx--) { for (int j = 0; j < trim_data.ellipsis_glyph_buf[gl_idx].repeat; j++) { //Draw glyph outlines and shadow. - draw_glyph(trim_data.ellipsis_glyph_buf[gl_idx], ci, font_color, font_shadow_color, font_outline_color, shadow_outline_size, outline_size, ofs, shadow_ofs); + draw_glyph(trim_data.ellipsis_glyph_buf[gl_idx], ci, font_color, ofs); ofs.x += trim_data.ellipsis_glyph_buf[gl_idx].advance; } } @@ -421,7 +484,7 @@ void Label::_notification(int p_what) { } // Draw glyph outlines and shadow. - draw_glyph(glyphs[j], ci, font_color, font_shadow_color, font_outline_color, shadow_outline_size, outline_size, ofs, shadow_ofs); + draw_glyph(glyphs[j], ci, font_color, ofs); ofs.x += glyphs[j].advance; glyhps_drawn++; } @@ -431,7 +494,7 @@ void Label::_notification(int p_what) { for (int gl_idx = 0; gl_idx < trim_data.ellipsis_glyph_buf.size(); gl_idx++) { for (int j = 0; j < trim_data.ellipsis_glyph_buf[gl_idx].repeat; j++) { //Draw glyph outlines and shadow. - draw_glyph(trim_data.ellipsis_glyph_buf[gl_idx], ci, font_color, font_shadow_color, font_outline_color, shadow_outline_size, outline_size, ofs, shadow_ofs); + draw_glyph(trim_data.ellipsis_glyph_buf[gl_idx], ci, font_color, ofs); ofs.x += trim_data.ellipsis_glyph_buf[gl_idx].advance; } } diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp index 3605842224..d9acbeb828 100644 --- a/scene/gui/line_edit.cpp +++ b/scene/gui/line_edit.cpp @@ -674,7 +674,7 @@ void LineEdit::_notification(int p_what) { int y_ofs = style->get_offset().y + (y_area - text_height) / 2; Color selection_color = get_theme_color(SNAME("selection_color")); - Color font_color = is_editable() ? get_theme_color(SNAME("font_color")) : get_theme_color(SNAME("font_uneditable_color")); + Color font_color = get_theme_color(is_editable() ? SNAME("font_color") : SNAME("font_uneditable_color")); Color font_selected_color = get_theme_color(SNAME("font_selected_color")); Color caret_color = get_theme_color(SNAME("caret_color")); diff --git a/scene/gui/link_button.cpp b/scene/gui/link_button.cpp index 419d49bccf..925e6f5b97 100644 --- a/scene/gui/link_button.cpp +++ b/scene/gui/link_button.cpp @@ -41,12 +41,16 @@ void LinkButton::_shape() { } else { text_buf->set_direction((TextServer::Direction)text_direction); } - TS->shaped_text_set_bidi_override(text_buf->get_rid(), structured_text_parser(st_parser, st_args, text)); - text_buf->add_string(text, font, font_size, opentype_features, (language != "") ? language : TranslationServer::get_singleton()->get_tool_locale()); + TS->shaped_text_set_bidi_override(text_buf->get_rid(), structured_text_parser(st_parser, st_args, xl_text)); + text_buf->add_string(xl_text, font, font_size, opentype_features, (language != "") ? language : TranslationServer::get_singleton()->get_tool_locale()); } void LinkButton::set_text(const String &p_text) { + if (text == p_text) { + return; + } text = p_text; + xl_text = atr(text); _shape(); minimum_size_changed(); update(); @@ -141,7 +145,13 @@ Size2 LinkButton::get_minimum_size() const { void LinkButton::_notification(int p_what) { switch (p_what) { - case NOTIFICATION_TRANSLATION_CHANGED: + case NOTIFICATION_TRANSLATION_CHANGED: { + xl_text = atr(text); + _shape(); + + minimum_size_changed(); + update(); + } break; case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: { update(); } break; diff --git a/scene/gui/link_button.h b/scene/gui/link_button.h index 7eaa9f88b6..231543c63c 100644 --- a/scene/gui/link_button.h +++ b/scene/gui/link_button.h @@ -47,6 +47,7 @@ public: private: String text; + String xl_text; Ref<TextLine> text_buf; UnderlineMode underline_mode = UNDERLINE_MODE_ALWAYS; diff --git a/scene/gui/option_button.h b/scene/gui/option_button.h index d846e395ad..953337ecce 100644 --- a/scene/gui/option_button.h +++ b/scene/gui/option_button.h @@ -56,6 +56,10 @@ protected: static void _bind_methods(); public: + // ATTENTION: This is used by the POT generator's scene parser. If the number of properties returned by `_get_items()` ever changes, + // this value should be updated to reflect the new size. + static const int ITEM_PROPERTY_SIZE = 5; + void add_icon_item(const Ref<Texture2D> &p_icon, const String &p_label, int p_id = -1); void add_item(const String &p_label, int p_id = -1); diff --git a/scene/gui/popup_menu.h b/scene/gui/popup_menu.h index 5c427e43bc..428076c6da 100644 --- a/scene/gui/popup_menu.h +++ b/scene/gui/popup_menu.h @@ -144,6 +144,10 @@ protected: static void _bind_methods(); public: + // ATTENTION: This is used by the POT generator's scene parser. If the number of properties returned by `_get_items()` ever changes, + // this value should be updated to reflect the new size. + static const int ITEM_PROPERTY_SIZE = 10; + void add_item(const String &p_label, int p_id = -1, uint32_t p_accel = 0); void add_icon_item(const Ref<Texture2D> &p_icon, const String &p_label, int p_id = -1, uint32_t p_accel = 0); void add_check_item(const String &p_label, int p_id = -1, uint32_t p_accel = 0); diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp index fbc67d8a24..aeadfd78ee 100644 --- a/scene/gui/rich_text_label.cpp +++ b/scene/gui/rich_text_label.cpp @@ -3946,24 +3946,15 @@ float RichTextLabel::get_percent_visible() const { return percent_visible; } -void RichTextLabel::set_effects(const Vector<Variant> &effects) { - custom_effects.clear(); - for (int i = 0; i < effects.size(); i++) { - Ref<RichTextEffect> effect = Ref<RichTextEffect>(effects[i]); - custom_effects.push_back(effect); - } - +void RichTextLabel::set_effects(Array p_effects) { + custom_effects = p_effects; if ((bbcode != "") && use_bbcode) { parse_bbcode(bbcode); } } -Vector<Variant> RichTextLabel::get_effects() { - Vector<Variant> r; - for (int i = 0; i < custom_effects.size(); i++) { - r.push_back(custom_effects[i]); - } - return r; +Array RichTextLabel::get_effects() { + return custom_effects; } void RichTextLabel::install_effect(const Variant effect) { @@ -4279,12 +4270,13 @@ void RichTextLabel::_draw_fbg_boxes(RID p_ci, RID p_rid, Vector2 line_off, Item Ref<RichTextEffect> RichTextLabel::_get_custom_effect_by_code(String p_bbcode_identifier) { for (int i = 0; i < custom_effects.size(); i++) { - if (!custom_effects[i].is_valid()) { + Ref<RichTextEffect> effect = custom_effects[i]; + if (!effect.is_valid()) { continue; } - if (custom_effects[i]->get_bbcode() == p_bbcode_identifier) { - return custom_effects[i]; + if (effect->get_bbcode() == p_bbcode_identifier) { + return effect; } } diff --git a/scene/gui/rich_text_label.h b/scene/gui/rich_text_label.h index ae04a7e684..f25a8bf193 100644 --- a/scene/gui/rich_text_label.h +++ b/scene/gui/rich_text_label.h @@ -363,7 +363,7 @@ private: ItemMeta *meta_hovering = nullptr; Variant current_meta; - Vector<Ref<RichTextEffect>> custom_effects; + Array custom_effects; void _invalidate_current_line(ItemFrame *p_frame); void _validate_line_caches(ItemFrame *p_frame); @@ -577,8 +577,8 @@ public: void set_percent_visible(float p_percent); float get_percent_visible() const; - void set_effects(const Vector<Variant> &effects); - Vector<Variant> get_effects(); + void set_effects(Array p_effects); + Array get_effects(); void install_effect(const Variant effect); diff --git a/scene/gui/scroll_bar.cpp b/scene/gui/scroll_bar.cpp index 08bcb0bdda..4edf373fbf 100644 --- a/scene/gui/scroll_bar.cpp +++ b/scene/gui/scroll_bar.cpp @@ -80,12 +80,16 @@ void ScrollBar::gui_input(const Ref<InputEvent> &p_event) { double total = orientation == VERTICAL ? get_size().height : get_size().width; if (ofs < decr_size) { + decr_active = true; set_value(get_value() - (custom_step >= 0 ? custom_step : get_step())); + update(); return; } if (ofs > total - incr_size) { + incr_active = true; set_value(get_value() + (custom_step >= 0 ? custom_step : get_step())); + update(); return; } @@ -130,6 +134,8 @@ void ScrollBar::gui_input(const Ref<InputEvent> &p_event) { } } else { + incr_active = false; + decr_active = false; drag.active = false; update(); } @@ -215,8 +221,24 @@ void ScrollBar::_notification(int p_what) { if (p_what == NOTIFICATION_DRAW) { RID ci = get_canvas_item(); - Ref<Texture2D> decr = highlight == HIGHLIGHT_DECR ? get_theme_icon(SNAME("decrement_highlight")) : get_theme_icon(SNAME("decrement")); - Ref<Texture2D> incr = highlight == HIGHLIGHT_INCR ? get_theme_icon(SNAME("increment_highlight")) : get_theme_icon(SNAME("increment")); + Ref<Texture2D> decr, incr; + + if (decr_active) { + decr = get_theme_icon(SNAME("decrement_pressed")); + } else if (highlight == HIGHLIGHT_DECR) { + decr = get_theme_icon(SNAME("decrement_highlight")); + } else { + decr = get_theme_icon(SNAME("decrement")); + } + + if (incr_active) { + incr = get_theme_icon(SNAME("increment_pressed")); + } else if (highlight == HIGHLIGHT_INCR) { + incr = get_theme_icon(SNAME("increment_highlight")); + } else { + incr = get_theme_icon(SNAME("increment")); + } + Ref<StyleBox> bg = has_focus() ? get_theme_stylebox(SNAME("scroll_focus")) : get_theme_stylebox(SNAME("scroll")); Ref<StyleBox> grabber; diff --git a/scene/gui/scroll_bar.h b/scene/gui/scroll_bar.h index fbc035397f..574d17ee20 100644 --- a/scene/gui/scroll_bar.h +++ b/scene/gui/scroll_bar.h @@ -51,6 +51,9 @@ class ScrollBar : public Range { HighlightStatus highlight = HIGHLIGHT_NONE; + bool incr_active = false; + bool decr_active = false; + struct Drag { bool active = false; float pos_at_click = 0.0; diff --git a/scene/gui/tab_container.cpp b/scene/gui/tab_container.cpp index 845a2ec6c7..137ce7e96f 100644 --- a/scene/gui/tab_container.cpp +++ b/scene/gui/tab_container.cpp @@ -1212,6 +1212,7 @@ void TabContainer::_bind_methods() { ClassDB::bind_method(D_METHOD("get_tab_icon", "tab_idx"), &TabContainer::get_tab_icon); ClassDB::bind_method(D_METHOD("set_tab_disabled", "tab_idx", "disabled"), &TabContainer::set_tab_disabled); ClassDB::bind_method(D_METHOD("get_tab_disabled", "tab_idx"), &TabContainer::get_tab_disabled); + ClassDB::bind_method(D_METHOD("get_tab_idx_at_point", "point"), &TabContainer::get_tab_idx_at_point); ClassDB::bind_method(D_METHOD("set_popup", "popup"), &TabContainer::set_popup); ClassDB::bind_method(D_METHOD("get_popup"), &TabContainer::get_popup); ClassDB::bind_method(D_METHOD("set_drag_to_rearrange_enabled", "enabled"), &TabContainer::set_drag_to_rearrange_enabled); diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp index e5ba12ec8d..6bcbedfceb 100644 --- a/scene/gui/text_edit.cpp +++ b/scene/gui/text_edit.cpp @@ -37,6 +37,7 @@ #include "core/object/script_language.h" #include "core/os/keyboard.h" #include "core/os/os.h" +#include "core/string/string_builder.h" #include "core/string/translation.h" #include "scene/main/window.h" @@ -78,7 +79,11 @@ void TextEdit::Text::set_font_size(int p_font_size) { } void TextEdit::Text::set_tab_size(int p_tab_size) { + if (tab_size == p_tab_size) { + return; + } tab_size = p_tab_size; + tab_size_dirty = true; } int TextEdit::Text::get_tab_size() const { @@ -118,10 +123,8 @@ int TextEdit::Text::get_line_width(int p_line, int p_wrap_index) const { return text[p_line].data_buf->get_size().x; } -int TextEdit::Text::get_line_height(int p_line, int p_wrap_index) const { - ERR_FAIL_INDEX_V(p_line, text.size(), 0); - - return text[p_line].data_buf->get_line_size(p_wrap_index).y; +int TextEdit::Text::get_line_height() const { + return line_height; } void TextEdit::Text::set_width(float p_width) { @@ -153,6 +156,36 @@ _FORCE_INLINE_ const String &TextEdit::Text::operator[](int p_line) const { return text[p_line].data; } +void TextEdit::Text::_calculate_line_height() { + int height = 0; + for (int i = 0; i < text.size(); i++) { + // Found another line with the same height...nothing to update. + if (text[i].height == line_height) { + height = line_height; + break; + } + height = MAX(height, text[i].height); + } + line_height = height; +} + +void TextEdit::Text::_calculate_max_line_width() { + int width = 0; + for (int i = 0; i < text.size(); i++) { + if (is_hidden(i)) { + continue; + } + + // Found another line with the same width...nothing to update. + if (text[i].width == max_width) { + width = max_width; + break; + } + width = MAX(width, text[i].width); + } + max_width = width; +} + void TextEdit::Text::invalidate_cache(int p_line, int p_column, const String &p_ime_text, const Vector<Vector2i> &p_bidi_override) { ERR_FAIL_INDEX(p_line, text.size()); @@ -182,17 +215,54 @@ void TextEdit::Text::invalidate_cache(int p_line, int p_column, const String &p_ tabs.push_back(font->get_char_size(' ', 0, font_size).width * tab_size); text.write[p_line].data_buf->tab_align(tabs); } + + // Update height. + const int old_height = text.write[p_line].height; + const int wrap_amount = get_line_wrap_amount(p_line); + int height = font->get_height(font_size); + for (int i = 0; i <= wrap_amount; i++) { + height = MAX(height, text[p_line].data_buf->get_line_size(i).y); + } + text.write[p_line].height = height; + + // If this line has shrunk, this may no longer the the tallest line. + if (old_height == line_height && height < line_height) { + _calculate_line_height(); + } else { + line_height = MAX(height, line_height); + } + + // Update width. + const int old_width = text.write[p_line].width; + int width = get_line_width(p_line); + text.write[p_line].width = width; + + // If this line has shrunk, this may no longer the the longest line. + if (old_width == max_width && width < max_width) { + _calculate_max_line_width(); + } else if (!is_hidden(p_line)) { + max_width = MAX(width, max_width); + } } void TextEdit::Text::invalidate_all_lines() { for (int i = 0; i < text.size(); i++) { text.write[i].data_buf->set_width(width); - if (tab_size > 0) { - Vector<float> tabs; - tabs.push_back(font->get_char_size(' ', 0, font_size).width * tab_size); - text.write[i].data_buf->tab_align(tabs); + if (tab_size_dirty) { + if (tab_size > 0) { + Vector<float> tabs; + tabs.push_back(font->get_char_size(' ', 0, font_size).width * tab_size); + text.write[i].data_buf->tab_align(tabs); + } + // Tabs have changes, force width update. + text.write[i].width = get_line_width(i); } } + + if (tab_size_dirty) { + _calculate_max_line_width(); + tab_size_dirty = false; + } } void TextEdit::Text::invalidate_all() { @@ -211,16 +281,8 @@ void TextEdit::Text::clear() { insert(0, "", Vector<Vector2i>()); } -int TextEdit::Text::get_max_width(bool p_exclude_hidden) const { - // Quite some work, but should be fast enough. - - int max = 0; - for (int i = 0; i < text.size(); i++) { - if (!p_exclude_hidden || !is_hidden(i)) { - max = MAX(max, get_line_width(i)); - } - } - return max; +int TextEdit::Text::get_max_width() const { + return max_width; } void TextEdit::Text::set(int p_line, const String &p_text, const Vector<Vector2i> &p_bidi_override) { @@ -243,7 +305,20 @@ void TextEdit::Text::insert(int p_at, const String &p_text, const Vector<Vector2 } void TextEdit::Text::remove(int p_at) { + int height = text[p_at].height; + int width = text[p_at].width; + text.remove(p_at); + + // If this is the tallest line, we need to get the next tallest. + if (height == line_height) { + _calculate_line_height(); + } + + // If this is the longest line, we need to get the next longest. + if (width == max_width) { + _calculate_max_line_width(); + } } void TextEdit::Text::add_gutter(int p_at) { @@ -293,7 +368,7 @@ void TextEdit::_notification(int p_what) { case NOTIFICATION_VISIBILITY_CHANGED: { if (is_visible()) { call_deferred(SNAME("_update_scrollbars")); - call_deferred(SNAME("_update_wrap_at")); + call_deferred(SNAME("_update_wrap_at_column")); } } break; case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: @@ -557,13 +632,25 @@ void TextEdit::_notification(int p_what) { } int minimap_draw_amount = minimap_visible_lines + get_line_wrap_count(minimap_line + 1); - // draw the minimap - Color viewport_color = (background_color.get_v() < 0.5) ? Color(1, 1, 1, 0.1) : Color(0, 0, 0, 0.1); + // Draw the minimap. + + // Add visual feedback when dragging or hovering the the visible area rectangle. + float viewport_alpha; + if (dragging_minimap) { + viewport_alpha = 0.25; + } else if (hovering_minimap) { + viewport_alpha = 0.175; + } else { + viewport_alpha = 0.1; + } + + const Color viewport_color = (background_color.get_v() < 0.5) ? Color(1, 1, 1, viewport_alpha) : Color(0, 0, 0, viewport_alpha); if (rtl) { RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(size.width - (xmargin_end + 2) - minimap_width, viewport_offset_y, minimap_width, viewport_height), viewport_color); } else { RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2((xmargin_end + 2), viewport_offset_y, minimap_width, viewport_height), viewport_color); } + for (int i = 0; i < minimap_draw_amount; i++) { minimap_line++; @@ -632,8 +719,9 @@ void TextEdit::_notification(int p_what) { int characters = 0; int tabs = 0; for (int j = 0; j < str.length(); j++) { - if (color_map.has(last_wrap_column + j)) { - current_color = color_map[last_wrap_column + j].get("color"); + const Variant *color_data = color_map.getptr(last_wrap_column + j); + if (color_data != nullptr) { + current_color = (color_data->operator Dictionary()).get("color", font_color); if (!editable) { current_color.a = font_readonly_color.a; } @@ -1011,8 +1099,9 @@ void TextEdit::_notification(int p_what) { char_ofs = 0; } for (int j = 0; j < gl_size; j++) { - if (color_map.has(glyphs[j].start)) { - current_color = color_map[glyphs[j].start].get("color"); + const Variant *color_data = color_map.getptr(glyphs[j].start); + if (color_data != nullptr) { + current_color = (color_data->operator Dictionary()).get("color", font_color); if (!editable && current_color.a > font_readonly_color.a) { current_color.a = font_readonly_color.a; } @@ -1549,6 +1638,10 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) { } } + if (draw_minimap && !dragging_selection) { + _update_minimap_hover(); + } + if (v_scroll->get_value() != prev_v_scroll || h_scroll->get_value() != prev_h_scroll) { accept_event(); // Accept event if scroll changed. } @@ -2532,15 +2625,15 @@ void TextEdit::set_text(const String &p_text) { } String TextEdit::get_text() const { - String longthing; - int len = text.size(); - for (int i = 0; i < len; i++) { - longthing += text[i]; - if (i != len - 1) { - longthing += "\n"; + StringBuilder ret_text; + const int text_size = text.size(); + for (int i = 0; i < text_size; i++) { + ret_text += text[i]; + if (i != text_size - 1) { + ret_text += "\n"; } } - return longthing; + return ret_text.as_string(); } int TextEdit::get_line_count() const { @@ -2576,13 +2669,7 @@ int TextEdit::get_line_width(int p_line, int p_wrap_index) const { } int TextEdit::get_line_height() const { - int height = font->get_height(font_size); - for (int i = 0; i < text.size(); i++) { - for (int j = 0; j <= text.get_line_wrap_amount(i); j++) { - height = MAX(height, text.get_line_height(i, j)); - } - } - return height + line_spacing; + return text.get_line_height() + line_spacing; } int TextEdit::get_indent_level(int p_line) const { @@ -4135,6 +4222,9 @@ TextEdit::GutterType TextEdit::get_gutter_type(int p_gutter) const { void TextEdit::set_gutter_width(int p_gutter, int p_width) { ERR_FAIL_INDEX(p_gutter, gutters.size()); + if (gutters[p_gutter].width == p_width) { + return; + } gutters.write[p_gutter].width = p_width; _update_gutter_width(); } @@ -4150,6 +4240,9 @@ int TextEdit::get_total_gutter_width() const { void TextEdit::set_gutter_draw(int p_gutter, bool p_draw) { ERR_FAIL_INDEX(p_gutter, gutters.size()); + if (gutters[p_gutter].draw == p_draw) { + return; + } gutters.write[p_gutter].draw = p_draw; _update_gutter_width(); } @@ -5442,7 +5535,7 @@ void TextEdit::_update_scrollbars() { } int visible_width = size.width - style_normal->get_minimum_size().width; - int total_width = text.get_max_width(true) + vmin.x + gutters_width + gutter_padding; + int total_width = text.get_max_width() + vmin.x + gutters_width + gutter_padding; if (draw_minimap) { total_width += minimap_width; @@ -5644,6 +5737,33 @@ void TextEdit::_scroll_lines_down() { } // Minimap + +void TextEdit::_update_minimap_hover() { + const Point2 mp = get_local_mouse_pos(); + const int xmargin_end = get_size().width - style_normal->get_margin(SIDE_RIGHT); + + const bool hovering_sidebar = mp.x > xmargin_end - minimap_width && mp.x < xmargin_end; + if (!hovering_sidebar) { + if (hovering_minimap) { + // Only redraw if the hovering status changed. + hovering_minimap = false; + update(); + } + + // Return early to avoid running the operations below when not needed. + return; + } + + const int row = get_minimap_line_at_pos(Point2i(mp.x, mp.y)); + + const bool new_hovering_minimap = row >= get_first_visible_line() && row <= get_last_full_visible_line(); + if (new_hovering_minimap != hovering_minimap) { + // Only redraw if the hovering status changed. + hovering_minimap = new_hovering_minimap; + update(); + } +} + void TextEdit::_update_minimap_click() { Point2 mp = get_local_mouse_pos(); @@ -5867,8 +5987,6 @@ void TextEdit::_base_insert_text(int p_line, int p_char, const String &p_text, i text.set_hidden(p_line, false); } - text.invalidate_cache(p_line); - r_end_line = p_line + substrings.size() - 1; r_end_column = text[r_end_line].length() - postinsert_text.length(); @@ -5925,8 +6043,6 @@ void TextEdit::_base_remove_text(int p_from_line, int p_from_column, int p_to_li } text.set(p_from_line, pre_text + post_text, structured_text_parser(st_parser, st_args, pre_text + post_text)); - text.invalidate_cache(p_from_line); - if (!text_changed_dirty && !setting_text) { if (is_inside_tree()) { MessageQueue::get_singleton()->push_call(this, "_text_changed_emit"); @@ -5943,7 +6059,6 @@ TextEdit::TextEdit() { set_default_cursor_shape(CURSOR_IBEAM); text.set_tab_size(text.get_tab_size()); - text.clear(); h_scroll = memnew(HScrollBar); v_scroll = memnew(VScrollBar); diff --git a/scene/gui/text_edit.h b/scene/gui/text_edit.h index ced03e19d0..525464c302 100644 --- a/scene/gui/text_edit.h +++ b/scene/gui/text_edit.h @@ -144,6 +144,8 @@ private: Color background_color = Color(0, 0, 0, 0); bool hidden = false; + int height = 0; + int width = 0; Line() { data_buf.instantiate(); @@ -152,6 +154,7 @@ private: private: bool is_dirty = false; + bool tab_size_dirty = false; mutable Vector<Line> text; Ref<Font> font; @@ -162,11 +165,16 @@ private: TextServer::Direction direction = TextServer::DIRECTION_AUTO; bool draw_control_chars = false; + int line_height = -1; + int max_width = -1; int width = -1; int tab_size = 4; int gutter_count = 0; + void _calculate_line_height(); + void _calculate_max_line_width(); + public: void set_tab_size(int p_tab_size); int get_tab_size() const; @@ -176,9 +184,9 @@ private: void set_direction_and_language(TextServer::Direction p_direction, const String &p_language); void set_draw_control_chars(bool p_draw_control_chars); - int get_line_height(int p_line, int p_wrap_index) const; + int get_line_height() const; int get_line_width(int p_line, int p_wrap_index = -1) const; - int get_max_width(bool p_exclude_hidden = false) const; + int get_max_width() const; void set_width(float p_width); int get_line_wrap_amount(int p_line) const; @@ -187,7 +195,14 @@ private: const Ref<TextParagraph> get_line_data(int p_line) const; void set(int p_line, const String &p_text, const Vector<Vector2i> &p_bidi_override); - void set_hidden(int p_line, bool p_hidden) { text.write[p_line].hidden = p_hidden; } + void set_hidden(int p_line, bool p_hidden) { + text.write[p_line].hidden = p_hidden; + if (!p_hidden && text[p_line].width > max_width) { + max_width = text[p_line].width; + } else if (p_hidden && text[p_line].width == max_width) { + _calculate_max_line_width(); + } + } bool is_hidden(int p_line) const { return text[p_line].hidden; } void insert(int p_at, const String &p_text, const Vector<Vector2i> &p_bidi_override); void remove(int p_at); @@ -446,12 +461,14 @@ private: // minimap scroll bool minimap_clicked = false; + bool hovering_minimap = false; bool dragging_minimap = false; bool can_drag_minimap = false; double minimap_scroll_ratio = 0.0; double minimap_scroll_click_pos = 0.0; + void _update_minimap_hover(); void _update_minimap_click(); void _update_minimap_drag(); @@ -528,7 +545,6 @@ private: protected: void _notification(int p_what); - virtual void gui_input(const Ref<InputEvent> &p_gui_input) override; static void _bind_methods(); @@ -577,6 +593,7 @@ protected: public: /* General overrides. */ + virtual void gui_input(const Ref<InputEvent> &p_gui_input) override; virtual Size2 get_minimum_size() const override; virtual bool is_text_field() const override; virtual CursorShape get_cursor_shape(const Point2 &p_pos = Point2i()) const override; diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp index cb990892ed..c4dfbc0d4e 100644 --- a/scene/gui/tree.cpp +++ b/scene/gui/tree.cpp @@ -2339,13 +2339,22 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int cache.click_type = Cache::CLICK_NONE; return -1; } + + // Make sure the click is correct. + Point2 click_pos = get_global_mouse_position() - get_global_position(); + if (!get_item_at_position(click_pos)) { + pressed_button = -1; + cache.click_type = Cache::CLICK_NONE; + return -1; + } + pressed_button = j; cache.click_type = Cache::CLICK_BUTTON; cache.click_index = j; cache.click_id = c.buttons[j].id; cache.click_item = p_item; cache.click_column = col; - cache.click_pos = get_global_mouse_position() - get_global_position(); + cache.click_pos = click_pos; update(); //emit_signal(SNAME("button_pressed")); return -1; diff --git a/scene/main/node.cpp b/scene/main/node.cpp index f2a2648140..05409b7bfe 100644 --- a/scene/main/node.cpp +++ b/scene/main/node.cpp @@ -123,28 +123,27 @@ void Node::_notification(int p_notification) { } } break; case NOTIFICATION_READY: { - if (get_script_instance()) { - if (GDVIRTUAL_IS_OVERRIDDEN(_input)) { - set_process_input(true); - } - - if (GDVIRTUAL_IS_OVERRIDDEN(_unhandled_input)) { - set_process_unhandled_input(true); - } + if (GDVIRTUAL_IS_OVERRIDDEN(_input)) { + set_process_input(true); + } - if (GDVIRTUAL_IS_OVERRIDDEN(_unhandled_key_input)) { - set_process_unhandled_key_input(true); - } + if (GDVIRTUAL_IS_OVERRIDDEN(_unhandled_input)) { + set_process_unhandled_input(true); + } - if (GDVIRTUAL_IS_OVERRIDDEN(_process)) { - set_process(true); - } - if (GDVIRTUAL_IS_OVERRIDDEN(_physics_process)) { - set_physics_process(true); - } + if (GDVIRTUAL_IS_OVERRIDDEN(_unhandled_key_input)) { + set_process_unhandled_key_input(true); + } - GDVIRTUAL_CALL(_ready); + if (GDVIRTUAL_IS_OVERRIDDEN(_process)) { + set_process(true); } + if (GDVIRTUAL_IS_OVERRIDDEN(_physics_process)) { + set_physics_process(true); + } + + GDVIRTUAL_CALL(_ready); + if (data.filename.length()) { ERR_FAIL_COND(!is_inside_tree()); get_multiplayer()->scene_enter_exit_notify(data.filename, this, true); @@ -517,32 +516,32 @@ void Node::_propagate_process_owner(Node *p_owner, int p_pause_notification, int } } -void Node::set_network_authority(int p_peer_id, bool p_recursive) { - data.network_authority = p_peer_id; +void Node::set_multiplayer_authority(int p_peer_id, bool p_recursive) { + data.multiplayer_authority = p_peer_id; if (p_recursive) { for (int i = 0; i < data.children.size(); i++) { - data.children[i]->set_network_authority(p_peer_id, true); + data.children[i]->set_multiplayer_authority(p_peer_id, true); } } } -int Node::get_network_authority() const { - return data.network_authority; +int Node::get_multiplayer_authority() const { + return data.multiplayer_authority; } -bool Node::is_network_authority() const { +bool Node::is_multiplayer_authority() const { ERR_FAIL_COND_V(!is_inside_tree(), false); - return get_multiplayer()->get_network_unique_id() == data.network_authority; + return get_multiplayer()->get_unique_id() == data.multiplayer_authority; } /***** RPC CONFIG ********/ -uint16_t Node::rpc_config(const StringName &p_method, MultiplayerAPI::RPCMode p_rpc_mode, MultiplayerPeer::TransferMode p_transfer_mode, int p_channel) { +uint16_t Node::rpc_config(const StringName &p_method, Multiplayer::RPCMode p_rpc_mode, Multiplayer::TransferMode p_transfer_mode, int p_channel) { for (int i = 0; i < data.rpc_methods.size(); i++) { if (data.rpc_methods[i].name == p_method) { - MultiplayerAPI::RPCConfig &nd = data.rpc_methods.write[i]; + Multiplayer::RPCConfig &nd = data.rpc_methods.write[i]; nd.rpc_mode = p_rpc_mode; nd.transfer_mode = p_transfer_mode; nd.channel = p_channel; @@ -550,7 +549,7 @@ uint16_t Node::rpc_config(const StringName &p_method, MultiplayerAPI::RPCMode p_ } } // New method - MultiplayerAPI::RPCConfig nd; + Multiplayer::RPCConfig nd; nd.name = p_method; nd.rpc_mode = p_rpc_mode; nd.transfer_mode = p_transfer_mode; @@ -643,7 +642,7 @@ Variant Node::_rpc_id_bind(const Variant **p_args, int p_argcount, Callable::Cal void Node::rpcp(int p_peer_id, const StringName &p_method, const Variant **p_arg, int p_argcount) { ERR_FAIL_COND(!is_inside_tree()); - get_multiplayer()->rpcp(this, p_peer_id, true, p_method, p_arg, p_argcount); + get_multiplayer()->rpcp(this, p_peer_id, p_method, p_arg, p_argcount); } Ref<MultiplayerAPI> Node::get_multiplayer() const { @@ -664,7 +663,7 @@ void Node::set_custom_multiplayer(Ref<MultiplayerAPI> p_multiplayer) { multiplayer = p_multiplayer; } -Vector<MultiplayerAPI::RPCConfig> Node::get_node_rpc_methods() const { +Vector<Multiplayer::RPCConfig> Node::get_node_rpc_methods() const { return data.rpc_methods; } @@ -2738,15 +2737,15 @@ void Node::_bind_methods() { ClassDB::bind_method(D_METHOD("request_ready"), &Node::request_ready); - ClassDB::bind_method(D_METHOD("set_network_authority", "id", "recursive"), &Node::set_network_authority, DEFVAL(true)); - ClassDB::bind_method(D_METHOD("get_network_authority"), &Node::get_network_authority); + ClassDB::bind_method(D_METHOD("set_multiplayer_authority", "id", "recursive"), &Node::set_multiplayer_authority, DEFVAL(true)); + ClassDB::bind_method(D_METHOD("get_multiplayer_authority"), &Node::get_multiplayer_authority); - ClassDB::bind_method(D_METHOD("is_network_authority"), &Node::is_network_authority); + ClassDB::bind_method(D_METHOD("is_multiplayer_authority"), &Node::is_multiplayer_authority); ClassDB::bind_method(D_METHOD("get_multiplayer"), &Node::get_multiplayer); ClassDB::bind_method(D_METHOD("get_custom_multiplayer"), &Node::get_custom_multiplayer); ClassDB::bind_method(D_METHOD("set_custom_multiplayer", "api"), &Node::set_custom_multiplayer); - ClassDB::bind_method(D_METHOD("rpc_config", "method", "rpc_mode", "transfer_mode", "channel"), &Node::rpc_config, DEFVAL(MultiplayerPeer::TRANSFER_MODE_RELIABLE), DEFVAL(0)); + ClassDB::bind_method(D_METHOD("rpc_config", "method", "rpc_mode", "transfer_mode", "channel"), &Node::rpc_config, DEFVAL(Multiplayer::TRANSFER_MODE_RELIABLE), DEFVAL(0)); ClassDB::bind_method(D_METHOD("set_editor_description", "editor_description"), &Node::set_editor_description); ClassDB::bind_method(D_METHOD("get_editor_description"), &Node::get_editor_description); diff --git a/scene/main/node.h b/scene/main/node.h index d0246ebe84..198501eeac 100644 --- a/scene/main/node.h +++ b/scene/main/node.h @@ -127,8 +127,8 @@ private: ProcessMode process_mode = PROCESS_MODE_INHERIT; Node *process_owner = nullptr; - int network_authority = 1; // Server by default. - Vector<MultiplayerAPI::RPCConfig> rpc_methods; + int multiplayer_authority = 1; // Server by default. + Vector<Multiplayer::RPCConfig> rpc_methods; // Variables used to properly sort the node when processing, ignored otherwise. // TODO: Should move all the stuff below to bits. @@ -462,12 +462,12 @@ public: bool is_displayed_folded() const; /* NETWORK */ - void set_network_authority(int p_peer_id, bool p_recursive = true); - int get_network_authority() const; - bool is_network_authority() const; + void set_multiplayer_authority(int p_peer_id, bool p_recursive = true); + int get_multiplayer_authority() const; + bool is_multiplayer_authority() const; - uint16_t rpc_config(const StringName &p_method, MultiplayerAPI::RPCMode p_rpc_mode, MultiplayerPeer::TransferMode p_transfer_mode, int p_channel = 0); // config a local method for RPC - Vector<MultiplayerAPI::RPCConfig> get_node_rpc_methods() const; + uint16_t rpc_config(const StringName &p_method, Multiplayer::RPCMode p_rpc_mode, Multiplayer::TransferMode p_transfer_mode, int p_channel = 0); // config a local method for RPC + Vector<Multiplayer::RPCConfig> get_node_rpc_methods() const; void rpc(const StringName &p_method, VARIANT_ARG_LIST); // RPC, honors RPCMode, TransferMode, channel void rpc_id(int p_peer_id, const StringName &p_method, VARIANT_ARG_LIST); // RPC to specific peer(s), honors RPCMode, TransferMode, channel diff --git a/scene/main/scene_tree.cpp b/scene/main/scene_tree.cpp index 8260d0dff5..8452f1cb1a 100644 --- a/scene/main/scene_tree.cpp +++ b/scene/main/scene_tree.cpp @@ -858,15 +858,6 @@ void SceneTree::_notify_group_pause(const StringName &p_group, int p_notificatio } } -/* -void SceneMainLoop::_update_listener_2d() { - if (listener_2d.is_valid()) { - SpatialSound2DServer::get_singleton()->listener_set_space( listener_2d, world_2d->get_sound_space() ); - } -} - -*/ - void SceneTree::_call_input_pause(const StringName &p_group, CallInputType p_call_type, const Ref<InputEvent> &p_input, Viewport *p_viewport) { Map<StringName, Group>::Element *E = group_map.find(p_group); if (!E) { @@ -1346,7 +1337,7 @@ SceneTree::SceneTree() { current_scene = nullptr; const int msaa_mode = GLOBAL_DEF("rendering/anti_aliasing/quality/msaa", 0); - ProjectSettings::get_singleton()->set_custom_property_info("rendering/anti_aliasing/quality/msaa", PropertyInfo(Variant::INT, "rendering/anti_aliasing/quality/msaa", PROPERTY_HINT_ENUM, String::utf8("Disabled (Fastest),2× (Fast),4× (Average),8× (Slow),16× (Slower)"))); + ProjectSettings::get_singleton()->set_custom_property_info("rendering/anti_aliasing/quality/msaa", PropertyInfo(Variant::INT, "rendering/anti_aliasing/quality/msaa", PROPERTY_HINT_ENUM, String::utf8("Disabled (Fastest),2× (Average),4× (Slow),8× (Slowest)"))); root->set_msaa(Viewport::MSAA(msaa_mode)); const int ssaa_mode = GLOBAL_DEF("rendering/anti_aliasing/quality/screen_space_aa", 0); diff --git a/scene/main/scene_tree.h b/scene/main/scene_tree.h index b1b94ae910..19331c1906 100644 --- a/scene/main/scene_tree.h +++ b/scene/main/scene_tree.h @@ -31,7 +31,7 @@ #ifndef SCENE_TREE_H #define SCENE_TREE_H -#include "core/io/multiplayer_api.h" +#include "core/multiplayer/multiplayer_api.h" #include "core/os/main_loop.h" #include "core/os/thread_safe.h" #include "core/templates/self_list.h" diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp index 8ec8c193fb..fb86d37280 100644 --- a/scene/main/viewport.cpp +++ b/scene/main/viewport.cpp @@ -37,6 +37,7 @@ #include "core/templates/pair.h" #include "scene/2d/camera_2d.h" #include "scene/2d/collision_object_2d.h" +#include "scene/2d/listener_2d.h" #ifndef _3D_DISABLED #include "scene/3d/camera_3d.h" #include "scene/3d/collision_object_3d.h" @@ -80,7 +81,7 @@ void ViewportTexture::setup_local_to_scene() { RS::get_singleton()->texture_proxy_update(proxy, vp->texture_rid); RS::get_singleton()->free(proxy_ph); } else { - ERR_FAIL_COND(proxy.is_valid()); //should be invalid + ERR_FAIL_COND(proxy.is_valid()); // Should be invalid. proxy = RS::get_singleton()->texture_proxy_create(vp->texture_rid); } } @@ -117,7 +118,6 @@ Size2 ViewportTexture::get_size() const { } RID ViewportTexture::get_rid() const { - //ERR_FAIL_COND_V_MSG(!vp, RID(), "Viewport Texture must be set to use it."); if (proxy.is_null()) { proxy_ph = RS::get_singleton()->texture_2d_placeholder_create(); proxy = RS::get_singleton()->texture_proxy_create(proxy_ph); @@ -262,7 +262,7 @@ void Viewport::_sub_window_update(Window *p_window) { void Viewport::_sub_window_grab_focus(Window *p_window) { if (p_window == nullptr) { - //release current focus + // Release current focus. if (gui.subwindow_focused) { gui.subwindow_focused->_event_callback(DisplayServer::WINDOW_EVENT_FOCUS_OUT); gui.subwindow_focused = nullptr; @@ -288,18 +288,18 @@ void Viewport::_sub_window_grab_focus(Window *p_window) { ERR_FAIL_COND(index == -1); if (p_window->get_flag(Window::FLAG_NO_FOCUS)) { - //can only move to foreground, but no focus granted + // Can only move to foreground, but no focus granted. SubWindow sw = gui.sub_windows[index]; gui.sub_windows.remove(index); gui.sub_windows.push_back(sw); index = gui.sub_windows.size() - 1; _sub_window_update_order(); - return; //i guess not... + return; } if (gui.subwindow_focused) { if (gui.subwindow_focused == p_window) { - return; //nothing to do + return; // Nothing to do. } gui.subwindow_focused->_event_callback(DisplayServer::WINDOW_EVENT_FOCUS_OUT); gui.subwindow_drag = SUB_WINDOW_DRAG_DISABLED; @@ -316,7 +316,7 @@ void Viewport::_sub_window_grab_focus(Window *p_window) { gui.subwindow_focused->_event_callback(DisplayServer::WINDOW_EVENT_FOCUS_IN); - { //move to foreground + { // Move to foreground. SubWindow sw = gui.sub_windows[index]; gui.sub_windows.remove(index); gui.sub_windows.push_back(sw); @@ -422,7 +422,7 @@ void Viewport::_notification(int p_what) { } if (camera_3d_set.size() && !camera_3d) { - //there are cameras but no current camera, pick first in tree and make it current + // There are cameras but no current camera, pick first in tree and make it current. Camera3D *first = nullptr; for (Set<Camera3D *>::Element *E = camera_3d_set.front(); E; E = E->next()) { if (first == nullptr || first->is_greater_than(E->get())) { @@ -521,8 +521,9 @@ void Viewport::_process_picking() { PhysicsDirectSpaceState2D *ss2d = PhysicsServer2D::get_singleton()->space_get_direct_state(find_world_2d()->get_space()); if (physics_has_last_mousepos) { - // if no mouse event exists, create a motion one. This is necessary because objects or camera may have moved. - // while this extra event is sent, it is checked if both camera and last object and last ID did not move. If nothing changed, the event is discarded to avoid flooding with unnecessary motion events every frame + // If no mouse event exists, create a motion one. This is necessary because objects or camera may have moved. + // While this extra event is sent, it is checked if both camera and last object and last ID did not move. + // If nothing changed, the event is discarded to avoid flooding with unnecessary motion events every frame. bool has_mouse_event = false; for (const Ref<InputEvent> &m : physics_picking_events) { if (m.is_valid()) { @@ -596,7 +597,7 @@ void Viewport::_process_picking() { Ref<InputEventKey> k = ev; if (k.is_valid()) { - //only for mask + // Only for mask. physics_last_mouse_state.alt = k->is_alt_pressed(); physics_last_mouse_state.shift = k->is_shift_pressed(); physics_last_mouse_state.control = k->is_ctrl_pressed(); @@ -617,7 +618,7 @@ void Viewport::_process_picking() { } if (ss2d) { - //send to 2D + // Send to 2D. uint64_t frame = get_tree()->get_frame(); @@ -626,11 +627,11 @@ void Viewport::_process_picking() { Transform2D canvas_transform; ObjectID canvas_layer_id; if (E->get()) { - // A descendant CanvasLayer + // A descendant CanvasLayer. canvas_transform = E->get()->get_transform(); canvas_layer_id = E->get()->get_instance_id(); } else { - // This Viewport's builtin canvas + // This Viewport's builtin canvas. canvas_transform = get_canvas_transform(); canvas_layer_id = ObjectID(); } @@ -650,7 +651,7 @@ void Viewport::_process_picking() { co->_mouse_enter(); } else { F->get() = frame; - // It was already hovered, so don't send the event if it's faked + // It was already hovered, so don't send the event if it's faked. if (mm.is_valid() && mm->get_device() == InputEvent::DEVICE_ID_INTERNAL) { send_event = false; } @@ -699,11 +700,11 @@ void Viewport::_process_picking() { } if (captured) { - //none + // None. } else if (pos == last_pos) { if (last_id.is_valid()) { if (ObjectDB::get_instance(last_id) && last_object) { - //good, exists + // Good, exists. _collision_object_3d_input_event(last_object, camera_3d, ev, result.position, result.normal, result.shape); if (last_object->get_capture_input_on_drag() && mb.is_valid() && mb->get_button_index() == 1 && mb->is_pressed()) { physics_object_capture = last_id; @@ -822,7 +823,9 @@ Rect2 Viewport::get_visible_rect() const { } void Viewport::_update_listener_2d() { - AudioServer::get_singleton()->notify_listener_changed(); + if (AudioServer::get_singleton()) { + AudioServer::get_singleton()->notify_listener_changed(); + } } void Viewport::set_as_audio_listener_2d(bool p_enable) { @@ -831,7 +834,6 @@ void Viewport::set_as_audio_listener_2d(bool p_enable) { } audio_listener_2d = p_enable; - _update_listener_2d(); } @@ -839,6 +841,10 @@ bool Viewport::is_audio_listener_2d() const { return audio_listener_2d; } +Listener2D *Viewport::get_listener_2d() const { + return listener_2d; +} + void Viewport::enable_canvas_transform_override(bool p_enable) { if (override_canvas_transform == p_enable) { return; @@ -903,6 +909,21 @@ void Viewport::_camera_2d_set(Camera2D *p_camera_2d) { camera_2d = p_camera_2d; } +void Viewport::_listener_2d_set(Listener2D *p_listener) { + if (listener_2d == p_listener) { + return; + } else if (listener_2d) { + listener_2d->clear_current(); + } + listener_2d = p_listener; +} + +void Viewport::_listener_2d_remove(Listener2D *p_listener) { + if (listener_2d == p_listener) { + listener_2d = nullptr; + } +} + void Viewport::_canvas_layer_add(CanvasLayer *p_canvas_layer) { canvas_layers.insert(p_canvas_layer); } @@ -1102,7 +1123,7 @@ String Viewport::_gui_get_tooltip(Control *p_control, const Vector2 &p_pos, Cont while (p_control) { tooltip = p_control->get_tooltip(pos); - //Temporary solution for PopupMenus + // Temporary solution for PopupMenus. PopupMenu *menu = Object::cast_to<PopupMenu>(this); if (menu) { tooltip = menu->get_tooltip(pos); @@ -1216,11 +1237,9 @@ void Viewport::_gui_show_tooltip() { } void Viewport::_gui_call_input(Control *p_control, const Ref<InputEvent> &p_input) { - //_block(); - Ref<InputEvent> ev = p_input; - //mouse wheel events can't be stopped + // Mouse wheel events can't be stopped. Ref<InputEventMouseButton> mb = p_input; bool cant_stop_me_now = (mb.is_valid() && @@ -1256,11 +1275,9 @@ void Viewport::_gui_call_input(Control *p_control, const Ref<InputEvent> &p_inpu break; } - ev = ev->xformed_by(ci->get_transform()); //transform event upwards + ev = ev->xformed_by(ci->get_transform()); // Transform event upwards. ci = ci->get_parent_item(); } - - //_unblock(); } void Viewport::_gui_call_notification(Control *p_control, int p_what) { @@ -1290,12 +1307,10 @@ void Viewport::_gui_call_notification(Control *p_control, int p_what) { ci = ci->get_parent_item(); } - - //_unblock(); } Control *Viewport::gui_find_control(const Point2 &p_global) { - //aca va subwindows + // Handle subwindows. _gui_sort_roots(); for (List<Control *>::Element *E = gui.roots.back(); E; E = E->prev()) { @@ -1327,8 +1342,7 @@ Control *Viewport::_gui_find_control_at_pos(CanvasItem *p_node, const Point2 &p_ } if (!p_node->is_visible()) { - //return _find_next_visible_control_at_pos(p_node,p_global,r_inv_xform); - return nullptr; //canvas item hidden, discard + return nullptr; // Canvas item hidden, discard. } Transform2D matrix = p_xform * p_node->get_transform(); @@ -1372,32 +1386,31 @@ Control *Viewport::_gui_find_control_at_pos(CanvasItem *p_node, const Point2 &p_ } bool Viewport::_gui_drop(Control *p_at_control, Point2 p_at_pos, bool p_just_check) { - { //attempt grab, try parent controls too - CanvasItem *ci = p_at_control; - while (ci) { - Control *control = Object::cast_to<Control>(ci); - if (control) { - if (control->can_drop_data(p_at_pos, gui.drag_data)) { - if (!p_just_check) { - control->drop_data(p_at_pos, gui.drag_data); - } - - return true; + // Attempt grab, try parent controls too. + CanvasItem *ci = p_at_control; + while (ci) { + Control *control = Object::cast_to<Control>(ci); + if (control) { + if (control->can_drop_data(p_at_pos, gui.drag_data)) { + if (!p_just_check) { + control->drop_data(p_at_pos, gui.drag_data); } - if (control->data.mouse_filter == Control::MOUSE_FILTER_STOP) { - break; - } + return true; } - p_at_pos = ci->get_transform().xform(p_at_pos); - - if (ci->is_set_as_top_level()) { + if (control->data.mouse_filter == Control::MOUSE_FILTER_STOP) { break; } + } + + p_at_pos = ci->get_transform().xform(p_at_pos); - ci = ci->get_parent_item(); + if (ci->is_set_as_top_level()) { + break; } + + ci = ci->get_parent_item(); } return false; @@ -1417,23 +1430,9 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { if (mb->is_pressed()) { Size2 pos = mpos; if (gui.mouse_focus_mask) { - //do not steal mouse focus and stuff while a focus mask exists - gui.mouse_focus_mask |= 1 << (mb->get_button_index() - 1); //add the button to the mask + // Do not steal mouse focus and stuff while a focus mask exists. + gui.mouse_focus_mask |= 1 << (mb->get_button_index() - 1); // Add the button to the mask. } else { - bool is_handled = false; - - if (is_handled) { - set_input_as_handled(); - return; - } - - //Matrix32 parent_xform; - - /* - if (data.parent_canvas_item) - parent_xform=data.parent_canvas_item->get_global_transform(); - */ - gui.mouse_focus = gui_find_control(pos); gui.last_mouse_focus = gui.mouse_focus; @@ -1450,7 +1449,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { } } - mb = mb->xformed_by(Transform2D()); // make a copy of the event + mb = mb->xformed_by(Transform2D()); // Make a copy of the event. mb->set_global_position(pos); @@ -1467,7 +1466,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { } #endif - if (mb->get_button_index() == MOUSE_BUTTON_LEFT) { //assign focus + if (mb->get_button_index() == MOUSE_BUTTON_LEFT) { // Assign focus. CanvasItem *ci = gui.mouse_focus; while (ci) { Control *control = Object::cast_to<Control>(ci); @@ -1499,7 +1498,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { set_input_as_handled(); if (gui.drag_data.get_type() != Variant::NIL && mb->get_button_index() == MOUSE_BUTTON_LEFT) { - //alternate drop use (when using force_drag(), as proposed by #5342 + // Alternate drop use (when using force_drag(), as proposed by #5342). if (gui.mouse_focus) { _gui_drop(gui.mouse_focus, pos, false); } @@ -1513,7 +1512,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { gui.drag_preview_id = ObjectID(); } _propagate_viewport_notification(this, NOTIFICATION_DRAG_END); - //change mouse accordingly + // Change mouse accordingly. } _gui_cancel_tooltip(); @@ -1533,26 +1532,28 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { gui.dragging = false; gui.drag_mouse_over = nullptr; _propagate_viewport_notification(this, NOTIFICATION_DRAG_END); - //change mouse accordingly + // Change mouse accordingly. } - gui.mouse_focus_mask &= ~(1 << (mb->get_button_index() - 1)); //remove from mask + gui.mouse_focus_mask &= ~(1 << (mb->get_button_index() - 1)); // Remove from mask. if (!gui.mouse_focus) { - //release event is only sent if a mouse focus (previously pressed button) exists + // Release event is only sent if a mouse focus (previously pressed button) exists. return; } Size2 pos = mpos; - mb = mb->xformed_by(Transform2D()); //make a copy + mb = mb->xformed_by(Transform2D()); // Make a copy. mb->set_global_position(pos); pos = gui.focus_inv_xform.xform(pos); mb->set_position(pos); Control *mouse_focus = gui.mouse_focus; - //disable mouse focus if needed before calling input, this makes popups on mouse press event work better, as the release will never be received otherwise + // Disable mouse focus if needed before calling input, + // this makes popups on mouse press event work better, + // as the release will never be received otherwise. if (gui.mouse_focus_mask == 0) { gui.mouse_focus = nullptr; gui.forced_mouse_focus = false; @@ -1562,11 +1563,6 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { _gui_call_input(mouse_focus, mb); } - /*if (gui.drag_data.get_type()!=Variant::NIL && mb->get_button_index()==MOUSE_BUTTON_LEFT) { - _propagate_viewport_notification(this,NOTIFICATION_DRAG_END); - gui.drag_data=Variant(); //always clear - }*/ - // In case the mouse was released after for example dragging a scrollbar, // check whether the current control is different from the stored one. If // it is different, rather than wait for it to be updated the next time the @@ -1605,12 +1601,12 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { Control *over = nullptr; - // D&D + // Drag & drop. if (!gui.drag_attempted && gui.mouse_focus && mm->get_button_mask() & MOUSE_BUTTON_MASK_LEFT) { gui.drag_accum += mm->get_relative(); float len = gui.drag_accum.length(); if (len > 10) { - { //attempt grab, try parent controls too + { // Attempt grab, try parent controls too. CanvasItem *ci = gui.mouse_focus; while (ci) { Control *control = Object::cast_to<Control>(ci); @@ -1683,14 +1679,14 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { Vector2 speed = localizer.basis_xform(mm->get_speed()); Vector2 rel = localizer.basis_xform(mm->get_relative()); - mm = mm->xformed_by(Transform2D()); //make a copy + mm = mm->xformed_by(Transform2D()); // Make a copy. mm->set_global_position(mpos); mm->set_speed(speed); mm->set_relative(rel); if (mm->get_button_mask() == 0) { - //nothing pressed + // Nothing pressed. bool can_tooltip = true; @@ -1714,7 +1710,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { is_tooltip_shown = true; } } else { - is_tooltip_shown = true; //well, nothing to compare against, likely using custom control, so if it changes there is nothing we can do + is_tooltip_shown = true; // Nothing to compare against, likely using custom control, so if it changes there is nothing we can do. } } } else { @@ -1771,7 +1767,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { } if (gui.drag_data.get_type() != Variant::NIL) { - //handle dragandrop + // Handle drag & drop. Control *drag_preview = _gui_get_drag_preview(); if (drag_preview) { @@ -1781,9 +1777,8 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { gui.drag_mouse_over = over; gui.drag_mouse_over_pos = Vector2(); - //find the window this is above of - - //see if there is an embedder + // Find the window this is above of. + // See if there is an embedder. Viewport *embedder = nullptr; Vector2 viewport_pos; @@ -1791,7 +1786,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { embedder = this; viewport_pos = mpos; } else { - //not an embedder, but may be a subwindow of an embedder + // Not an embedder, but may be a subwindow of an embedder. Window *w = Object::cast_to<Window>(this); if (w) { if (w->is_embedded()) { @@ -1799,7 +1794,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { Transform2D ai = (get_final_transform().affine_inverse() * _get_input_pre_xform()).affine_inverse(); - viewport_pos = ai.xform(mpos) + w->get_position(); //to parent coords + viewport_pos = ai.xform(mpos) + w->get_position(); // To parent coords. } } } @@ -1807,7 +1802,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { Viewport *viewport_under = nullptr; if (embedder) { - //use embedder logic + // Use embedder logic. for (int i = embedder->gui.sub_windows.size() - 1; i >= 0; i--) { Window *sw = embedder->gui.sub_windows[i].window; @@ -1825,11 +1820,11 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { } if (!viewport_under) { - //not in a subwindow, likely in embedder + // Not in a subwindow, likely in embedder. viewport_under = embedder; } } else { - //use displayserver logic + // Use DisplayServer logic. Vector2i screen_mouse_pos = DisplayServer::get_singleton()->mouse_get_position(); DisplayServer::WindowID window_id = DisplayServer::get_singleton()->get_window_at_screen_position(screen_mouse_pos); @@ -1837,7 +1832,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { if (window_id != DisplayServer::INVALID_WINDOW_ID) { ObjectID object_under = DisplayServer::get_singleton()->window_get_attached_instance_id(window_id); - if (object_under != ObjectID()) { //fetch window + if (object_under != ObjectID()) { // Fetch window. Window *w = Object::cast_to<Window>(ObjectDB::get_instance(object_under)); if (w) { viewport_under = w; @@ -1850,7 +1845,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { if (viewport_under) { Transform2D ai = (viewport_under->get_final_transform().affine_inverse() * viewport_under->_get_input_pre_xform()); viewport_pos = ai.xform(viewport_pos); - //find control under at pos + // Find control under at position. gui.drag_mouse_over = viewport_under->gui_find_control(viewport_pos); if (gui.drag_mouse_over) { Transform2D localizer = gui.drag_mouse_over->get_global_transform_with_canvas().affine_inverse(); @@ -1882,7 +1877,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { Control *over = gui_find_control(pos); if (over) { if (over->can_process()) { - touch_event = touch_event->xformed_by(Transform2D()); //make a copy + touch_event = touch_event->xformed_by(Transform2D()); // Make a copy. if (over == gui.mouse_focus) { pos = gui.focus_inv_xform.xform(pos); } else { @@ -1896,7 +1891,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { } } else if (touch_event->get_index() == 0 && gui.last_mouse_focus) { if (gui.last_mouse_focus->can_process()) { - touch_event = touch_event->xformed_by(Transform2D()); //make a copy + touch_event = touch_event->xformed_by(Transform2D()); // Make a copy. touch_event->set_position(gui.focus_inv_xform.xform(pos)); _gui_call_input(gui.last_mouse_focus, touch_event); @@ -1917,7 +1912,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { Control *over = gui_find_control(pos); if (over) { if (over->can_process()) { - gesture_event = gesture_event->xformed_by(Transform2D()); //make a copy + gesture_event = gesture_event->xformed_by(Transform2D()); // Make a copy. if (over == gui.mouse_focus) { pos = gui.focus_inv_xform.xform(pos); } else { @@ -1944,7 +1939,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { Vector2 speed = localizer.basis_xform(drag_event->get_speed()); Vector2 rel = localizer.basis_xform(drag_event->get_relative()); - drag_event = drag_event->xformed_by(Transform2D()); //make a copy + drag_event = drag_event->xformed_by(Transform2D()); // Make a copy. drag_event->set_speed(speed); drag_event->set_relative(rel); @@ -1975,12 +1970,11 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { } } - Control *from = gui.key_focus ? gui.key_focus : nullptr; //hmm + Control *from = gui.key_focus ? gui.key_focus : nullptr; - //keyboard focus - //if (from && p_event->is_pressed() && !p_event->is_alt_pressed() && !p_event->is_meta_pressed() && !p_event->key->is_command_pressed()) { + // Keyboard focus. Ref<InputEventKey> k = p_event; - //need to check for mods, otherwise any combination of alt/ctrl/shift+<up/down/left/right/etc> is handled here when it shouldn't be. + // Need to check for mods, otherwise any combination of alt/ctrl/shift+<up/down/left/right/etc> is handled here when it shouldn't be. bool mods = k.is_valid() && (k->is_ctrl_pressed() || k->is_alt_pressed() || k->is_shift_pressed() || k->is_meta_pressed()); if (from && p_event->is_pressed()) { @@ -2051,7 +2045,7 @@ void Viewport::_gui_set_drag_preview(Control *p_base, Control *p_control) { } p_control->set_as_top_level(true); p_control->set_position(gui.last_mouse_pos); - p_base->get_root_parent_control()->add_child(p_control); //add as child of viewport + p_base->get_root_parent_control()->add_child(p_control); // Add as child of viewport. p_control->raise(); gui.drag_preview_id = p_control->get_instance_id(); @@ -2153,8 +2147,8 @@ bool Viewport::_gui_control_has_focus(const Control *p_control) { } void Viewport::_gui_control_grab_focus(Control *p_control) { - //no need for change if (gui.key_focus && gui.key_focus == p_control) { + // No need for change. return; } get_tree()->call_group_flags(SceneTree::GROUP_CALL_REALTIME, "_viewports", "_gui_remove_focus_for_window", (Node *)get_base_window()); @@ -2236,7 +2230,7 @@ void Viewport::_cleanup_mouseover_colliders(bool p_clean_all_frames, bool p_paus to_erase.pop_front(); } - // Per-shape + // Per-shape. List<Map<Pair<ObjectID, int>, uint64_t, PairSort<ObjectID, int>>::Element *> shapes_to_erase; for (Map<Pair<ObjectID, int>, uint64_t, PairSort<ObjectID, int>>::Element *E = physics_2d_shape_mouseover.front(); E; E = E->next()) { @@ -2275,7 +2269,7 @@ void Viewport::_gui_grab_click_focus(Control *p_control) { void Viewport::_post_gui_grab_click_focus() { Control *focus_grabber = gui.mouse_click_grabber; if (!focus_grabber) { - // Redundant grab requests were made + // Redundant grab requests were made. return; } gui.mouse_click_grabber = nullptr; @@ -2293,7 +2287,7 @@ void Viewport::_post_gui_grab_click_focus() { Ref<InputEventMouseButton> mb; mb.instantiate(); - //send unclick + // Send unclick. mb->set_position(click); mb->set_button_index(MouseButton(i + 1)); @@ -2311,7 +2305,7 @@ void Viewport::_post_gui_grab_click_focus() { Ref<InputEventMouseButton> mb; mb.instantiate(); - //send click + // Send click. mb->set_position(click); mb->set_button_index(MouseButton(i + 1)); @@ -2348,7 +2342,7 @@ Viewport::SubWindowResize Viewport::_sub_window_get_resize_margin(Window *p_subw r.size.y += title_height; if (r.has_point(p_point)) { - return SUB_WINDOW_RESIZE_DISABLED; //it's inside, so no resize + return SUB_WINDOW_RESIZE_DISABLED; // It's inside, so no resize. } int dist_x = p_point.x < r.position.x ? (p_point.x - r.position.x) : (p_point.x > (r.position.x + r.size.x) ? (p_point.x - (r.position.x + r.size.x)) : 0); @@ -2407,12 +2401,12 @@ bool Viewport::_sub_windows_forward_input(const Ref<InputEvent> &p_event) { if (mb.is_valid() && !mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT) { if (gui.subwindow_drag == SUB_WINDOW_DRAG_CLOSE) { if (gui.subwindow_drag_close_rect.has_point(mb->get_position())) { - //close window + // Close window. gui.subwindow_focused->_event_callback(DisplayServer::WINDOW_EVENT_CLOSE_REQUEST); } } gui.subwindow_drag = SUB_WINDOW_DRAG_DISABLED; - if (gui.subwindow_focused != nullptr) { //may have been erased + if (gui.subwindow_focused != nullptr) { // May have been erased. _sub_window_update(gui.subwindow_focused); } } @@ -2519,27 +2513,27 @@ bool Viewport::_sub_windows_forward_input(const Ref<InputEvent> &p_event) { gui.subwindow_focused->_rect_changed_callback(r); } - if (gui.subwindow_focused) { //may have been erased + if (gui.subwindow_focused) { // May have been erased. _sub_window_update(gui.subwindow_focused); } } - return true; //handled + return true; // Handled. } Ref<InputEventMouseButton> mb = p_event; - //if the event is a mouse button, we need to check whether another window was clicked + // If the event is a mouse button, we need to check whether another window was clicked. if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT) { bool click_on_window = false; for (int i = gui.sub_windows.size() - 1; i >= 0; i--) { SubWindow &sw = gui.sub_windows.write[i]; - //clicked inside window? + // Clicked inside window? Rect2i r = Rect2i(sw.window->get_position(), sw.window->get_size()); if (!sw.window->get_flag(Window::FLAG_BORDERLESS)) { - //check top bar + // Check top bar. int title_height = sw.window->get_theme_constant(SNAME("title_height")); Rect2i title_bar = r; title_bar.position.y -= title_height; @@ -2557,13 +2551,13 @@ bool Viewport::_sub_windows_forward_input(const Ref<InputEvent> &p_event) { close_rect.size = close_icon->get_size(); if (gui.subwindow_focused != sw.window) { - //refocus + // Refocus. _sub_window_grab_focus(sw.window); } if (close_rect.has_point(mb->get_position())) { gui.subwindow_drag = SUB_WINDOW_DRAG_CLOSE; - gui.subwindow_drag_close_inside = true; //starts inside + gui.subwindow_drag_close_inside = true; // Starts inside. gui.subwindow_drag_close_rect = close_rect; } else { gui.subwindow_drag = SUB_WINDOW_DRAG_MOVE; @@ -2584,9 +2578,9 @@ bool Viewport::_sub_windows_forward_input(const Ref<InputEvent> &p_event) { } } if (!click_on_window && r.has_point(mb->get_position())) { - //clicked, see if it needs to fetch focus + // Clicked, see if it needs to fetch focus. if (gui.subwindow_focused != sw.window) { - //refocus + // Refocus. _sub_window_grab_focus(sw.window); } @@ -2599,7 +2593,7 @@ bool Viewport::_sub_windows_forward_input(const Ref<InputEvent> &p_event) { } if (!click_on_window && gui.subwindow_focused) { - //no window found and clicked, remove focus + // No window found and clicked, remove focus. _sub_window_grab_focus(nullptr); } } @@ -2623,13 +2617,13 @@ bool Viewport::_sub_windows_forward_input(const Ref<InputEvent> &p_event) { DisplayServer::get_singleton()->cursor_set_shape(shapes[resize]); - return true; //reserved for showing the resize cursor + return true; // Reserved for showing the resize cursor. } } } if (gui.subwindow_drag != SUB_WINDOW_DRAG_DISABLED) { - return true; // dragging, don't pass the event + return true; // Dragging, don't pass the event. } if (!gui.subwindow_focused) { @@ -2706,10 +2700,10 @@ void Viewport::push_unhandled_input(const Ref<InputEvent> &p_event, bool p_local ev = p_event; } - // Unhandled Input + // Unhandled Input. get_tree()->_call_input_pause(unhandled_input_group, SceneTree::CALL_INPUT_TYPE_UNHANDLED_INPUT, ev, this); - // Unhandled key Input - used for performance reasons - This is called a lot less than _unhandled_input since it ignores MouseMotion, etc + // Unhandled key Input - used for performance reasons - This is called a lot less than _unhandled_input since it ignores MouseMotion, etc. if (!is_input_handled() && (Object::cast_to<InputEventKey>(*ev) != nullptr || Object::cast_to<InputEventShortcut>(*ev) != nullptr)) { get_tree()->_call_input_pause(unhandled_key_input_group, SceneTree::CALL_INPUT_TYPE_UNHANDLED_KEY_INPUT, ev, this); } @@ -2720,7 +2714,7 @@ void Viewport::push_unhandled_input(const Ref<InputEvent> &p_event, bool p_local Object::cast_to<InputEventMouseMotion>(*ev) || Object::cast_to<InputEventScreenDrag>(*ev) || Object::cast_to<InputEventScreenTouch>(*ev) || - Object::cast_to<InputEventKey>(*ev) //to remember state + Object::cast_to<InputEventKey>(*ev) // To remember state. )) { physics_picking_events.push_back(ev); @@ -2766,10 +2760,6 @@ Variant Viewport::gui_get_drag_data() const { } TypedArray<String> Viewport::get_configuration_warnings() const { - /*if (get_parent() && !Object::cast_to<Control>(get_parent()) && !render_target) { - return TTR("This viewport is not set as render target. If you intend for it to display its contents directly to the screen, make it a child of a Control so it can obtain a size. Otherwise, make it a RenderTarget and assign its internal texture to some node for display."); - }*/ - TypedArray<String> warnings = Node::get_configuration_warnings(); if (size.x == 0 || size.y == 0) { @@ -3075,7 +3065,9 @@ bool Viewport::is_audio_listener_3d() const { } void Viewport::_update_listener_3d() { - AudioServer::get_singleton()->notify_listener_changed(); + if (AudioServer::get_singleton()) { + AudioServer::get_singleton()->notify_listener_changed(); + } } void Viewport::_listener_transform_3d_changed_notify() { @@ -3120,7 +3112,7 @@ void Viewport::_listener_3d_make_next_current(Listener3D *p_exclude) { E->get()->make_current(); } } else { - // Attempt to reset listener to the camera position + // Attempt to reset listener to the camera position. if (camera_3d != nullptr) { _update_listener_3d(); _camera_3d_transform_changed_notify(); @@ -3133,11 +3125,11 @@ void Viewport::_collision_object_3d_input_event(CollisionObject3D *p_object, Cam Transform3D camera_transform = p_camera->get_global_transform(); ObjectID id = p_object->get_instance_id(); - //avoid sending the fake event unnecessarily if nothing really changed in the context + // Avoid sending the fake event unnecessarily if nothing really changed in the context. if (object_transform == physics_last_object_transform && camera_transform == physics_last_camera_transform && physics_last_id == id) { Ref<InputEventMouseMotion> mm = p_input_event; if (mm.is_valid() && mm->get_device() == InputEvent::DEVICE_ID_INTERNAL) { - return; //discarded + return; // Discarded. } } p_object->_input_event_call(camera_3d, p_input_event, p_pos, p_normal, p_shape); @@ -3606,7 +3598,7 @@ void Viewport::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "snap_2d_transforms_to_pixel"), "set_snap_2d_transforms_to_pixel", "is_snap_2d_transforms_to_pixel_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "snap_2d_vertices_to_pixel"), "set_snap_2d_vertices_to_pixel", "is_snap_2d_vertices_to_pixel_enabled"); ADD_GROUP("Rendering", ""); - ADD_PROPERTY(PropertyInfo(Variant::INT, "msaa", PROPERTY_HINT_ENUM, String::utf8("Disabled (Fastest),2× (Fast),4× (Average),8× (Slow),16× (Slower)")), "set_msaa", "get_msaa"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "msaa", PROPERTY_HINT_ENUM, String::utf8("Disabled (Fastest),2× (Average),4× (Slow),8× (Slowest)")), "set_msaa", "get_msaa"); ADD_PROPERTY(PropertyInfo(Variant::INT, "screen_space_aa", PROPERTY_HINT_ENUM, "Disabled (Fastest),FXAA (Fast)"), "set_screen_space_aa", "get_screen_space_aa"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_debanding"), "set_use_debanding", "is_using_debanding"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_occlusion_culling"), "set_use_occlusion_culling", "is_using_occlusion_culling"); @@ -3658,7 +3650,6 @@ void Viewport::_bind_methods() { BIND_ENUM_CONSTANT(MSAA_2X); BIND_ENUM_CONSTANT(MSAA_4X); BIND_ENUM_CONSTANT(MSAA_8X); - BIND_ENUM_CONSTANT(MSAA_16X); BIND_ENUM_CONSTANT(MSAA_MAX); BIND_ENUM_CONSTANT(SCREEN_SPACE_AA_DISABLED); @@ -3733,10 +3724,8 @@ Viewport::Viewport() { viewport_textures.insert(default_texture.ptr()); default_texture->proxy = RS::get_singleton()->texture_proxy_create(texture_rid); - //internal_listener_2d = SpatialSound2DServer::get_singleton()->listener_create(); - canvas_layers.insert(nullptr); // This eases picking code (interpreted as the canvas of the Viewport) + canvas_layers.insert(nullptr); // This eases picking code (interpreted as the canvas of the Viewport). - //clear=true; set_shadow_atlas_size(shadow_atlas_size); for (int i = 0; i < 4; i++) { @@ -3764,11 +3753,11 @@ Viewport::Viewport() { set_scale_3d((Scale3D)scale); #endif // _3D_DISABLED - set_sdf_oversize(sdf_oversize); //set to server + set_sdf_oversize(sdf_oversize); // Set to server. } Viewport::~Viewport() { - //erase itself from viewport textures + // Erase itself from viewport textures. for (Set<ViewportTexture *>::Element *E = viewport_textures.front(); E; E = E->next()) { E->get()->vp = nullptr; } diff --git a/scene/main/viewport.h b/scene/main/viewport.h index d9b21ce6a8..bfb52c4b98 100644 --- a/scene/main/viewport.h +++ b/scene/main/viewport.h @@ -41,6 +41,7 @@ class Listener3D; class World3D; #endif // _3D_DISABLED +class Listener2D; class Camera2D; class CanvasItem; class CanvasLayer; @@ -112,7 +113,7 @@ public: MSAA_2X, MSAA_4X, MSAA_8X, - MSAA_16X, + // 16x MSAA is not supported due to its high cost and driver bugs. MSAA_MAX }; @@ -201,6 +202,7 @@ private: Viewport *parent = nullptr; + Listener2D *listener_2d = nullptr; Camera2D *camera_2d = nullptr; Set<CanvasLayer *> canvas_layers; @@ -416,6 +418,10 @@ private: bool _gui_drop(Control *p_at_control, Point2 p_at_pos, bool p_just_check); + friend class Listener2D; + void _listener_2d_set(Listener2D *p_listener); + void _listener_2d_remove(Listener2D *p_listener); + friend class Camera2D; void _camera_2d_set(Camera2D *p_camera_2d); @@ -457,6 +463,7 @@ protected: public: uint64_t get_processed_events_count() const { return event_count; } + Listener2D *get_listener_2d() const; Camera2D *get_camera_2d() const; void set_as_audio_listener_2d(bool p_enable); bool is_audio_listener_2d() const; diff --git a/scene/main/window.cpp b/scene/main/window.cpp index 1fcfce2ea9..ca5a3915d0 100644 --- a/scene/main/window.cpp +++ b/scene/main/window.cpp @@ -1497,7 +1497,7 @@ void Window::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "position"), "set_position", "get_position"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "size"), "set_size", "get_size"); ADD_PROPERTY(PropertyInfo(Variant::INT, "mode", PROPERTY_HINT_ENUM, "Windowed,Minimized,Maximized,Fullscreen"), "set_mode", "get_mode"); - ADD_PROPERTY(PropertyInfo(Variant::STRING, "current_screen"), "set_current_screen", "get_current_screen"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "current_screen"), "set_current_screen", "get_current_screen"); ADD_GROUP("Flags", ""); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "visible"), "set_visible", "is_visible"); diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp index 0c97fee711..015a4d5dba 100644 --- a/scene/register_scene_types.cpp +++ b/scene/register_scene_types.cpp @@ -49,6 +49,7 @@ #include "scene/2d/light_2d.h" #include "scene/2d/light_occluder_2d.h" #include "scene/2d/line_2d.h" +#include "scene/2d/listener_2d.h" #include "scene/2d/mesh_instance_2d.h" #include "scene/2d/multimesh_instance_2d.h" #include "scene/2d/navigation_agent_2d.h" @@ -488,6 +489,7 @@ void register_scene_types() { GDREGISTER_VIRTUAL_CLASS(CollisionObject3D); GDREGISTER_VIRTUAL_CLASS(PhysicsBody3D); GDREGISTER_CLASS(StaticBody3D); + GDREGISTER_CLASS(AnimatableBody3D); GDREGISTER_CLASS(RigidBody3D); GDREGISTER_CLASS(KinematicCollision3D); GDREGISTER_CLASS(CharacterBody3D); @@ -647,6 +649,7 @@ void register_scene_types() { GDREGISTER_VIRTUAL_CLASS(CollisionObject2D); GDREGISTER_VIRTUAL_CLASS(PhysicsBody2D); GDREGISTER_CLASS(StaticBody2D); + GDREGISTER_CLASS(AnimatableBody2D); GDREGISTER_CLASS(RigidBody2D); GDREGISTER_CLASS(CharacterBody2D); GDREGISTER_CLASS(KinematicCollision2D); @@ -669,6 +672,7 @@ void register_scene_types() { OS::get_singleton()->yield(); //may take time to init GDREGISTER_CLASS(Camera2D); + GDREGISTER_CLASS(Listener2D); GDREGISTER_VIRTUAL_CLASS(Joint2D); GDREGISTER_CLASS(PinJoint2D); GDREGISTER_CLASS(GrooveJoint2D); diff --git a/scene/resources/audio_stream_sample.cpp b/scene/resources/audio_stream_sample.cpp index 2ab9b7b5a4..d018103e64 100644 --- a/scene/resources/audio_stream_sample.cpp +++ b/scene/resources/audio_stream_sample.cpp @@ -480,6 +480,10 @@ float AudioStreamSample::get_length() const { return float(len) / mix_rate; } +bool AudioStreamSample::is_monophonic() const { + return false; +} + void AudioStreamSample::set_data(const Vector<uint8_t> &p_data) { AudioServer::get_singleton()->lock(); if (data) { diff --git a/scene/resources/audio_stream_sample.h b/scene/resources/audio_stream_sample.h index 8bf3d29123..24198e3c98 100644 --- a/scene/resources/audio_stream_sample.h +++ b/scene/resources/audio_stream_sample.h @@ -136,6 +136,8 @@ public: virtual float get_length() const override; //if supported, otherwise return 0 + virtual bool is_monophonic() const override; + void set_data(const Vector<uint8_t> &p_data); Vector<uint8_t> get_data() const; diff --git a/scene/resources/default_theme/default_theme.cpp b/scene/resources/default_theme/default_theme.cpp index 4845c556c6..fa3824e6eb 100644 --- a/scene/resources/default_theme/default_theme.cpp +++ b/scene/resources/default_theme/default_theme.cpp @@ -502,8 +502,10 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_icon("increment", "HScrollBar", empty_icon); theme->set_icon("increment_highlight", "HScrollBar", empty_icon); + theme->set_icon("increment_pressed", "HScrollBar", empty_icon); theme->set_icon("decrement", "HScrollBar", empty_icon); theme->set_icon("decrement_highlight", "HScrollBar", empty_icon); + theme->set_icon("decrement_pressed", "HScrollBar", empty_icon); // VScrollBar @@ -515,8 +517,10 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_icon("increment", "VScrollBar", empty_icon); theme->set_icon("increment_highlight", "VScrollBar", empty_icon); + theme->set_icon("increment_pressed", "VScrollBar", empty_icon); theme->set_icon("decrement", "VScrollBar", empty_icon); theme->set_icon("decrement_highlight", "VScrollBar", empty_icon); + theme->set_icon("decrement_pressed", "VScrollBar", empty_icon); // HSlider diff --git a/scene/resources/material.cpp b/scene/resources/material.cpp index 1d2a2ef26c..77a68151c4 100644 --- a/scene/resources/material.cpp +++ b/scene/resources/material.cpp @@ -963,7 +963,9 @@ void BaseMaterial3D::_update_shader() { } else { code += " float depth = 1.0 - texture(texture_heightmap, base_uv).r;\n"; } - code += " vec2 ofs = base_uv - view_dir.xy / view_dir.z * (depth * heightmap_scale);\n"; + // Use offset limiting to improve the appearance of non-deep parallax. + // This reduces the impression of depth, but avoids visible warping in the distance. + code += " vec2 ofs = base_uv - view_dir.xy * depth * heightmap_scale;\n"; } code += " base_uv=ofs;\n"; diff --git a/scene/resources/mesh.cpp b/scene/resources/mesh.cpp index ad589a605e..71d0c69d55 100644 --- a/scene/resources/mesh.cpp +++ b/scene/resources/mesh.cpp @@ -543,6 +543,7 @@ void Mesh::_bind_methods() { BIND_ENUM_CONSTANT(ARRAY_FORMAT_BLEND_SHAPE_MASK); BIND_ENUM_CONSTANT(ARRAY_FORMAT_CUSTOM_BASE); + BIND_ENUM_CONSTANT(ARRAY_FORMAT_CUSTOM_BITS); BIND_ENUM_CONSTANT(ARRAY_FORMAT_CUSTOM0_SHIFT); BIND_ENUM_CONSTANT(ARRAY_FORMAT_CUSTOM1_SHIFT); BIND_ENUM_CONSTANT(ARRAY_FORMAT_CUSTOM2_SHIFT); diff --git a/scene/resources/mesh.h b/scene/resources/mesh.h index 27b0eb098b..aa4ed1cb13 100644 --- a/scene/resources/mesh.h +++ b/scene/resources/mesh.h @@ -105,6 +105,7 @@ public: ARRAY_FORMAT_BLEND_SHAPE_MASK = RS::ARRAY_FORMAT_BLEND_SHAPE_MASK, ARRAY_FORMAT_CUSTOM_BASE = RS::ARRAY_FORMAT_CUSTOM_BASE, + ARRAY_FORMAT_CUSTOM_BITS = RS::ARRAY_FORMAT_CUSTOM_BITS, ARRAY_FORMAT_CUSTOM0_SHIFT = RS::ARRAY_FORMAT_CUSTOM0_SHIFT, ARRAY_FORMAT_CUSTOM1_SHIFT = RS::ARRAY_FORMAT_CUSTOM1_SHIFT, ARRAY_FORMAT_CUSTOM2_SHIFT = RS::ARRAY_FORMAT_CUSTOM2_SHIFT, diff --git a/scene/resources/mesh_library.cpp b/scene/resources/mesh_library.cpp index 33c9ca6d1e..cfb7c3e037 100644 --- a/scene/resources/mesh_library.cpp +++ b/scene/resources/mesh_library.cpp @@ -43,6 +43,8 @@ bool MeshLibrary::_set(const StringName &p_name, const Variant &p_value) { set_item_name(idx, p_value); } else if (what == "mesh") { set_item_mesh(idx, p_value); + } else if (what == "mesh_transform") { + set_item_mesh_transform(idx, p_value); } else if (what == "shape") { Vector<ShapeData> shapes; ShapeData sd; @@ -77,6 +79,8 @@ bool MeshLibrary::_get(const StringName &p_name, Variant &r_ret) const { r_ret = get_item_name(idx); } else if (what == "mesh") { r_ret = get_item_mesh(idx); + } else if (what == "mesh_transform") { + r_ret = get_item_mesh_transform(idx); } else if (what == "shapes") { r_ret = _get_item_shapes(idx); } else if (what == "navmesh") { @@ -127,6 +131,14 @@ void MeshLibrary::set_item_mesh(int p_item, const Ref<Mesh> &p_mesh) { notify_property_list_changed(); } +void MeshLibrary::set_item_mesh_transform(int p_item, const Transform3D &p_transform) { + ERR_FAIL_COND_MSG(!item_map.has(p_item), "Requested for nonexistent MeshLibrary item '" + itos(p_item) + "'."); + item_map[p_item].mesh_transform = p_transform; + notify_change_to_owners(); + emit_changed(); + notify_property_list_changed(); +} + void MeshLibrary::set_item_shapes(int p_item, const Vector<ShapeData> &p_shapes) { ERR_FAIL_COND_MSG(!item_map.has(p_item), "Requested for nonexistent MeshLibrary item '" + itos(p_item) + "'."); item_map[p_item].shapes = p_shapes; @@ -170,6 +182,11 @@ Ref<Mesh> MeshLibrary::get_item_mesh(int p_item) const { return item_map[p_item].mesh; } +Transform3D MeshLibrary::get_item_mesh_transform(int p_item) const { + ERR_FAIL_COND_V_MSG(!item_map.has(p_item), Transform3D(), "Requested for nonexistent MeshLibrary item '" + itos(p_item) + "'."); + return item_map[p_item].mesh_transform; +} + Vector<MeshLibrary::ShapeData> MeshLibrary::get_item_shapes(int p_item) const { ERR_FAIL_COND_V_MSG(!item_map.has(p_item), Vector<ShapeData>(), "Requested for nonexistent MeshLibrary item '" + itos(p_item) + "'."); return item_map[p_item].shapes; @@ -271,12 +288,14 @@ void MeshLibrary::_bind_methods() { ClassDB::bind_method(D_METHOD("create_item", "id"), &MeshLibrary::create_item); ClassDB::bind_method(D_METHOD("set_item_name", "id", "name"), &MeshLibrary::set_item_name); ClassDB::bind_method(D_METHOD("set_item_mesh", "id", "mesh"), &MeshLibrary::set_item_mesh); + ClassDB::bind_method(D_METHOD("set_item_mesh_transform", "id", "mesh_transform"), &MeshLibrary::set_item_mesh_transform); ClassDB::bind_method(D_METHOD("set_item_navmesh", "id", "navmesh"), &MeshLibrary::set_item_navmesh); ClassDB::bind_method(D_METHOD("set_item_navmesh_transform", "id", "navmesh"), &MeshLibrary::set_item_navmesh_transform); ClassDB::bind_method(D_METHOD("set_item_shapes", "id", "shapes"), &MeshLibrary::_set_item_shapes); ClassDB::bind_method(D_METHOD("set_item_preview", "id", "texture"), &MeshLibrary::set_item_preview); ClassDB::bind_method(D_METHOD("get_item_name", "id"), &MeshLibrary::get_item_name); ClassDB::bind_method(D_METHOD("get_item_mesh", "id"), &MeshLibrary::get_item_mesh); + ClassDB::bind_method(D_METHOD("get_item_mesh_transform", "id"), &MeshLibrary::get_item_mesh_transform); ClassDB::bind_method(D_METHOD("get_item_navmesh", "id"), &MeshLibrary::get_item_navmesh); ClassDB::bind_method(D_METHOD("get_item_navmesh_transform", "id"), &MeshLibrary::get_item_navmesh_transform); ClassDB::bind_method(D_METHOD("get_item_shapes", "id"), &MeshLibrary::_get_item_shapes); diff --git a/scene/resources/mesh_library.h b/scene/resources/mesh_library.h index 1e8a6bf3ff..c25df757e9 100644 --- a/scene/resources/mesh_library.h +++ b/scene/resources/mesh_library.h @@ -52,6 +52,7 @@ public: Vector<ShapeData> shapes; Ref<Texture2D> preview; Transform3D navmesh_transform; + Transform3D mesh_transform; Ref<NavigationMesh> navmesh; }; @@ -72,12 +73,14 @@ public: void create_item(int p_item); void set_item_name(int p_item, const String &p_name); void set_item_mesh(int p_item, const Ref<Mesh> &p_mesh); + void set_item_mesh_transform(int p_item, const Transform3D &p_transform); void set_item_navmesh(int p_item, const Ref<NavigationMesh> &p_navmesh); void set_item_navmesh_transform(int p_item, const Transform3D &p_transform); void set_item_shapes(int p_item, const Vector<ShapeData> &p_shapes); void set_item_preview(int p_item, const Ref<Texture2D> &p_preview); String get_item_name(int p_item) const; Ref<Mesh> get_item_mesh(int p_item) const; + Transform3D get_item_mesh_transform(int p_item) const; Ref<NavigationMesh> get_item_navmesh(int p_item) const; Transform3D get_item_navmesh_transform(int p_item) const; Vector<ShapeData> get_item_shapes(int p_item) const; diff --git a/scene/resources/resource_format_text.cpp b/scene/resources/resource_format_text.cpp index b863a309c0..341ce22185 100644 --- a/scene/resources/resource_format_text.cpp +++ b/scene/resources/resource_format_text.cpp @@ -1849,10 +1849,16 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const RES &p_r } if (groups.size()) { + // Write all groups on the same line as they're part of a section header. + // This improves readability while not impacting VCS friendliness too much, + // since it's rare to have more than 5 groups assigned to a single node. groups.sort_custom<StringName::AlphCompare>(); - String sgroups = " groups=[\n"; + String sgroups = " groups=["; for (int j = 0; j < groups.size(); j++) { - sgroups += "\"" + String(groups[j]).c_escape() + "\",\n"; + sgroups += "\"" + String(groups[j]).c_escape() + "\""; + if (j < groups.size() - 1) { + sgroups += ", "; + } } sgroups += "]"; header += sgroups; diff --git a/scene/resources/surface_tool.cpp b/scene/resources/surface_tool.cpp index 875aa30824..d5e370568d 100644 --- a/scene/resources/surface_tool.cpp +++ b/scene/resources/surface_tool.cpp @@ -409,7 +409,7 @@ Array SurfaceTool::commit_to_arrays() { for (uint32_t idx = 0; idx < vertex_array.size(); idx++) { const Vertex &v = vertex_array[idx]; - const Color &c = v.custom[idx]; + const Color &c = v.custom[fmt]; w[idx * 4 + 0] = CLAMP(int32_t(c.r * 255.0), 0, 255); w[idx * 4 + 1] = CLAMP(int32_t(c.g * 255.0), 0, 255); w[idx * 4 + 2] = CLAMP(int32_t(c.b * 255.0), 0, 255); @@ -426,7 +426,7 @@ Array SurfaceTool::commit_to_arrays() { for (uint32_t idx = 0; idx < vertex_array.size(); idx++) { const Vertex &v = vertex_array[idx]; - const Color &c = v.custom[idx]; + const Color &c = v.custom[fmt]; w[idx * 4 + 0] = uint8_t(int8_t(CLAMP(int32_t(c.r * 127.0), -128, 127))); w[idx * 4 + 1] = uint8_t(int8_t(CLAMP(int32_t(c.g * 127.0), -128, 127))); w[idx * 4 + 2] = uint8_t(int8_t(CLAMP(int32_t(c.b * 127.0), -128, 127))); @@ -443,7 +443,7 @@ Array SurfaceTool::commit_to_arrays() { for (uint32_t idx = 0; idx < vertex_array.size(); idx++) { const Vertex &v = vertex_array[idx]; - const Color &c = v.custom[idx]; + const Color &c = v.custom[fmt]; w[idx * 2 + 0] = Math::make_half_float(c.r); w[idx * 2 + 1] = Math::make_half_float(c.g); } @@ -458,7 +458,7 @@ Array SurfaceTool::commit_to_arrays() { for (uint32_t idx = 0; idx < vertex_array.size(); idx++) { const Vertex &v = vertex_array[idx]; - const Color &c = v.custom[idx]; + const Color &c = v.custom[fmt]; w[idx * 4 + 0] = Math::make_half_float(c.r); w[idx * 4 + 1] = Math::make_half_float(c.g); w[idx * 4 + 2] = Math::make_half_float(c.b); @@ -475,7 +475,7 @@ Array SurfaceTool::commit_to_arrays() { for (uint32_t idx = 0; idx < vertex_array.size(); idx++) { const Vertex &v = vertex_array[idx]; - const Color &c = v.custom[idx]; + const Color &c = v.custom[fmt]; w[idx] = c.r; } @@ -489,7 +489,7 @@ Array SurfaceTool::commit_to_arrays() { for (uint32_t idx = 0; idx < vertex_array.size(); idx++) { const Vertex &v = vertex_array[idx]; - const Color &c = v.custom[idx]; + const Color &c = v.custom[fmt]; w[idx * 2 + 0] = c.r; w[idx * 2 + 1] = c.g; } @@ -504,7 +504,7 @@ Array SurfaceTool::commit_to_arrays() { for (uint32_t idx = 0; idx < vertex_array.size(); idx++) { const Vertex &v = vertex_array[idx]; - const Color &c = v.custom[idx]; + const Color &c = v.custom[fmt]; w[idx * 3 + 0] = c.r; w[idx * 3 + 1] = c.g; w[idx * 3 + 2] = c.b; @@ -520,7 +520,7 @@ Array SurfaceTool::commit_to_arrays() { for (uint32_t idx = 0; idx < vertex_array.size(); idx++) { const Vertex &v = vertex_array[idx]; - const Color &c = v.custom[idx]; + const Color &c = v.custom[fmt]; w[idx * 4 + 0] = c.r; w[idx * 4 + 1] = c.g; w[idx * 4 + 2] = c.b; @@ -679,6 +679,9 @@ void SurfaceTool::_create_list(const Ref<Mesh> &p_existing, int p_surface, Local _create_list_from_arrays(arr, r_vertex, r_index, lformat); } +static const uint32_t custom_mask[RS::ARRAY_CUSTOM_COUNT] = { Mesh::ARRAY_FORMAT_CUSTOM0, Mesh::ARRAY_FORMAT_CUSTOM1, Mesh::ARRAY_FORMAT_CUSTOM2, Mesh::ARRAY_FORMAT_CUSTOM3 }; +static const uint32_t custom_shift[RS::ARRAY_CUSTOM_COUNT] = { Mesh::ARRAY_FORMAT_CUSTOM0_SHIFT, Mesh::ARRAY_FORMAT_CUSTOM1_SHIFT, Mesh::ARRAY_FORMAT_CUSTOM2_SHIFT, Mesh::ARRAY_FORMAT_CUSTOM3_SHIFT }; + void SurfaceTool::create_vertex_array_from_triangle_arrays(const Array &p_arrays, LocalVector<SurfaceTool::Vertex> &ret, uint32_t *r_format) { ret.clear(); @@ -733,8 +736,6 @@ void SurfaceTool::create_vertex_array_from_triangle_arrays(const Array &p_arrays if (warr.size()) { lformat |= RS::ARRAY_FORMAT_WEIGHTS; } - static const uint32_t custom_mask[RS::ARRAY_CUSTOM_COUNT] = { Mesh::ARRAY_FORMAT_CUSTOM0, Mesh::ARRAY_FORMAT_CUSTOM1, Mesh::ARRAY_FORMAT_CUSTOM2, Mesh::ARRAY_FORMAT_CUSTOM3 }; - static const uint32_t custom_shift[RS::ARRAY_CUSTOM_COUNT] = { Mesh::ARRAY_FORMAT_CUSTOM0_SHIFT, Mesh::ARRAY_FORMAT_CUSTOM1_SHIFT, Mesh::ARRAY_FORMAT_CUSTOM2_SHIFT, Mesh::ARRAY_FORMAT_CUSTOM3_SHIFT }; for (int i = 0; i < RS::ARRAY_CUSTOM_COUNT; i++) { ERR_CONTINUE_MSG(p_arrays[RS::ARRAY_CUSTOM0 + i].get_type() == Variant::PACKED_BYTE_ARRAY, "Extracting Byte/Half formats is not supported"); @@ -832,6 +833,12 @@ void SurfaceTool::create_from_triangle_arrays(const Array &p_arrays) { clear(); primitive = Mesh::PRIMITIVE_TRIANGLES; _create_list_from_arrays(p_arrays, &vertex_array, &index_array, format); + + for (int j = 0; j < RS::ARRAY_CUSTOM_COUNT; j++) { + if (format & custom_mask[j]) { + last_custom_format[j] = (CustomFormat)((format >> custom_shift[j]) & RS::ARRAY_FORMAT_CUSTOM_MASK); + } + } } void SurfaceTool::create_from(const Ref<Mesh> &p_existing, int p_surface) { @@ -841,6 +848,12 @@ void SurfaceTool::create_from(const Ref<Mesh> &p_existing, int p_surface) { primitive = p_existing->surface_get_primitive_type(p_surface); _create_list(p_existing, p_surface, &vertex_array, &index_array, format); material = p_existing->surface_get_material(p_surface); + + for (int j = 0; j < RS::ARRAY_CUSTOM_COUNT; j++) { + if (format & custom_mask[j]) { + last_custom_format[j] = (CustomFormat)((format >> custom_shift[j]) & RS::ARRAY_FORMAT_CUSTOM_MASK); + } + } } void SurfaceTool::create_from_blend_shape(const Ref<Mesh> &p_existing, int p_surface, const String &p_blend_shape_name) { @@ -863,6 +876,12 @@ void SurfaceTool::create_from_blend_shape(const Ref<Mesh> &p_existing, int p_sur Array mesh = arr[shape_idx]; ERR_FAIL_COND(mesh.size() != RS::ARRAY_MAX); _create_list_from_arrays(arr[shape_idx], &vertex_array, &index_array, format); + + for (int j = 0; j < RS::ARRAY_CUSTOM_COUNT; j++) { + if (format & custom_mask[j]) { + last_custom_format[j] = (CustomFormat)((format >> custom_shift[j]) & RS::ARRAY_FORMAT_CUSTOM_MASK); + } + } } void SurfaceTool::append_from(const Ref<Mesh> &p_existing, int p_surface, const Transform3D &p_xform) { @@ -878,6 +897,16 @@ void SurfaceTool::append_from(const Ref<Mesh> &p_existing, int p_surface, const LocalVector<int> nindices; _create_list(p_existing, p_surface, &nvertices, &nindices, nformat); format |= nformat; + + for (int j = 0; j < RS::ARRAY_CUSTOM_COUNT; j++) { + if (format & custom_mask[j]) { + CustomFormat new_format = (CustomFormat)((format >> custom_shift[j]) & RS::ARRAY_FORMAT_CUSTOM_MASK); + if (last_custom_format[j] != CUSTOM_MAX && last_custom_format[j] != new_format) { + WARN_PRINT(vformat("Custom %d format %d mismatch when appending format %d", j, last_custom_format[j], new_format)); + } + last_custom_format[j] = new_format; + } + } int vfrom = vertex_array.size(); for (uint32_t vi = 0; vi < nvertices.size(); vi++) { diff --git a/scene/resources/tile_set.cpp b/scene/resources/tile_set.cpp index fcd31143a8..e288e18f33 100644 --- a/scene/resources/tile_set.cpp +++ b/scene/resources/tile_set.cpp @@ -205,25 +205,46 @@ bool TileSet::is_uv_clipping() const { return uv_clipping; }; -void TileSet::set_occlusion_layers_count(int p_occlusion_layers_count) { - ERR_FAIL_COND(p_occlusion_layers_count < 0); - if (occlusion_layers.size() == p_occlusion_layers_count) { - return; - } +int TileSet::get_occlusion_layers_count() const { + return occlusion_layers.size(); +}; - occlusion_layers.resize(p_occlusion_layers_count); +void TileSet::add_occlusion_layer(int p_index) { + if (p_index < 0) { + p_index = occlusion_layers.size(); + } + ERR_FAIL_INDEX(p_index, occlusion_layers.size() + 1); + occlusion_layers.insert(p_index, OcclusionLayer()); - for (Map<int, Ref<TileSetSource>>::Element *E_source = sources.front(); E_source; E_source = E_source->next()) { - E_source->get()->notify_tile_data_properties_should_change(); + for (KeyValue<int, Ref<TileSetSource>> source : sources) { + source.value->add_occlusion_layer(p_index); } notify_property_list_changed(); emit_changed(); } -int TileSet::get_occlusion_layers_count() const { - return occlusion_layers.size(); -}; +void TileSet::move_occlusion_layer(int p_from_index, int p_to_pos) { + ERR_FAIL_INDEX(p_from_index, occlusion_layers.size()); + ERR_FAIL_INDEX(p_to_pos, occlusion_layers.size() + 1); + occlusion_layers.insert(p_to_pos, occlusion_layers[p_from_index]); + occlusion_layers.remove(p_to_pos < p_from_index ? p_from_index + 1 : p_from_index); + for (KeyValue<int, Ref<TileSetSource>> source : sources) { + source.value->move_occlusion_layer(p_from_index, p_to_pos); + } + notify_property_list_changed(); + emit_changed(); +} + +void TileSet::remove_occlusion_layer(int p_index) { + ERR_FAIL_INDEX(p_index, occlusion_layers.size()); + occlusion_layers.remove(p_index); + for (KeyValue<int, Ref<TileSetSource>> source : sources) { + source.value->remove_occlusion_layer(p_index); + } + notify_property_list_changed(); + emit_changed(); +} void TileSet::set_occlusion_layer_light_mask(int p_layer_index, int p_light_mask) { ERR_FAIL_INDEX(p_layer_index, occlusion_layers.size()); @@ -236,7 +257,7 @@ int TileSet::get_occlusion_layer_light_mask(int p_layer_index) const { return occlusion_layers[p_layer_index].light_mask; } -void TileSet::set_occlusion_layer_sdf_collision(int p_layer_index, int p_sdf_collision) { +void TileSet::set_occlusion_layer_sdf_collision(int p_layer_index, bool p_sdf_collision) { ERR_FAIL_INDEX(p_layer_index, occlusion_layers.size()); occlusion_layers.write[p_layer_index].sdf_collision = p_sdf_collision; emit_changed(); @@ -247,25 +268,45 @@ bool TileSet::get_occlusion_layer_sdf_collision(int p_layer_index) const { return occlusion_layers[p_layer_index].sdf_collision; } -// Physics -void TileSet::set_physics_layers_count(int p_physics_layers_count) { - ERR_FAIL_COND(p_physics_layers_count < 0); - if (physics_layers.size() == p_physics_layers_count) { - return; - } +int TileSet::get_physics_layers_count() const { + return physics_layers.size(); +} - physics_layers.resize(p_physics_layers_count); +void TileSet::add_physics_layer(int p_index) { + if (p_index < 0) { + p_index = physics_layers.size(); + } + ERR_FAIL_INDEX(p_index, physics_layers.size() + 1); + physics_layers.insert(p_index, PhysicsLayer()); - for (Map<int, Ref<TileSetSource>>::Element *E_source = sources.front(); E_source; E_source = E_source->next()) { - E_source->get()->notify_tile_data_properties_should_change(); + for (KeyValue<int, Ref<TileSetSource>> source : sources) { + source.value->add_physics_layer(p_index); } notify_property_list_changed(); emit_changed(); } -int TileSet::get_physics_layers_count() const { - return physics_layers.size(); +void TileSet::move_physics_layer(int p_from_index, int p_to_pos) { + ERR_FAIL_INDEX(p_from_index, physics_layers.size()); + ERR_FAIL_INDEX(p_to_pos, physics_layers.size() + 1); + physics_layers.insert(p_to_pos, physics_layers[p_from_index]); + physics_layers.remove(p_to_pos < p_from_index ? p_from_index + 1 : p_from_index); + for (KeyValue<int, Ref<TileSetSource>> source : sources) { + source.value->move_physics_layer(p_from_index, p_to_pos); + } + notify_property_list_changed(); + emit_changed(); +} + +void TileSet::remove_physics_layer(int p_index) { + ERR_FAIL_INDEX(p_index, physics_layers.size()); + physics_layers.remove(p_index); + for (KeyValue<int, Ref<TileSetSource>> source : sources) { + source.value->remove_physics_layer(p_index); + } + notify_property_list_changed(); + emit_changed(); } void TileSet::set_physics_layer_collision_layer(int p_layer_index, uint32_t p_layer) { @@ -301,17 +342,45 @@ Ref<PhysicsMaterial> TileSet::get_physics_layer_physics_material(int p_layer_ind } // Terrains -void TileSet::set_terrain_sets_count(int p_terrains_sets_count) { - ERR_FAIL_COND(p_terrains_sets_count < 0); +int TileSet::get_terrain_sets_count() const { + return terrain_sets.size(); +} - terrain_sets.resize(p_terrains_sets_count); +void TileSet::add_terrain_set(int p_index) { + if (p_index < 0) { + p_index = terrain_sets.size(); + } + ERR_FAIL_INDEX(p_index, terrain_sets.size() + 1); + terrain_sets.insert(p_index, TerrainSet()); + + for (KeyValue<int, Ref<TileSetSource>> source : sources) { + source.value->add_terrain_set(p_index); + } notify_property_list_changed(); emit_changed(); } -int TileSet::get_terrain_sets_count() const { - return terrain_sets.size(); +void TileSet::move_terrain_set(int p_from_index, int p_to_pos) { + ERR_FAIL_INDEX(p_from_index, terrain_sets.size()); + ERR_FAIL_INDEX(p_to_pos, terrain_sets.size() + 1); + terrain_sets.insert(p_to_pos, terrain_sets[p_from_index]); + terrain_sets.remove(p_to_pos < p_from_index ? p_from_index + 1 : p_from_index); + for (KeyValue<int, Ref<TileSetSource>> source : sources) { + source.value->move_terrain_set(p_from_index, p_to_pos); + } + notify_property_list_changed(); + emit_changed(); +} + +void TileSet::remove_terrain_set(int p_index) { + ERR_FAIL_INDEX(p_index, terrain_sets.size()); + terrain_sets.remove(p_index); + for (KeyValue<int, Ref<TileSetSource>> source : sources) { + source.value->remove_terrain_set(p_index); + } + notify_property_list_changed(); + emit_changed(); } void TileSet::set_terrain_set_mode(int p_terrain_set, TerrainMode p_terrain_mode) { @@ -330,36 +399,61 @@ TileSet::TerrainMode TileSet::get_terrain_set_mode(int p_terrain_set) const { return terrain_sets[p_terrain_set].mode; } -void TileSet::set_terrains_count(int p_terrain_set, int p_terrains_layers_count) { +int TileSet::get_terrains_count(int p_terrain_set) const { + ERR_FAIL_INDEX_V(p_terrain_set, terrain_sets.size(), -1); + return terrain_sets[p_terrain_set].terrains.size(); +} + +void TileSet::add_terrain(int p_terrain_set, int p_index) { ERR_FAIL_INDEX(p_terrain_set, terrain_sets.size()); - ERR_FAIL_COND(p_terrains_layers_count < 0); - if (terrain_sets[p_terrain_set].terrains.size() == p_terrains_layers_count) { - return; + Vector<Terrain> &terrains = terrain_sets.write[p_terrain_set].terrains; + if (p_index < 0) { + p_index = terrains.size(); } - - int old_size = terrain_sets[p_terrain_set].terrains.size(); - terrain_sets.write[p_terrain_set].terrains.resize(p_terrains_layers_count); + ERR_FAIL_INDEX(p_index, terrains.size() + 1); + terrains.insert(p_index, Terrain()); // Default name and color - for (int i = old_size; i < terrain_sets.write[p_terrain_set].terrains.size(); i++) { - float hue_rotate = (i * 2 % 16) / 16.0; - Color c; - c.set_hsv(Math::fmod(float(hue_rotate), float(1.0)), 0.5, 0.5); - terrain_sets.write[p_terrain_set].terrains.write[i].color = c; - terrain_sets.write[p_terrain_set].terrains.write[i].name = String(vformat("Terrain %d", i)); + float hue_rotate = (terrains.size() % 16) / 16.0; + Color c; + c.set_hsv(Math::fmod(float(hue_rotate), float(1.0)), 0.5, 0.5); + terrains.write[p_index].color = c; + terrains.write[p_index].name = String(vformat("Terrain %d", p_index)); + + for (KeyValue<int, Ref<TileSetSource>> source : sources) { + source.value->add_terrain(p_terrain_set, p_index); } - for (Map<int, Ref<TileSetSource>>::Element *E_source = sources.front(); E_source; E_source = E_source->next()) { - E_source->get()->notify_tile_data_properties_should_change(); - } + notify_property_list_changed(); + emit_changed(); +} +void TileSet::move_terrain(int p_terrain_set, int p_from_index, int p_to_pos) { + ERR_FAIL_INDEX(p_terrain_set, terrain_sets.size()); + Vector<Terrain> &terrains = terrain_sets.write[p_terrain_set].terrains; + + ERR_FAIL_INDEX(p_from_index, terrains.size()); + ERR_FAIL_INDEX(p_to_pos, terrains.size() + 1); + terrains.insert(p_to_pos, terrains[p_from_index]); + terrains.remove(p_to_pos < p_from_index ? p_from_index + 1 : p_from_index); + for (KeyValue<int, Ref<TileSetSource>> source : sources) { + source.value->move_terrain(p_terrain_set, p_from_index, p_to_pos); + } notify_property_list_changed(); emit_changed(); } -int TileSet::get_terrains_count(int p_terrain_set) const { - ERR_FAIL_INDEX_V(p_terrain_set, terrain_sets.size(), -1); - return terrain_sets[p_terrain_set].terrains.size(); +void TileSet::remove_terrain(int p_terrain_set, int p_index) { + ERR_FAIL_INDEX(p_terrain_set, terrain_sets.size()); + Vector<Terrain> &terrains = terrain_sets.write[p_terrain_set].terrains; + + ERR_FAIL_INDEX(p_index, terrains.size()); + terrains.remove(p_index); + for (KeyValue<int, Ref<TileSetSource>> source : sources) { + source.value->remove_terrain(p_terrain_set, p_index); + } + notify_property_list_changed(); + emit_changed(); } void TileSet::set_terrain_name(int p_terrain_set, int p_terrain_index, String p_name) { @@ -485,24 +579,45 @@ bool TileSet::is_valid_peering_bit_terrain(int p_terrain_set, TileSet::CellNeigh } // Navigation -void TileSet::set_navigation_layers_count(int p_navigation_layers_count) { - ERR_FAIL_COND(p_navigation_layers_count < 0); - if (navigation_layers.size() == p_navigation_layers_count) { - return; - } +int TileSet::get_navigation_layers_count() const { + return navigation_layers.size(); +} - navigation_layers.resize(p_navigation_layers_count); +void TileSet::add_navigation_layer(int p_index) { + if (p_index < 0) { + p_index = navigation_layers.size(); + } + ERR_FAIL_INDEX(p_index, navigation_layers.size() + 1); + navigation_layers.insert(p_index, NavigationLayer()); - for (Map<int, Ref<TileSetSource>>::Element *E_source = sources.front(); E_source; E_source = E_source->next()) { - E_source->get()->notify_tile_data_properties_should_change(); + for (KeyValue<int, Ref<TileSetSource>> source : sources) { + source.value->add_navigation_layer(p_index); } notify_property_list_changed(); emit_changed(); } -int TileSet::get_navigation_layers_count() const { - return navigation_layers.size(); +void TileSet::move_navigation_layer(int p_from_index, int p_to_pos) { + ERR_FAIL_INDEX(p_from_index, navigation_layers.size()); + ERR_FAIL_INDEX(p_to_pos, navigation_layers.size() + 1); + navigation_layers.insert(p_to_pos, navigation_layers[p_from_index]); + navigation_layers.remove(p_to_pos < p_from_index ? p_from_index + 1 : p_from_index); + for (KeyValue<int, Ref<TileSetSource>> source : sources) { + source.value->move_navigation_layer(p_from_index, p_to_pos); + } + notify_property_list_changed(); + emit_changed(); +} + +void TileSet::remove_navigation_layer(int p_index) { + ERR_FAIL_INDEX(p_index, navigation_layers.size()); + navigation_layers.remove(p_index); + for (KeyValue<int, Ref<TileSetSource>> source : sources) { + source.value->remove_navigation_layer(p_index); + } + notify_property_list_changed(); + emit_changed(); } void TileSet::set_navigation_layer_layers(int p_layer_index, uint32_t p_layers) { @@ -517,30 +632,52 @@ uint32_t TileSet::get_navigation_layer_layers(int p_layer_index) const { } // Custom data. -void TileSet::set_custom_data_layers_count(int p_custom_data_layers_count) { - ERR_FAIL_COND(p_custom_data_layers_count < 0); - if (custom_data_layers.size() == p_custom_data_layers_count) { - return; - } - - custom_data_layers.resize(p_custom_data_layers_count); +int TileSet::get_custom_data_layers_count() const { + return custom_data_layers.size(); +} - for (Map<String, int>::Element *E = custom_data_layers_by_name.front(); E; E = E->next()) { - if (E->get() >= custom_data_layers.size()) { - custom_data_layers_by_name.erase(E); - } +void TileSet::add_custom_data_layer(int p_index) { + if (p_index < 0) { + p_index = custom_data_layers.size(); } + ERR_FAIL_INDEX(p_index, custom_data_layers.size() + 1); + custom_data_layers.insert(p_index, CustomDataLayer()); - for (Map<int, Ref<TileSetSource>>::Element *E_source = sources.front(); E_source; E_source = E_source->next()) { - E_source->get()->notify_tile_data_properties_should_change(); + for (KeyValue<int, Ref<TileSetSource>> source : sources) { + source.value->add_custom_data_layer(p_index); } notify_property_list_changed(); emit_changed(); } -int TileSet::get_custom_data_layers_count() const { - return custom_data_layers.size(); +void TileSet::move_custom_data_layer(int p_from_index, int p_to_pos) { + ERR_FAIL_INDEX(p_from_index, custom_data_layers.size()); + ERR_FAIL_INDEX(p_to_pos, custom_data_layers.size() + 1); + custom_data_layers.insert(p_to_pos, custom_data_layers[p_from_index]); + custom_data_layers.remove(p_to_pos < p_from_index ? p_from_index + 1 : p_from_index); + for (KeyValue<int, Ref<TileSetSource>> source : sources) { + source.value->move_custom_data_layer(p_from_index, p_to_pos); + } + notify_property_list_changed(); + emit_changed(); +} + +void TileSet::remove_custom_data_layer(int p_index) { + ERR_FAIL_INDEX(p_index, custom_data_layers.size()); + custom_data_layers.remove(p_index); + for (KeyValue<String, int> E : custom_data_layers_by_name) { + if (E.value == p_index) { + custom_data_layers_by_name.erase(E.key); + break; + } + } + + for (KeyValue<int, Ref<TileSetSource>> source : sources) { + source.value->remove_custom_data_layer(p_index); + } + notify_property_list_changed(); + emit_changed(); } int TileSet::get_custom_data_layer_by_name(String p_value) const { @@ -1110,7 +1247,11 @@ Vector<Vector<Ref<Texture2D>>> TileSet::generate_terrains_icons(Size2i p_size) { if (is_valid_peering_bit_terrain(terrain_set, cell_neighbor)) { int terrain = tile_data->get_peering_bit_terrain(cell_neighbor); if (terrain >= 0) { - bit_counts[terrain] += 1; + if (terrain >= (int)bit_counts.size()) { + WARN_PRINT(vformat("Invalid peering bit terrain: %d", terrain)); + } else { + bit_counts[terrain] += 1; + } } } } @@ -1825,19 +1966,19 @@ void TileSet::_compatibility_conversion() { tile_data->set_flip_h(flip_h); tile_data->set_flip_v(flip_v); tile_data->set_transpose(transpose); - tile_data->tile_set_material(ctd->material); + tile_data->set_material(ctd->material); tile_data->set_modulate(ctd->modulate); tile_data->set_z_index(ctd->z_index); if (ctd->occluder.is_valid()) { if (get_occlusion_layers_count() < 1) { - set_occlusion_layers_count(1); + add_occlusion_layer(); } tile_data->set_occluder(0, ctd->occluder); } if (ctd->navigation.is_valid()) { if (get_navigation_layers_count() < 1) { - set_navigation_layers_count(1); + add_navigation_layer(); } tile_data->set_navigation_polygon(0, ctd->autotile_navpoly_map[coords]); } @@ -1847,7 +1988,7 @@ void TileSet::_compatibility_conversion() { // Add the shapes. if (ctd->shapes.size() > 0) { if (get_physics_layers_count() < 1) { - set_physics_layers_count(1); + add_physics_layer(); } } for (int k = 0; k < ctd->shapes.size(); k++) { @@ -1917,18 +2058,18 @@ void TileSet::_compatibility_conversion() { tile_data->set_flip_h(flip_h); tile_data->set_flip_v(flip_v); tile_data->set_transpose(transpose); - tile_data->tile_set_material(ctd->material); + tile_data->set_material(ctd->material); tile_data->set_modulate(ctd->modulate); tile_data->set_z_index(ctd->z_index); if (ctd->autotile_occluder_map.has(coords)) { if (get_occlusion_layers_count() < 1) { - set_occlusion_layers_count(1); + add_occlusion_layer(); } tile_data->set_occluder(0, ctd->autotile_occluder_map[coords]); } if (ctd->autotile_navpoly_map.has(coords)) { if (get_navigation_layers_count() < 1) { - set_navigation_layers_count(1); + add_navigation_layer(); } tile_data->set_navigation_polygon(0, ctd->autotile_navpoly_map[coords]); } @@ -1942,7 +2083,7 @@ void TileSet::_compatibility_conversion() { // Add the shapes. if (ctd->shapes.size() > 0) { if (get_physics_layers_count() < 1) { - set_physics_layers_count(1); + add_physics_layer(); } } for (int k = 0; k < ctd->shapes.size(); k++) { @@ -2206,15 +2347,15 @@ bool TileSet::_set(const StringName &p_name, const Variant &p_value) { ERR_FAIL_COND_V(index < 0, false); if (components[1] == "light_mask") { ERR_FAIL_COND_V(p_value.get_type() != Variant::INT, false); - if (index >= occlusion_layers.size()) { - set_occlusion_layers_count(index + 1); + while (index >= occlusion_layers.size()) { + add_occlusion_layer(); } set_occlusion_layer_light_mask(index, p_value); return true; } else if (components[1] == "sdf_collision") { ERR_FAIL_COND_V(p_value.get_type() != Variant::BOOL, false); - if (index >= occlusion_layers.size()) { - set_occlusion_layers_count(index + 1); + while (index >= occlusion_layers.size()) { + add_occlusion_layer(); } set_occlusion_layer_sdf_collision(index, p_value); return true; @@ -2225,23 +2366,22 @@ bool TileSet::_set(const StringName &p_name, const Variant &p_value) { ERR_FAIL_COND_V(index < 0, false); if (components[1] == "collision_layer") { ERR_FAIL_COND_V(p_value.get_type() != Variant::INT, false); - if (index >= physics_layers.size()) { - set_physics_layers_count(index + 1); + while (index >= physics_layers.size()) { + add_physics_layer(); } set_physics_layer_collision_layer(index, p_value); return true; } else if (components[1] == "collision_mask") { ERR_FAIL_COND_V(p_value.get_type() != Variant::INT, false); - if (index >= physics_layers.size()) { - set_physics_layers_count(index + 1); + while (index >= physics_layers.size()) { + add_physics_layer(); } set_physics_layer_collision_mask(index, p_value); return true; } else if (components[1] == "physics_material") { Ref<PhysicsMaterial> physics_material = p_value; - ERR_FAIL_COND_V(!physics_material.is_valid(), false); - if (index >= physics_layers.size()) { - set_physics_layers_count(index + 1); + while (index >= physics_layers.size()) { + add_physics_layer(); } set_physics_layer_physics_material(index, physics_material); return true; @@ -2252,37 +2392,30 @@ bool TileSet::_set(const StringName &p_name, const Variant &p_value) { ERR_FAIL_COND_V(terrain_set_index < 0, false); if (components[1] == "mode") { ERR_FAIL_COND_V(p_value.get_type() != Variant::INT, false); - if (terrain_set_index >= terrain_sets.size()) { - set_terrain_sets_count(terrain_set_index + 1); + while (terrain_set_index >= terrain_sets.size()) { + add_terrain_set(); } set_terrain_set_mode(terrain_set_index, TerrainMode(int(p_value))); - } else if (components[1] == "terrains_count") { - ERR_FAIL_COND_V(p_value.get_type() != Variant::INT, false); - if (terrain_set_index >= terrain_sets.size()) { - set_terrain_sets_count(terrain_set_index + 1); - } - set_terrains_count(terrain_set_index, p_value); - return true; } else if (components.size() >= 3 && components[1].begins_with("terrain_") && components[1].trim_prefix("terrain_").is_valid_int()) { int terrain_index = components[1].trim_prefix("terrain_").to_int(); ERR_FAIL_COND_V(terrain_index < 0, false); if (components[2] == "name") { ERR_FAIL_COND_V(p_value.get_type() != Variant::STRING, false); - if (terrain_set_index >= terrain_sets.size()) { - set_terrain_sets_count(terrain_set_index + 1); + while (terrain_set_index >= terrain_sets.size()) { + add_terrain_set(); } - if (terrain_index >= terrain_sets[terrain_set_index].terrains.size()) { - set_terrains_count(terrain_set_index, terrain_index + 1); + while (terrain_index >= terrain_sets[terrain_set_index].terrains.size()) { + add_terrain(terrain_set_index); } set_terrain_name(terrain_set_index, terrain_index, p_value); return true; } else if (components[2] == "color") { ERR_FAIL_COND_V(p_value.get_type() != Variant::COLOR, false); - if (terrain_set_index >= terrain_sets.size()) { - set_terrain_sets_count(terrain_set_index + 1); + while (terrain_set_index >= terrain_sets.size()) { + add_terrain_set(); } - if (terrain_index >= terrain_sets[terrain_set_index].terrains.size()) { - set_terrains_count(terrain_set_index, terrain_index + 1); + while (terrain_index >= terrain_sets[terrain_set_index].terrains.size()) { + add_terrain(terrain_set_index); } set_terrain_color(terrain_set_index, terrain_index, p_value); return true; @@ -2294,8 +2427,8 @@ bool TileSet::_set(const StringName &p_name, const Variant &p_value) { ERR_FAIL_COND_V(index < 0, false); if (components[1] == "layers") { ERR_FAIL_COND_V(p_value.get_type() != Variant::INT, false); - if (index >= navigation_layers.size()) { - set_navigation_layers_count(index + 1); + while (index >= navigation_layers.size()) { + add_navigation_layer(); } set_navigation_layer_layers(index, p_value); return true; @@ -2306,15 +2439,15 @@ bool TileSet::_set(const StringName &p_name, const Variant &p_value) { ERR_FAIL_COND_V(index < 0, false); if (components[1] == "name") { ERR_FAIL_COND_V(p_value.get_type() != Variant::STRING, false); - if (index >= custom_data_layers.size()) { - set_custom_data_layers_count(index + 1); + while (index >= custom_data_layers.size()) { + add_custom_data_layer(); } set_custom_data_name(index, p_value); return true; } else if (components[1] == "type") { ERR_FAIL_COND_V(p_value.get_type() != Variant::INT, false); - if (index >= custom_data_layers.size()) { - set_custom_data_layers_count(index + 1); + while (index >= custom_data_layers.size()) { + add_custom_data_layer(); } set_custom_data_type(index, Variant::Type(int(p_value))); return true; @@ -2402,9 +2535,6 @@ bool TileSet::_get(const StringName &p_name, Variant &r_ret) const { if (components[1] == "mode") { r_ret = get_terrain_set_mode(terrain_set_index); return true; - } else if (components[1] == "terrains_count") { - r_ret = get_terrains_count(terrain_set_index); - return true; } else if (components.size() >= 3 && components[1].begins_with("terrain_") && components[1].trim_prefix("terrain_").is_valid_int()) { int terrain_index = components[1].trim_prefix("terrain_").to_int(); if (terrain_index < 0 || terrain_index >= terrain_sets[terrain_set_index].terrains.size()) { @@ -2522,7 +2652,7 @@ void TileSet::_get_property_list(List<PropertyInfo> *p_list) const { p_list->push_back(PropertyInfo(Variant::NIL, "Terrains", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_GROUP)); for (int terrain_set_index = 0; terrain_set_index < terrain_sets.size(); terrain_set_index++) { p_list->push_back(PropertyInfo(Variant::INT, vformat("terrain_set_%d/mode", terrain_set_index), PROPERTY_HINT_ENUM, "Match corners and sides,Match corners,Match sides")); - p_list->push_back(PropertyInfo(Variant::INT, vformat("terrain_set_%d/terrains_count", terrain_set_index), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR)); + p_list->push_back(PropertyInfo(Variant::NIL, vformat("terrain_set_%d/terrains", terrain_set_index), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_ARRAY, vformat("terrain_set_%d/terrain_", terrain_set_index))); for (int terrain_index = 0; terrain_index < terrain_sets[terrain_set_index].terrains.size(); terrain_index++) { p_list->push_back(PropertyInfo(Variant::STRING, vformat("terrain_set_%d/terrain_%d/name", terrain_set_index, terrain_index))); p_list->push_back(PropertyInfo(Variant::COLOR, vformat("terrain_set_%d/terrain_%d/color", terrain_set_index, terrain_index))); @@ -2563,13 +2693,13 @@ void TileSet::_get_property_list(List<PropertyInfo> *p_list) const { void TileSet::_bind_methods() { // Sources management. ClassDB::bind_method(D_METHOD("get_next_source_id"), &TileSet::get_next_source_id); - ClassDB::bind_method(D_METHOD("add_source", "atlas_source_id_override"), &TileSet::add_source, DEFVAL(TileSet::INVALID_SOURCE)); + ClassDB::bind_method(D_METHOD("add_source", "source", "atlas_source_id_override"), &TileSet::add_source, DEFVAL(TileSet::INVALID_SOURCE)); ClassDB::bind_method(D_METHOD("remove_source", "source_id"), &TileSet::remove_source); - ClassDB::bind_method(D_METHOD("set_source_id", "source_id"), &TileSet::set_source_id); + ClassDB::bind_method(D_METHOD("set_source_id", "source_id", "new_source_id"), &TileSet::set_source_id); ClassDB::bind_method(D_METHOD("get_source_count"), &TileSet::get_source_count); ClassDB::bind_method(D_METHOD("get_source_id", "index"), &TileSet::get_source_id); - ClassDB::bind_method(D_METHOD("has_source", "index"), &TileSet::has_source); - ClassDB::bind_method(D_METHOD("get_source", "index"), &TileSet::get_source); + ClassDB::bind_method(D_METHOD("has_source", "source_id"), &TileSet::has_source); + ClassDB::bind_method(D_METHOD("get_source", "source_id"), &TileSet::get_source); // Shape and layout. ClassDB::bind_method(D_METHOD("set_tile_shape", "shape"), &TileSet::set_tile_shape); @@ -2590,16 +2720,20 @@ void TileSet::_bind_methods() { ClassDB::bind_method(D_METHOD("set_uv_clipping", "uv_clipping"), &TileSet::set_uv_clipping); ClassDB::bind_method(D_METHOD("is_uv_clipping"), &TileSet::is_uv_clipping); - ClassDB::bind_method(D_METHOD("set_occlusion_layers_count", "occlusion_layers_count"), &TileSet::set_occlusion_layers_count); ClassDB::bind_method(D_METHOD("get_occlusion_layers_count"), &TileSet::get_occlusion_layers_count); + ClassDB::bind_method(D_METHOD("add_occlusion_layer", "to_position"), &TileSet::add_occlusion_layer, DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("move_occlusion_layer", "layer_index", "to_position"), &TileSet::move_occlusion_layer); + ClassDB::bind_method(D_METHOD("remove_occlusion_layer", "layer_index"), &TileSet::remove_occlusion_layer); ClassDB::bind_method(D_METHOD("set_occlusion_layer_light_mask", "layer_index", "light_mask"), &TileSet::set_occlusion_layer_light_mask); - ClassDB::bind_method(D_METHOD("get_occlusion_layer_light_mask"), &TileSet::get_occlusion_layer_light_mask); + ClassDB::bind_method(D_METHOD("get_occlusion_layer_light_mask", "layer_index"), &TileSet::get_occlusion_layer_light_mask); ClassDB::bind_method(D_METHOD("set_occlusion_layer_sdf_collision", "layer_index", "sdf_collision"), &TileSet::set_occlusion_layer_sdf_collision); - ClassDB::bind_method(D_METHOD("get_occlusion_layer_sdf_collision"), &TileSet::get_occlusion_layer_sdf_collision); + ClassDB::bind_method(D_METHOD("get_occlusion_layer_sdf_collision", "layer_index"), &TileSet::get_occlusion_layer_sdf_collision); // Physics - ClassDB::bind_method(D_METHOD("set_physics_layers_count", "physics_layers_count"), &TileSet::set_physics_layers_count); ClassDB::bind_method(D_METHOD("get_physics_layers_count"), &TileSet::get_physics_layers_count); + ClassDB::bind_method(D_METHOD("add_physics_layer", "to_position"), &TileSet::add_physics_layer, DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("move_physics_layer", "layer_index", "to_position"), &TileSet::move_physics_layer); + ClassDB::bind_method(D_METHOD("remove_physics_layer", "layer_index"), &TileSet::remove_physics_layer); ClassDB::bind_method(D_METHOD("set_physics_layer_collision_layer", "layer_index", "layer"), &TileSet::set_physics_layer_collision_layer); ClassDB::bind_method(D_METHOD("get_physics_layer_collision_layer", "layer_index"), &TileSet::get_physics_layer_collision_layer); ClassDB::bind_method(D_METHOD("set_physics_layer_collision_mask", "layer_index", "mask"), &TileSet::set_physics_layer_collision_mask); @@ -2608,27 +2742,35 @@ void TileSet::_bind_methods() { ClassDB::bind_method(D_METHOD("get_physics_layer_physics_material", "layer_index"), &TileSet::get_physics_layer_physics_material); // Terrains - ClassDB::bind_method(D_METHOD("set_terrain_sets_count", "terrain_sets_count"), &TileSet::set_terrain_sets_count); ClassDB::bind_method(D_METHOD("get_terrain_sets_count"), &TileSet::get_terrain_sets_count); + ClassDB::bind_method(D_METHOD("add_terrain_set", "to_position"), &TileSet::add_terrain_set, DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("move_terrain_set", "terrain_set", "to_position"), &TileSet::move_terrain_set); + ClassDB::bind_method(D_METHOD("remove_terrain_set", "terrain_set"), &TileSet::remove_terrain_set); ClassDB::bind_method(D_METHOD("set_terrain_set_mode", "terrain_set", "mode"), &TileSet::set_terrain_set_mode); ClassDB::bind_method(D_METHOD("get_terrain_set_mode", "terrain_set"), &TileSet::get_terrain_set_mode); - ClassDB::bind_method(D_METHOD("set_terrains_count", "terrain_set", "terrains_count"), &TileSet::set_terrains_count); ClassDB::bind_method(D_METHOD("get_terrains_count", "terrain_set"), &TileSet::get_terrains_count); + ClassDB::bind_method(D_METHOD("add_terrain", "terrain_set", "to_position"), &TileSet::add_terrain, DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("move_terrain", "terrain_set", "terrain_index", "to_position"), &TileSet::move_terrain); + ClassDB::bind_method(D_METHOD("remove_terrain", "terrain_set", "terrain_index"), &TileSet::remove_terrain); ClassDB::bind_method(D_METHOD("set_terrain_name", "terrain_set", "terrain_index", "name"), &TileSet::set_terrain_name); ClassDB::bind_method(D_METHOD("get_terrain_name", "terrain_set", "terrain_index"), &TileSet::get_terrain_name); ClassDB::bind_method(D_METHOD("set_terrain_color", "terrain_set", "terrain_index", "color"), &TileSet::set_terrain_color); ClassDB::bind_method(D_METHOD("get_terrain_color", "terrain_set", "terrain_index"), &TileSet::get_terrain_color); // Navigation - ClassDB::bind_method(D_METHOD("set_navigation_layers_count", "navigation_layers_count"), &TileSet::set_navigation_layers_count); ClassDB::bind_method(D_METHOD("get_navigation_layers_count"), &TileSet::get_navigation_layers_count); + ClassDB::bind_method(D_METHOD("add_navigation_layer", "to_position"), &TileSet::add_navigation_layer, DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("move_navigation_layer", "layer_index", "to_position"), &TileSet::move_navigation_layer); + ClassDB::bind_method(D_METHOD("remove_navigation_layer", "layer_index"), &TileSet::remove_navigation_layer); ClassDB::bind_method(D_METHOD("set_navigation_layer_layers", "layer_index", "layers"), &TileSet::set_navigation_layer_layers); ClassDB::bind_method(D_METHOD("get_navigation_layer_layers", "layer_index"), &TileSet::get_navigation_layer_layers); // Custom data - ClassDB::bind_method(D_METHOD("set_custom_data_layers_count", "custom_data_layers_count"), &TileSet::set_custom_data_layers_count); ClassDB::bind_method(D_METHOD("get_custom_data_layers_count"), &TileSet::get_custom_data_layers_count); + ClassDB::bind_method(D_METHOD("add_custom_data_layer", "to_position"), &TileSet::add_custom_data_layer, DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("move_custom_data_layer", "layer_index", "to_position"), &TileSet::move_custom_data_layer); + ClassDB::bind_method(D_METHOD("remove_custom_data_layer", "layer_index"), &TileSet::remove_custom_data_layer); // Tile proxies ClassDB::bind_method(D_METHOD("set_source_level_tile_proxy", "source_from", "source_to"), &TileSet::set_source_level_tile_proxy); @@ -2653,19 +2795,19 @@ void TileSet::_bind_methods() { ADD_GROUP("Rendering", ""); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "uv_clipping"), "set_uv_clipping", "is_uv_clipping"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "occlusion_layers_count", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_occlusion_layers_count", "get_occlusion_layers_count"); + ADD_ARRAY("occlusion_layers", "occlusion_layer_"); ADD_GROUP("Physics", ""); - ADD_PROPERTY(PropertyInfo(Variant::INT, "physics_layers_count", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_physics_layers_count", "get_physics_layers_count"); + ADD_ARRAY("physics_layers", "physics_layer_"); ADD_GROUP("Terrains", ""); - ADD_PROPERTY(PropertyInfo(Variant::INT, "terrains_sets_count", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_terrain_sets_count", "get_terrain_sets_count"); + ADD_ARRAY("terrain_sets", "terrain_set_"); ADD_GROUP("Navigation", ""); - ADD_PROPERTY(PropertyInfo(Variant::INT, "navigation_layers_count", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_navigation_layers_count", "get_navigation_layers_count"); + ADD_ARRAY("navigation_layers", "navigation_layer_"); ADD_GROUP("Custom data", ""); - ADD_PROPERTY(PropertyInfo(Variant::INT, "custom_data_layers_count", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_custom_data_layers_count", "get_custom_data_layers_count"); + ADD_ARRAY("custom_data_layers", "custom_data_layer_"); // -- Enum binding -- BIND_ENUM_CONSTANT(TILE_SHAPE_SQUARE); @@ -2728,6 +2870,18 @@ void TileSetSource::set_tile_set(const TileSet *p_tile_set) { tile_set = p_tile_set; } +void TileSetSource::_bind_methods() { + // Base tiles + ClassDB::bind_method(D_METHOD("get_tiles_count"), &TileSetSource::get_tiles_count); + ClassDB::bind_method(D_METHOD("get_tile_id", "index"), &TileSetSource::get_tile_id); + ClassDB::bind_method(D_METHOD("has_tile", "atlas_coords"), &TileSetSource::has_tile); + + // Alternative tiles + ClassDB::bind_method(D_METHOD("get_alternative_tiles_count", "atlas_coords"), &TileSetSource::get_alternative_tiles_count); + ClassDB::bind_method(D_METHOD("get_alternative_tile_id", "atlas_coords", "index"), &TileSetSource::get_alternative_tile_id); + ClassDB::bind_method(D_METHOD("has_alternative_tile", "atlas_coords", "alternative_tile"), &TileSetSource::has_alternative_tile); +} + /////////////////////////////// TileSetAtlasSource ////////////////////////////////////// void TileSetAtlasSource::set_tile_set(const TileSet *p_tile_set) { @@ -2750,6 +2904,150 @@ void TileSetAtlasSource::notify_tile_data_properties_should_change() { } } +void TileSetAtlasSource::add_occlusion_layer(int p_to_pos) { + for (KeyValue<Vector2i, TileAlternativesData> E_tile : tiles) { + for (KeyValue<int, TileData *> E_alternative : E_tile.value.alternatives) { + E_alternative.value->add_occlusion_layer(p_to_pos); + } + } +} + +void TileSetAtlasSource::move_occlusion_layer(int p_from_index, int p_to_pos) { + for (KeyValue<Vector2i, TileAlternativesData> E_tile : tiles) { + for (KeyValue<int, TileData *> E_alternative : E_tile.value.alternatives) { + E_alternative.value->move_occlusion_layer(p_from_index, p_to_pos); + } + } +} + +void TileSetAtlasSource::remove_occlusion_layer(int p_index) { + for (KeyValue<Vector2i, TileAlternativesData> E_tile : tiles) { + for (KeyValue<int, TileData *> E_alternative : E_tile.value.alternatives) { + E_alternative.value->remove_occlusion_layer(p_index); + } + } +} + +void TileSetAtlasSource::add_physics_layer(int p_to_pos) { + for (KeyValue<Vector2i, TileAlternativesData> E_tile : tiles) { + for (KeyValue<int, TileData *> E_alternative : E_tile.value.alternatives) { + E_alternative.value->add_physics_layer(p_to_pos); + } + } +} + +void TileSetAtlasSource::move_physics_layer(int p_from_index, int p_to_pos) { + for (KeyValue<Vector2i, TileAlternativesData> E_tile : tiles) { + for (KeyValue<int, TileData *> E_alternative : E_tile.value.alternatives) { + E_alternative.value->move_physics_layer(p_from_index, p_to_pos); + } + } +} + +void TileSetAtlasSource::remove_physics_layer(int p_index) { + for (KeyValue<Vector2i, TileAlternativesData> E_tile : tiles) { + for (KeyValue<int, TileData *> E_alternative : E_tile.value.alternatives) { + E_alternative.value->remove_physics_layer(p_index); + } + } +} + +void TileSetAtlasSource::add_terrain_set(int p_to_pos) { + for (KeyValue<Vector2i, TileAlternativesData> E_tile : tiles) { + for (KeyValue<int, TileData *> E_alternative : E_tile.value.alternatives) { + E_alternative.value->add_terrain_set(p_to_pos); + } + } +} + +void TileSetAtlasSource::move_terrain_set(int p_from_index, int p_to_pos) { + for (KeyValue<Vector2i, TileAlternativesData> E_tile : tiles) { + for (KeyValue<int, TileData *> E_alternative : E_tile.value.alternatives) { + E_alternative.value->move_terrain_set(p_from_index, p_to_pos); + } + } +} + +void TileSetAtlasSource::remove_terrain_set(int p_index) { + for (KeyValue<Vector2i, TileAlternativesData> E_tile : tiles) { + for (KeyValue<int, TileData *> E_alternative : E_tile.value.alternatives) { + E_alternative.value->remove_terrain_set(p_index); + } + } +} + +void TileSetAtlasSource::add_terrain(int p_terrain_set, int p_to_pos) { + for (KeyValue<Vector2i, TileAlternativesData> E_tile : tiles) { + for (KeyValue<int, TileData *> E_alternative : E_tile.value.alternatives) { + E_alternative.value->add_terrain(p_terrain_set, p_to_pos); + } + } +} + +void TileSetAtlasSource::move_terrain(int p_terrain_set, int p_from_index, int p_to_pos) { + for (KeyValue<Vector2i, TileAlternativesData> E_tile : tiles) { + for (KeyValue<int, TileData *> E_alternative : E_tile.value.alternatives) { + E_alternative.value->move_terrain(p_terrain_set, p_from_index, p_to_pos); + } + } +} + +void TileSetAtlasSource::remove_terrain(int p_terrain_set, int p_index) { + for (KeyValue<Vector2i, TileAlternativesData> E_tile : tiles) { + for (KeyValue<int, TileData *> E_alternative : E_tile.value.alternatives) { + E_alternative.value->remove_terrain(p_terrain_set, p_index); + } + } +} + +void TileSetAtlasSource::add_navigation_layer(int p_to_pos) { + for (KeyValue<Vector2i, TileAlternativesData> E_tile : tiles) { + for (KeyValue<int, TileData *> E_alternative : E_tile.value.alternatives) { + E_alternative.value->add_navigation_layer(p_to_pos); + } + } +} + +void TileSetAtlasSource::move_navigation_layer(int p_from_index, int p_to_pos) { + for (KeyValue<Vector2i, TileAlternativesData> E_tile : tiles) { + for (KeyValue<int, TileData *> E_alternative : E_tile.value.alternatives) { + E_alternative.value->move_navigation_layer(p_from_index, p_to_pos); + } + } +} + +void TileSetAtlasSource::remove_navigation_layer(int p_index) { + for (KeyValue<Vector2i, TileAlternativesData> E_tile : tiles) { + for (KeyValue<int, TileData *> E_alternative : E_tile.value.alternatives) { + E_alternative.value->remove_navigation_layer(p_index); + } + } +} + +void TileSetAtlasSource::add_custom_data_layer(int p_to_pos) { + for (KeyValue<Vector2i, TileAlternativesData> E_tile : tiles) { + for (KeyValue<int, TileData *> E_alternative : E_tile.value.alternatives) { + E_alternative.value->add_custom_data_layer(p_to_pos); + } + } +} + +void TileSetAtlasSource::move_custom_data_layer(int p_from_index, int p_to_pos) { + for (KeyValue<Vector2i, TileAlternativesData> E_tile : tiles) { + for (KeyValue<int, TileData *> E_alternative : E_tile.value.alternatives) { + E_alternative.value->move_custom_data_layer(p_from_index, p_to_pos); + } + } +} + +void TileSetAtlasSource::remove_custom_data_layer(int p_index) { + for (KeyValue<Vector2i, TileAlternativesData> E_tile : tiles) { + for (KeyValue<int, TileData *> E_alternative : E_tile.value.alternatives) { + E_alternative.value->remove_custom_data_layer(p_index); + } + } +} + void TileSetAtlasSource::reset_state() { // Reset all TileData. for (Map<Vector2i, TileAlternativesData>::Element *E_tile = tiles.front(); E_tile; E_tile = E_tile->next()) { @@ -3273,32 +3571,24 @@ void TileSetAtlasSource::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D", PROPERTY_USAGE_NOEDITOR), "set_texture", "get_texture"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "margins", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_margins", "get_margins"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "separation", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_separation", "get_separation"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "tile_size", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_texture_region_size", "get_texture_region_size"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "texture_region_size", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_texture_region_size", "get_texture_region_size"); // Base tiles ClassDB::bind_method(D_METHOD("create_tile", "atlas_coords", "size"), &TileSetAtlasSource::create_tile, DEFVAL(Vector2i(1, 1))); ClassDB::bind_method(D_METHOD("remove_tile", "atlas_coords"), &TileSetAtlasSource::remove_tile); // Remove a tile. If p_tile_key.alternative_tile if different from 0, remove the alternative - ClassDB::bind_method(D_METHOD("has_tile", "atlas_coords"), &TileSetAtlasSource::has_tile); ClassDB::bind_method(D_METHOD("can_move_tile_in_atlas", "atlas_coords", "new_atlas_coords", "new_size"), &TileSetAtlasSource::can_move_tile_in_atlas, DEFVAL(INVALID_ATLAS_COORDS), DEFVAL(Vector2i(-1, -1))); ClassDB::bind_method(D_METHOD("move_tile_in_atlas", "atlas_coords", "new_atlas_coords", "new_size"), &TileSetAtlasSource::move_tile_in_atlas, DEFVAL(INVALID_ATLAS_COORDS), DEFVAL(Vector2i(-1, -1))); ClassDB::bind_method(D_METHOD("get_tile_size_in_atlas", "atlas_coords"), &TileSetAtlasSource::get_tile_size_in_atlas); - ClassDB::bind_method(D_METHOD("get_tiles_count"), &TileSetAtlasSource::get_tiles_count); - ClassDB::bind_method(D_METHOD("get_tile_id", "index"), &TileSetAtlasSource::get_tile_id); - ClassDB::bind_method(D_METHOD("get_tile_at_coords", "atlas_coords"), &TileSetAtlasSource::get_tile_at_coords); // Alternative tiles ClassDB::bind_method(D_METHOD("create_alternative_tile", "atlas_coords", "alternative_id_override"), &TileSetAtlasSource::create_alternative_tile, DEFVAL(INVALID_TILE_ALTERNATIVE)); ClassDB::bind_method(D_METHOD("remove_alternative_tile", "atlas_coords", "alternative_tile"), &TileSetAtlasSource::remove_alternative_tile); ClassDB::bind_method(D_METHOD("set_alternative_tile_id", "atlas_coords", "alternative_tile", "new_id"), &TileSetAtlasSource::set_alternative_tile_id); - ClassDB::bind_method(D_METHOD("has_alternative_tile", "atlas_coords", "alternative_tile"), &TileSetAtlasSource::has_alternative_tile); ClassDB::bind_method(D_METHOD("get_next_alternative_tile_id", "atlas_coords"), &TileSetAtlasSource::get_next_alternative_tile_id); - ClassDB::bind_method(D_METHOD("get_alternative_tiles_count", "atlas_coords"), &TileSetAtlasSource::get_alternative_tiles_count); - ClassDB::bind_method(D_METHOD("get_alternative_tile_id", "atlas_coords", "index"), &TileSetAtlasSource::get_alternative_tile_id); - - ClassDB::bind_method(D_METHOD("get_tile_data", "atlas_coords", "index"), &TileSetAtlasSource::get_tile_data); + ClassDB::bind_method(D_METHOD("get_tile_data", "atlas_coords", "alternative_tile"), &TileSetAtlasSource::get_tile_data); // Helpers. ClassDB::bind_method(D_METHOD("get_atlas_grid_size"), &TileSetAtlasSource::get_atlas_grid_size); @@ -3511,16 +3801,6 @@ void TileSetScenesCollectionSource::_get_property_list(List<PropertyInfo> *p_lis } void TileSetScenesCollectionSource::_bind_methods() { - // Base tiles - ClassDB::bind_method(D_METHOD("get_tiles_count"), &TileSetScenesCollectionSource::get_tiles_count); - ClassDB::bind_method(D_METHOD("get_tile_id", "index"), &TileSetScenesCollectionSource::get_tile_id); - ClassDB::bind_method(D_METHOD("has_tile", "atlas_coords"), &TileSetScenesCollectionSource::has_tile); - - // Alternative tiles - ClassDB::bind_method(D_METHOD("get_alternative_tiles_count", "atlas_coords"), &TileSetScenesCollectionSource::get_alternative_tiles_count); - ClassDB::bind_method(D_METHOD("get_alternative_tile_id", "atlas_coords", "index"), &TileSetScenesCollectionSource::get_alternative_tile_id); - ClassDB::bind_method(D_METHOD("has_alternative_tile", "atlas_coords", "alternative_tile"), &TileSetScenesCollectionSource::has_alternative_tile); - ClassDB::bind_method(D_METHOD("get_scene_tiles_count"), &TileSetScenesCollectionSource::get_scene_tiles_count); ClassDB::bind_method(D_METHOD("get_scene_tile_id", "index"), &TileSetScenesCollectionSource::get_scene_tile_id); ClassDB::bind_method(D_METHOD("has_scene_tile_id", "id"), &TileSetScenesCollectionSource::has_scene_tile_id); @@ -3575,6 +3855,155 @@ void TileData::notify_tile_data_properties_should_change() { emit_signal(SNAME("changed")); } +void TileData::add_occlusion_layer(int p_to_pos) { + if (p_to_pos < 0) { + p_to_pos = occluders.size(); + } + ERR_FAIL_INDEX(p_to_pos, occluders.size() + 1); + occluders.insert(p_to_pos, Ref<OccluderPolygon2D>()); +} + +void TileData::move_occlusion_layer(int p_from_index, int p_to_pos) { + ERR_FAIL_INDEX(p_from_index, occluders.size()); + ERR_FAIL_INDEX(p_to_pos, occluders.size() + 1); + occluders.insert(p_to_pos, occluders[p_from_index]); + occluders.remove(p_to_pos < p_from_index ? p_from_index + 1 : p_from_index); +} + +void TileData::remove_occlusion_layer(int p_index) { + ERR_FAIL_INDEX(p_index, occluders.size()); + occluders.remove(p_index); +} + +void TileData::add_physics_layer(int p_to_pos) { + if (p_to_pos < 0) { + p_to_pos = physics.size(); + } + ERR_FAIL_INDEX(p_to_pos, physics.size() + 1); + physics.insert(p_to_pos, PhysicsLayerTileData()); +} + +void TileData::move_physics_layer(int p_from_index, int p_to_pos) { + ERR_FAIL_INDEX(p_from_index, physics.size()); + ERR_FAIL_INDEX(p_to_pos, physics.size() + 1); + physics.insert(p_to_pos, physics[p_from_index]); + physics.remove(p_to_pos < p_from_index ? p_from_index + 1 : p_from_index); +} + +void TileData::remove_physics_layer(int p_index) { + ERR_FAIL_INDEX(p_index, physics.size()); + physics.remove(p_index); +} + +void TileData::add_terrain_set(int p_to_pos) { + if (p_to_pos >= 0 && p_to_pos <= terrain_set) { + terrain_set += 1; + } +} + +void TileData::move_terrain_set(int p_from_index, int p_to_pos) { + if (p_from_index == terrain_set) { + terrain_set = (p_from_index < p_to_pos) ? p_to_pos - 1 : p_to_pos; + } else { + if (p_from_index < terrain_set) { + terrain_set -= 1; + } + if (p_to_pos <= terrain_set) { + terrain_set += 1; + } + } +} + +void TileData::remove_terrain_set(int p_index) { + if (p_index == terrain_set) { + terrain_set = -1; + for (int i = 0; i < 16; i++) { + terrain_peering_bits[i] = -1; + } + } else if (terrain_set > p_index) { + terrain_set -= 1; + } +} + +void TileData::add_terrain(int p_terrain_set, int p_to_pos) { + if (terrain_set == p_terrain_set) { + for (int i = 0; i < 16; i++) { + if (p_to_pos >= 0 && p_to_pos <= terrain_peering_bits[i]) { + terrain_peering_bits[i] += 1; + } + } + } +} + +void TileData::move_terrain(int p_terrain_set, int p_from_index, int p_to_pos) { + if (terrain_set == p_terrain_set) { + for (int i = 0; i < 16; i++) { + if (p_from_index == terrain_peering_bits[i]) { + terrain_peering_bits[i] = (p_from_index < p_to_pos) ? p_to_pos - 1 : p_to_pos; + } else { + if (p_from_index < terrain_peering_bits[i]) { + terrain_peering_bits[i] -= 1; + } + if (p_to_pos <= terrain_peering_bits[i]) { + terrain_peering_bits[i] += 1; + } + } + } + } +} + +void TileData::remove_terrain(int p_terrain_set, int p_index) { + if (terrain_set == p_terrain_set) { + for (int i = 0; i < 16; i++) { + if (terrain_peering_bits[i] == p_index) { + terrain_peering_bits[i] = -1; + } else if (terrain_peering_bits[i] > p_index) { + terrain_peering_bits[i] -= 1; + } + } + } +} + +void TileData::add_navigation_layer(int p_to_pos) { + if (p_to_pos < 0) { + p_to_pos = navigation.size(); + } + ERR_FAIL_INDEX(p_to_pos, navigation.size() + 1); + navigation.insert(p_to_pos, Ref<NavigationPolygon>()); +} + +void TileData::move_navigation_layer(int p_from_index, int p_to_pos) { + ERR_FAIL_INDEX(p_from_index, navigation.size()); + ERR_FAIL_INDEX(p_to_pos, navigation.size() + 1); + navigation.insert(p_to_pos, navigation[p_from_index]); + navigation.remove(p_to_pos < p_from_index ? p_from_index + 1 : p_from_index); +} + +void TileData::remove_navigation_layer(int p_index) { + ERR_FAIL_INDEX(p_index, navigation.size()); + navigation.remove(p_index); +} + +void TileData::add_custom_data_layer(int p_to_pos) { + if (p_to_pos < 0) { + p_to_pos = custom_data.size(); + } + ERR_FAIL_INDEX(p_to_pos, custom_data.size() + 1); + custom_data.insert(p_to_pos, Variant()); +} + +void TileData::move_custom_data_layer(int p_from_index, int p_to_pos) { + ERR_FAIL_INDEX(p_from_index, custom_data.size()); + ERR_FAIL_INDEX(p_to_pos, custom_data.size() + 1); + custom_data.insert(p_to_pos, navigation[p_from_index]); + custom_data.remove(p_to_pos < p_from_index ? p_from_index + 1 : p_from_index); +} + +void TileData::remove_custom_data_layer(int p_index) { + ERR_FAIL_INDEX(p_index, custom_data.size()); + custom_data.remove(p_index); +} + void TileData::reset_state() { occluders.clear(); physics.clear(); @@ -3628,11 +4057,11 @@ Vector2i TileData::get_texture_offset() const { return tex_offset; } -void TileData::tile_set_material(Ref<ShaderMaterial> p_material) { +void TileData::set_material(Ref<ShaderMaterial> p_material) { material = p_material; emit_signal(SNAME("changed")); } -Ref<ShaderMaterial> TileData::tile_get_material() const { +Ref<ShaderMaterial> TileData::get_material() const { return material; } @@ -4148,8 +4577,8 @@ void TileData::_bind_methods() { ClassDB::bind_method(D_METHOD("get_flip_v"), &TileData::get_flip_v); ClassDB::bind_method(D_METHOD("set_transpose", "transpose"), &TileData::set_transpose); ClassDB::bind_method(D_METHOD("get_transpose"), &TileData::get_transpose); - ClassDB::bind_method(D_METHOD("tile_set_material", "material"), &TileData::tile_set_material); - ClassDB::bind_method(D_METHOD("tile_get_material"), &TileData::tile_get_material); + ClassDB::bind_method(D_METHOD("set_material", "material"), &TileData::set_material); + ClassDB::bind_method(D_METHOD("get_material"), &TileData::get_material); ClassDB::bind_method(D_METHOD("set_texture_offset", "texture_offset"), &TileData::set_texture_offset); ClassDB::bind_method(D_METHOD("get_texture_offset"), &TileData::get_texture_offset); ClassDB::bind_method(D_METHOD("set_modulate", "modulate"), &TileData::set_modulate); @@ -4200,6 +4629,7 @@ void TileData::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "transpose"), "set_transpose", "get_transpose"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "texture_offset"), "set_texture_offset", "get_texture_offset"); ADD_PROPERTY(PropertyInfo(Variant::COLOR, "modulate"), "set_modulate", "get_modulate"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "material", PROPERTY_HINT_RESOURCE_TYPE, "ShaderMaterial"), "set_material", "get_material"); ADD_PROPERTY(PropertyInfo(Variant::INT, "z_index"), "set_z_index", "get_z_index"); ADD_PROPERTY(PropertyInfo(Variant::INT, "y_sort_origin"), "set_y_sort_origin", "get_y_sort_origin"); diff --git a/scene/resources/tile_set.h b/scene/resources/tile_set.h index 35e6999d13..3baf022dc0 100644 --- a/scene/resources/tile_set.h +++ b/scene/resources/tile_set.h @@ -225,10 +225,10 @@ private: bool terrain_bits_meshes_dirty = true; // Navigation - struct Navigationlayer { + struct NavigationLayer { uint32_t layers = 1; }; - Vector<Navigationlayer> navigation_layers; + Vector<NavigationLayer> navigation_layers; // CustomData struct CustomDataLayer { @@ -298,16 +298,20 @@ public: void set_uv_clipping(bool p_uv_clipping); bool is_uv_clipping() const; - void set_occlusion_layers_count(int p_occlusion_layers_count); int get_occlusion_layers_count() const; + void add_occlusion_layer(int p_index = -1); + void move_occlusion_layer(int p_from_index, int p_to_pos); + void remove_occlusion_layer(int p_index); void set_occlusion_layer_light_mask(int p_layer_index, int p_light_mask); int get_occlusion_layer_light_mask(int p_layer_index) const; - void set_occlusion_layer_sdf_collision(int p_layer_index, int p_sdf_collision); + void set_occlusion_layer_sdf_collision(int p_layer_index, bool p_sdf_collision); bool get_occlusion_layer_sdf_collision(int p_layer_index) const; // Physics - void set_physics_layers_count(int p_physics_layers_count); int get_physics_layers_count() const; + void add_physics_layer(int p_index = -1); + void move_physics_layer(int p_from_index, int p_to_pos); + void remove_physics_layer(int p_index); void set_physics_layer_collision_layer(int p_layer_index, uint32_t p_layer); uint32_t get_physics_layer_collision_layer(int p_layer_index) const; void set_physics_layer_collision_mask(int p_layer_index, uint32_t p_mask); @@ -315,13 +319,19 @@ public: void set_physics_layer_physics_material(int p_layer_index, Ref<PhysicsMaterial> p_physics_material); Ref<PhysicsMaterial> get_physics_layer_physics_material(int p_layer_index) const; - // Terrains - void set_terrain_sets_count(int p_terrains_sets_count); + // Terrain sets int get_terrain_sets_count() const; + void add_terrain_set(int p_index = -1); + void move_terrain_set(int p_from_index, int p_to_pos); + void remove_terrain_set(int p_index); void set_terrain_set_mode(int p_terrain_set, TerrainMode p_terrain_mode); TerrainMode get_terrain_set_mode(int p_terrain_set) const; - void set_terrains_count(int p_terrain_set, int p_terrains_count); + + // Terrains int get_terrains_count(int p_terrain_set) const; + void add_terrain(int p_terrain_set, int p_index = -1); + void move_terrain(int p_terrain_set, int p_from_index, int p_to_pos); + void remove_terrain(int p_terrain_set, int p_index); void set_terrain_name(int p_terrain_set, int p_terrain_index, String p_name); String get_terrain_name(int p_terrain_set, int p_terrain_index) const; void set_terrain_color(int p_terrain_set, int p_terrain_index, Color p_color); @@ -330,14 +340,18 @@ public: bool is_valid_peering_bit_terrain(int p_terrain_set, TileSet::CellNeighbor p_peering_bit) const; // Navigation - void set_navigation_layers_count(int p_navigation_layers_count); int get_navigation_layers_count() const; + void add_navigation_layer(int p_index = -1); + void move_navigation_layer(int p_from_index, int p_to_pos); + void remove_navigation_layer(int p_index); void set_navigation_layer_layers(int p_layer_index, uint32_t p_layers); uint32_t get_navigation_layer_layers(int p_layer_index) const; // Custom data - void set_custom_data_layers_count(int p_custom_data_layers_count); int get_custom_data_layers_count() const; + void add_custom_data_layer(int p_index = -1); + void move_custom_data_layer(int p_from_index, int p_to_pos); + void remove_custom_data_layer(int p_index); int get_custom_data_layer_by_name(String p_value) const; void set_custom_data_name(int p_layer_id, String p_value); String get_custom_data_name(int p_layer_id) const; @@ -390,6 +404,8 @@ class TileSetSource : public Resource { protected: const TileSet *tile_set = nullptr; + static void _bind_methods(); + public: static const Vector2i INVALID_ATLAS_COORDS; // Vector2i(-1, -1); static const int INVALID_TILE_ALTERNATIVE; // -1; @@ -397,6 +413,24 @@ public: // Not exposed. virtual void set_tile_set(const TileSet *p_tile_set); virtual void notify_tile_data_properties_should_change(){}; + virtual void add_occlusion_layer(int p_index){}; + virtual void move_occlusion_layer(int p_from_index, int p_to_pos){}; + virtual void remove_occlusion_layer(int p_index){}; + virtual void add_physics_layer(int p_index){}; + virtual void move_physics_layer(int p_from_index, int p_to_pos){}; + virtual void remove_physics_layer(int p_index){}; + virtual void add_terrain_set(int p_index){}; + virtual void move_terrain_set(int p_from_index, int p_to_pos){}; + virtual void remove_terrain_set(int p_index){}; + virtual void add_terrain(int p_terrain_set, int p_index){}; + virtual void move_terrain(int p_terrain_set, int p_from_index, int p_to_pos){}; + virtual void remove_terrain(int p_terrain_set, int p_index){}; + virtual void add_navigation_layer(int p_index){}; + virtual void move_navigation_layer(int p_from_index, int p_to_pos){}; + virtual void remove_navigation_layer(int p_index){}; + virtual void add_custom_data_layer(int p_index){}; + virtual void move_custom_data_layer(int p_from_index, int p_to_pos){}; + virtual void remove_custom_data_layer(int p_index){}; virtual void reset_state() override{}; // Tiles. @@ -448,6 +482,24 @@ public: // Not exposed. virtual void set_tile_set(const TileSet *p_tile_set) override; virtual void notify_tile_data_properties_should_change() override; + virtual void add_occlusion_layer(int p_index) override; + virtual void move_occlusion_layer(int p_from_index, int p_to_pos) override; + virtual void remove_occlusion_layer(int p_index) override; + virtual void add_physics_layer(int p_index) override; + virtual void move_physics_layer(int p_from_index, int p_to_pos) override; + virtual void remove_physics_layer(int p_index) override; + virtual void add_terrain_set(int p_index) override; + virtual void move_terrain_set(int p_from_index, int p_to_pos) override; + virtual void remove_terrain_set(int p_index) override; + virtual void add_terrain(int p_terrain_set, int p_index) override; + virtual void move_terrain(int p_terrain_set, int p_from_index, int p_to_pos) override; + virtual void remove_terrain(int p_terrain_set, int p_index) override; + virtual void add_navigation_layer(int p_index) override; + virtual void move_navigation_layer(int p_from_index, int p_to_pos) override; + virtual void remove_navigation_layer(int p_index) override; + virtual void add_custom_data_layer(int p_index) override; + virtual void move_custom_data_layer(int p_from_index, int p_to_pos) override; + virtual void remove_custom_data_layer(int p_index) override; virtual void reset_state() override; // Base properties. @@ -528,7 +580,7 @@ public: int get_alternative_tile_id(const Vector2i p_atlas_coords, int p_index) const override; bool has_alternative_tile(const Vector2i p_atlas_coords, int p_alternative_tile) const override; - // Scenes sccessors. Lot are similar to "Alternative tiles". + // Scenes accessors. Lot are similar to "Alternative tiles". int get_scene_tiles_count() { return get_alternative_tiles_count(Vector2i()); } int get_scene_tile_id(int p_index) { return get_alternative_tile_id(Vector2i(), p_index); }; bool has_scene_tile_id(int p_id) { return has_alternative_tile(Vector2i(), p_id); }; @@ -597,6 +649,24 @@ public: // Not exposed. void set_tile_set(const TileSet *p_tile_set); void notify_tile_data_properties_should_change(); + void add_occlusion_layer(int p_index); + void move_occlusion_layer(int p_from_index, int p_to_pos); + void remove_occlusion_layer(int p_index); + void add_physics_layer(int p_index); + void move_physics_layer(int p_from_index, int p_to_pos); + void remove_physics_layer(int p_index); + void add_terrain_set(int p_index); + void move_terrain_set(int p_from_index, int p_to_pos); + void remove_terrain_set(int p_index); + void add_terrain(int p_terrain_set, int p_index); + void move_terrain(int p_terrain_set, int p_from_index, int p_to_pos); + void remove_terrain(int p_terrain_set, int p_index); + void add_navigation_layer(int p_index); + void move_navigation_layer(int p_from_index, int p_to_pos); + void remove_navigation_layer(int p_index); + void add_custom_data_layer(int p_index); + void move_custom_data_layer(int p_from_index, int p_to_pos); + void remove_custom_data_layer(int p_index); void reset_state(); void set_allow_transform(bool p_allow_transform); bool is_allowing_transform() const; @@ -611,8 +681,8 @@ public: void set_texture_offset(Vector2i p_texture_offset); Vector2i get_texture_offset() const; - void tile_set_material(Ref<ShaderMaterial> p_material); - Ref<ShaderMaterial> tile_get_material() const; + void set_material(Ref<ShaderMaterial> p_material); + Ref<ShaderMaterial> get_material() const; void set_modulate(Color p_modulate); Color get_modulate() const; void set_z_index(int p_z_index); diff --git a/servers/audio/audio_stream.cpp b/servers/audio/audio_stream.cpp index e1b391b823..c098a97906 100644 --- a/servers/audio/audio_stream.cpp +++ b/servers/audio/audio_stream.cpp @@ -189,11 +189,21 @@ float AudioStream::get_length() const { return 0; } +bool AudioStream::is_monophonic() const { + bool ret; + if (GDVIRTUAL_CALL(_is_monophonic, ret)) { + return ret; + } + return true; +} + void AudioStream::_bind_methods() { ClassDB::bind_method(D_METHOD("get_length"), &AudioStream::get_length); + ClassDB::bind_method(D_METHOD("is_monophonic"), &AudioStream::is_monophonic); GDVIRTUAL_BIND(_instance_playback); GDVIRTUAL_BIND(_get_stream_name); GDVIRTUAL_BIND(_get_length); + GDVIRTUAL_BIND(_is_monophonic); } //////////////////////////////// @@ -221,6 +231,10 @@ float AudioStreamMicrophone::get_length() const { return 0; } +bool AudioStreamMicrophone::is_monophonic() const { + return true; +} + void AudioStreamMicrophone::_bind_methods() { } @@ -388,6 +402,14 @@ float AudioStreamRandomPitch::get_length() const { return 0; } +bool AudioStreamRandomPitch::is_monophonic() const { + if (audio_stream.is_valid()) { + return audio_stream->is_monophonic(); + } + + return true; // It doesn't really matter what we return here, but no sense instancing a many playbacks of a null stream. +} + void AudioStreamRandomPitch::_bind_methods() { ClassDB::bind_method(D_METHOD("set_audio_stream", "stream"), &AudioStreamRandomPitch::set_audio_stream); ClassDB::bind_method(D_METHOD("get_audio_stream"), &AudioStreamRandomPitch::get_audio_stream); diff --git a/servers/audio/audio_stream.h b/servers/audio/audio_stream.h index 922335508e..12d4343f5c 100644 --- a/servers/audio/audio_stream.h +++ b/servers/audio/audio_stream.h @@ -102,12 +102,14 @@ protected: GDVIRTUAL0RC(Ref<AudioStreamPlayback>, _instance_playback) GDVIRTUAL0RC(String, _get_stream_name) GDVIRTUAL0RC(float, _get_length) + GDVIRTUAL0RC(bool, _is_monophonic) public: virtual Ref<AudioStreamPlayback> instance_playback(); virtual String get_stream_name() const; virtual float get_length() const; + virtual bool is_monophonic() const; }; // Microphone @@ -129,6 +131,8 @@ public: virtual float get_length() const override; //if supported, otherwise return 0 + virtual bool is_monophonic() const override; + AudioStreamMicrophone(); }; @@ -187,6 +191,7 @@ public: virtual String get_stream_name() const override; virtual float get_length() const override; //if supported, otherwise return 0 + virtual bool is_monophonic() const override; AudioStreamRandomPitch(); }; diff --git a/servers/audio/effects/audio_effect_capture.h b/servers/audio/effects/audio_effect_capture.h index 7f50fc4965..bb1d03be8c 100644 --- a/servers/audio/effects/audio_effect_capture.h +++ b/servers/audio/effects/audio_effect_capture.h @@ -34,6 +34,7 @@ #include "core/config/engine.h" #include "core/math/audio_frame.h" #include "core/object/ref_counted.h" +#include "core/templates/ring_buffer.h" #include "core/templates/vector.h" #include "servers/audio/audio_effect.h" #include "servers/audio_server.h" diff --git a/servers/audio/effects/audio_stream_generator.cpp b/servers/audio/effects/audio_stream_generator.cpp index edb5c6d2dd..447acf53a4 100644 --- a/servers/audio/effects/audio_stream_generator.cpp +++ b/servers/audio/effects/audio_stream_generator.cpp @@ -64,6 +64,10 @@ float AudioStreamGenerator::get_length() const { return 0; } +bool AudioStreamGenerator::is_monophonic() const { + return true; +} + void AudioStreamGenerator::_bind_methods() { ClassDB::bind_method(D_METHOD("set_mix_rate", "hz"), &AudioStreamGenerator::set_mix_rate); ClassDB::bind_method(D_METHOD("get_mix_rate"), &AudioStreamGenerator::get_mix_rate); diff --git a/servers/audio/effects/audio_stream_generator.h b/servers/audio/effects/audio_stream_generator.h index 6bec744081..918589f6d0 100644 --- a/servers/audio/effects/audio_stream_generator.h +++ b/servers/audio/effects/audio_stream_generator.h @@ -54,6 +54,7 @@ public: virtual String get_stream_name() const override; virtual float get_length() const override; + virtual bool is_monophonic() const override; AudioStreamGenerator(); }; diff --git a/servers/audio_server.cpp b/servers/audio_server.cpp index 81735d522f..758ce766c3 100644 --- a/servers/audio_server.cpp +++ b/servers/audio_server.cpp @@ -363,10 +363,10 @@ void AudioServer::_mix_step() { if (mixed_frames != buffer_size) { // We know we have at least the size of our lookahead buffer for fade-out purposes. - float fadeout_base = 0.87; + float fadeout_base = 0.94; float fadeout_coefficient = 1; - static_assert(LOOKAHEAD_BUFFER_SIZE == 32, "Update fadeout_base and comment here if you change LOOKAHEAD_BUFFER_SIZE."); - // 0.87 ^ 32 = 0.0116. There might still be a pop but it'll be way better than if we didn't do this. + static_assert(LOOKAHEAD_BUFFER_SIZE == 64, "Update fadeout_base and comment here if you change LOOKAHEAD_BUFFER_SIZE."); + // 0.94 ^ 64 = 0.01906. There might still be a pop but it'll be way better than if we didn't do this. for (unsigned int idx = mixed_frames; idx < buffer_size; idx++) { fadeout_coefficient *= fadeout_base; buf[idx] *= fadeout_coefficient; @@ -381,29 +381,24 @@ void AudioServer::_mix_step() { } } - ERR_FAIL_COND(playback->bus_details.load() == nullptr); + AudioStreamPlaybackBusDetails *ptr = playback->bus_details.load(); + ERR_FAIL_COND(ptr == nullptr); // By putting null into the bus details pointers, we're taking ownership of their memory for the duration of this mix. - AudioStreamPlaybackBusDetails *bus_details = nullptr; - { - std::atomic<AudioStreamPlaybackBusDetails *> bus_details_atomic = nullptr; - bus_details = playback->bus_details.exchange(bus_details_atomic); - } - ERR_FAIL_COND(bus_details == nullptr); - AudioStreamPlaybackBusDetails *prev_bus_details = playback->prev_bus_details; + AudioStreamPlaybackBusDetails bus_details = *ptr; // Mix to any active buses. for (int idx = 0; idx < MAX_BUSES_PER_PLAYBACK; idx++) { - if (!bus_details->bus_active[idx]) { + if (!bus_details.bus_active[idx]) { continue; } - int bus_idx = thread_find_bus_index(bus_details->bus[idx]); + int bus_idx = thread_find_bus_index(bus_details.bus[idx]); int prev_bus_idx = -1; for (int search_idx = 0; search_idx < MAX_BUSES_PER_PLAYBACK; search_idx++) { - if (!prev_bus_details->bus_active[search_idx]) { + if (!playback->prev_bus_details->bus_active[search_idx]) { continue; } - if (prev_bus_details->bus[search_idx].hash() == bus_details->bus[idx].hash()) { + if (playback->prev_bus_details->bus[search_idx].hash() == bus_details.bus[idx].hash()) { prev_bus_idx = search_idx; } } @@ -411,13 +406,13 @@ void AudioServer::_mix_step() { for (int channel_idx = 0; channel_idx < channel_count; channel_idx++) { AudioFrame *channel_buf = thread_get_channel_mix_buffer(bus_idx, channel_idx); if (fading_out) { - bus_details->volume[idx][channel_idx] = AudioFrame(0, 0); + bus_details.volume[idx][channel_idx] = AudioFrame(0, 0); } - AudioFrame channel_vol = bus_details->volume[idx][channel_idx]; + AudioFrame channel_vol = bus_details.volume[idx][channel_idx]; AudioFrame prev_channel_vol = AudioFrame(0, 0); if (prev_bus_idx != -1) { - prev_channel_vol = prev_bus_details->volume[prev_bus_idx][channel_idx]; + prev_channel_vol = playback->prev_bus_details->volume[prev_bus_idx][channel_idx]; } _mix_step_for_channel(channel_buf, buf, prev_channel_vol, channel_vol, playback->attenuation_filter_cutoff_hz.get(), playback->highshelf_gain.get(), &playback->filter_process[channel_idx * 2], &playback->filter_process[channel_idx * 2 + 1]); } @@ -425,14 +420,14 @@ void AudioServer::_mix_step() { // Now go through and fade-out any buses that were being played to previously that we missed by going through current data. for (int idx = 0; idx < MAX_BUSES_PER_PLAYBACK; idx++) { - if (!prev_bus_details->bus_active[idx]) { + if (!playback->prev_bus_details->bus_active[idx]) { continue; } - int bus_idx = thread_find_bus_index(prev_bus_details->bus[idx]); + int bus_idx = thread_find_bus_index(playback->prev_bus_details->bus[idx]); int current_bus_idx = -1; for (int search_idx = 0; search_idx < MAX_BUSES_PER_PLAYBACK; search_idx++) { - if (bus_details->bus[search_idx] == prev_bus_details->bus[idx]) { + if (bus_details.bus[search_idx] == playback->prev_bus_details->bus[idx]) { current_bus_idx = search_idx; } } @@ -443,24 +438,17 @@ void AudioServer::_mix_step() { for (int channel_idx = 0; channel_idx < channel_count; channel_idx++) { AudioFrame *channel_buf = thread_get_channel_mix_buffer(bus_idx, channel_idx); - AudioFrame prev_channel_vol = prev_bus_details->volume[idx][channel_idx]; + AudioFrame prev_channel_vol = playback->prev_bus_details->volume[idx][channel_idx]; // Fade out to silence _mix_step_for_channel(channel_buf, buf, prev_channel_vol, AudioFrame(0, 0), playback->attenuation_filter_cutoff_hz.get(), playback->highshelf_gain.get(), &playback->filter_process[channel_idx * 2], &playback->filter_process[channel_idx * 2 + 1]); } } // Copy the bus details we mixed with to the previous bus details to maintain volume ramps. - std::copy(std::begin(bus_details->bus_active), std::end(bus_details->bus_active), std::begin(prev_bus_details->bus_active)); - std::copy(std::begin(bus_details->bus), std::end(bus_details->bus), std::begin(prev_bus_details->bus)); + std::copy(std::begin(bus_details.bus_active), std::end(bus_details.bus_active), std::begin(playback->prev_bus_details->bus_active)); + std::copy(std::begin(bus_details.bus), std::end(bus_details.bus), std::begin(playback->prev_bus_details->bus)); for (int bus_idx = 0; bus_idx < MAX_BUSES_PER_PLAYBACK; bus_idx++) { - std::copy(std::begin(bus_details->volume[bus_idx]), std::end(bus_details->volume[bus_idx]), std::begin(prev_bus_details->volume[bus_idx])); - } - - AudioStreamPlaybackBusDetails *bus_details_expected = nullptr; - // Only put the bus details pointer back if it hasn't been updated already. - if (!playback->bus_details.compare_exchange_strong(/* expected= */ bus_details_expected, /* new= */ bus_details)) { - // If it *has* been updated already, queue the old one for deletion. - bus_details_graveyard.insert(bus_details); + std::copy(std::begin(bus_details.volume[bus_idx]), std::end(bus_details.volume[bus_idx]), std::begin(playback->prev_bus_details->volume[bus_idx])); } switch (playback->state.load()) { @@ -1132,7 +1120,7 @@ void AudioServer::start_playback_stream(Ref<AudioStreamPlayback> p_playback, Str start_playback_stream(p_playback, map, p_start_time); } -void AudioServer::start_playback_stream(Ref<AudioStreamPlayback> p_playback, Map<StringName, Vector<AudioFrame>> p_bus_volumes, float p_start_time) { +void AudioServer::start_playback_stream(Ref<AudioStreamPlayback> p_playback, Map<StringName, Vector<AudioFrame>> p_bus_volumes, float p_start_time, float p_highshelf_gain, float p_attenuation_cutoff_hz, float p_pitch_scale) { ERR_FAIL_COND(p_playback.is_null()); AudioStreamPlaybackListNode *playback_node = new AudioStreamPlaybackListNode(); @@ -1154,10 +1142,9 @@ void AudioServer::start_playback_stream(Ref<AudioStreamPlayback> p_playback, Map playback_node->bus_details = new_bus_details; playback_node->prev_bus_details = new AudioStreamPlaybackBusDetails(); - playback_node->setseek.set(-1); - playback_node->pitch_scale.set(1); - playback_node->highshelf_gain.set(0); - playback_node->attenuation_filter_cutoff_hz.set(0); + playback_node->pitch_scale.set(p_pitch_scale); + playback_node->highshelf_gain.set(p_highshelf_gain); + playback_node->attenuation_filter_cutoff_hz.set(p_attenuation_cutoff_hz); memset(playback_node->prev_bus_details->volume, 0, sizeof(playback_node->prev_bus_details->volume)); @@ -1181,6 +1168,9 @@ void AudioServer::stop_playback_stream(Ref<AudioStreamPlayback> p_playback) { AudioStreamPlaybackListNode::PlaybackState new_state, old_state; do { old_state = playback_node->state.load(); + if (old_state == AudioStreamPlaybackListNode::AWAITING_DELETION) { + break; // Don't fade out again. + } new_state = AudioStreamPlaybackListNode::FADE_OUT_TO_DELETION; } while (!playback_node->state.compare_exchange_strong(old_state, new_state)); @@ -1206,6 +1196,9 @@ void AudioServer::set_playback_bus_volumes_linear(Ref<AudioStreamPlayback> p_pla int idx = 0; for (KeyValue<StringName, Vector<AudioFrame>> pair : p_bus_volumes) { + if (idx >= MAX_BUSES_PER_PLAYBACK) { + break; + } ERR_FAIL_COND(pair.value.size() < channel_count); ERR_FAIL_COND(pair.value.size() != MAX_CHANNELS_PER_BUS); @@ -1214,6 +1207,7 @@ void AudioServer::set_playback_bus_volumes_linear(Ref<AudioStreamPlayback> p_pla for (int channel_idx = 0; channel_idx < MAX_CHANNELS_PER_BUS; channel_idx++) { new_bus_details->volume[idx][channel_idx] = pair.value[channel_idx]; } + idx++; } do { @@ -1260,17 +1254,18 @@ void AudioServer::set_playback_paused(Ref<AudioStreamPlayback> p_playback, bool if (!playback_node) { return; } - if (!p_paused && playback_node->state == AudioStreamPlaybackListNode::PLAYING) { - return; // No-op. - } - if (p_paused && (playback_node->state == AudioStreamPlaybackListNode::PAUSED || playback_node->state == AudioStreamPlaybackListNode::FADE_OUT_TO_PAUSE)) { - return; // No-op. - } AudioStreamPlaybackListNode::PlaybackState new_state, old_state; do { old_state = playback_node->state.load(); new_state = p_paused ? AudioStreamPlaybackListNode::FADE_OUT_TO_PAUSE : AudioStreamPlaybackListNode::PLAYING; + if (!p_paused && old_state == AudioStreamPlaybackListNode::PLAYING) { + return; // No-op. + } + if (p_paused && (old_state == AudioStreamPlaybackListNode::PAUSED || old_state == AudioStreamPlaybackListNode::FADE_OUT_TO_PAUSE)) { + return; // No-op. + } + } while (!playback_node->state.compare_exchange_strong(old_state, new_state)); } @@ -1444,10 +1439,15 @@ void AudioServer::update() { update_callback_list.maybe_cleanup(); listener_changed_callback_list.maybe_cleanup(); playback_list.maybe_cleanup(); + for (AudioStreamPlaybackBusDetails *bus_details : bus_details_graveyard_frame_old) { + bus_details_graveyard_frame_old.erase(bus_details, [](AudioStreamPlaybackBusDetails *d) { delete d; }); + } for (AudioStreamPlaybackBusDetails *bus_details : bus_details_graveyard) { - bus_details_graveyard.erase(bus_details, [](AudioStreamPlaybackBusDetails *d) { delete d; }); + bus_details_graveyard_frame_old.insert(bus_details); + bus_details_graveyard.erase(bus_details); } bus_details_graveyard.maybe_cleanup(); + bus_details_graveyard_frame_old.maybe_cleanup(); } void AudioServer::load_default_bus_layout() { diff --git a/servers/audio_server.h b/servers/audio_server.h index affcb3df7b..a60d4ae4c4 100644 --- a/servers/audio_server.h +++ b/servers/audio_server.h @@ -163,7 +163,7 @@ public: AUDIO_DATA_INVALID_ID = -1, MAX_CHANNELS_PER_BUS = 4, MAX_BUSES_PER_PLAYBACK = 6, - LOOKAHEAD_BUFFER_SIZE = 32, + LOOKAHEAD_BUFFER_SIZE = 64, }; typedef void (*AudioCallback)(void *p_userdata); @@ -262,6 +262,9 @@ private: SafeList<AudioStreamPlaybackListNode *> playback_list; SafeList<AudioStreamPlaybackBusDetails *> bus_details_graveyard; + // TODO document if this is necessary. + SafeList<AudioStreamPlaybackBusDetails *> bus_details_graveyard_frame_old; + Vector<Vector<AudioFrame>> temp_buffer; //temp_buffer for each level Vector<AudioFrame> mix_buffer; Vector<Bus *> buses; @@ -364,8 +367,10 @@ public: void set_playback_speed_scale(float p_scale); float get_playback_speed_scale() const; + // Convenience method. void start_playback_stream(Ref<AudioStreamPlayback> p_playback, StringName p_bus, Vector<AudioFrame> p_volume_db_vector, float p_start_time = 0); - void start_playback_stream(Ref<AudioStreamPlayback> p_playback, Map<StringName, Vector<AudioFrame>> p_bus_volumes, float p_start_time = 0); + // Expose all parameters. + void start_playback_stream(Ref<AudioStreamPlayback> p_playback, Map<StringName, Vector<AudioFrame>> p_bus_volumes, float p_start_time = 0, float p_highshelf_gain = 0, float p_attenuation_cutoff_hz = 0, float p_pitch_scale = 1); void stop_playback_stream(Ref<AudioStreamPlayback> p_playback); void set_playback_bus_exclusive(Ref<AudioStreamPlayback> p_playback, StringName p_bus, Vector<AudioFrame> p_volumes); diff --git a/servers/display_server.cpp b/servers/display_server.cpp index 3d44484033..cdf892094d 100644 --- a/servers/display_server.cpp +++ b/servers/display_server.cpp @@ -605,4 +605,5 @@ DisplayServer::DisplayServer() { } DisplayServer::~DisplayServer() { + singleton = nullptr; } diff --git a/servers/physics_2d/body_2d_sw.cpp b/servers/physics_2d/body_2d_sw.cpp index 64f71fd25c..edd769aa9a 100644 --- a/servers/physics_2d/body_2d_sw.cpp +++ b/servers/physics_2d/body_2d_sw.cpp @@ -34,47 +34,72 @@ #include "body_direct_state_2d_sw.h" #include "space_2d_sw.h" -void Body2DSW::_update_inertia() { - if (!user_inertia && get_space() && !inertia_update_list.in_list()) { - get_space()->body_add_to_inertia_update_list(&inertia_update_list); +void Body2DSW::_mass_properties_changed() { + if (get_space() && !mass_properties_update_list.in_list() && (calculate_inertia || calculate_center_of_mass)) { + get_space()->body_add_to_mass_properties_update_list(&mass_properties_update_list); } } -void Body2DSW::update_inertias() { +void Body2DSW::update_mass_properties() { //update shapes and motions switch (mode) { case PhysicsServer2D::BODY_MODE_DYNAMIC: { - if (user_inertia) { - _inv_inertia = inertia > 0 ? (1.0 / inertia) : 0; - break; - } - //update tensor for allshapes, not the best way but should be somehow OK. (inspired from bullet) real_t total_area = 0; - for (int i = 0; i < get_shape_count(); i++) { + if (is_shape_disabled(i)) { + continue; + } total_area += get_shape_aabb(i).get_area(); } - inertia = 0; + if (calculate_center_of_mass) { + // We have to recompute the center of mass. + center_of_mass = Vector2(); - for (int i = 0; i < get_shape_count(); i++) { - if (is_shape_disabled(i)) { - continue; + if (total_area != 0.0) { + for (int i = 0; i < get_shape_count(); i++) { + if (is_shape_disabled(i)) { + continue; + } + + real_t area = get_shape_aabb(i).get_area(); + + real_t mass = area * this->mass / total_area; + + // NOTE: we assume that the shape origin is also its center of mass. + center_of_mass += mass * get_shape_transform(i).get_origin(); + } + + center_of_mass /= mass; } + } + + if (calculate_inertia) { + inertia = 0; + + for (int i = 0; i < get_shape_count(); i++) { + if (is_shape_disabled(i)) { + continue; + } - const Shape2DSW *shape = get_shape(i); + const Shape2DSW *shape = get_shape(i); - real_t area = get_shape_aabb(i).get_area(); + real_t area = get_shape_aabb(i).get_area(); + if (area == 0.0) { + continue; + } - real_t mass = area * this->mass / total_area; + real_t mass = area * this->mass / total_area; - Transform2D mtx = get_shape_transform(i); - Vector2 scale = mtx.get_scale(); - inertia += shape->get_moment_of_inertia(mass, scale) + mass * mtx.get_origin().length_squared(); + Transform2D mtx = get_shape_transform(i); + Vector2 scale = mtx.get_scale(); + Vector2 shape_origin = mtx.get_origin() - center_of_mass; + inertia += shape->get_moment_of_inertia(mass, scale) + mass * shape_origin.length_squared(); + } } - _inv_inertia = inertia > 0 ? (1.0 / inertia) : 0; + _inv_inertia = inertia > 0.0 ? (1.0 / inertia) : 0.0; if (mass) { _inv_mass = 1.0 / mass; @@ -94,9 +119,12 @@ void Body2DSW::update_inertias() { } break; } - //_update_inertia_tensor(); +} - //_update_shapes(); +void Body2DSW::reset_mass_properties() { + calculate_inertia = true; + calculate_center_of_mass = true; + _mass_properties_changed(); } void Body2DSW::set_active(bool p_active) { @@ -118,7 +146,7 @@ void Body2DSW::set_active(bool p_active) { } } -void Body2DSW::set_param(PhysicsServer2D::BodyParameter p_param, real_t p_value) { +void Body2DSW::set_param(PhysicsServer2D::BodyParameter p_param, const Variant &p_value) { switch (p_param) { case PhysicsServer2D::BODY_PARAM_BOUNCE: { bounce = p_value; @@ -127,21 +155,32 @@ void Body2DSW::set_param(PhysicsServer2D::BodyParameter p_param, real_t p_value) friction = p_value; } break; case PhysicsServer2D::BODY_PARAM_MASS: { - ERR_FAIL_COND(p_value <= 0); - mass = p_value; - _update_inertia(); - + real_t mass_value = p_value; + ERR_FAIL_COND(mass_value <= 0); + mass = mass_value; + if (mode >= PhysicsServer2D::BODY_MODE_DYNAMIC) { + _mass_properties_changed(); + } } break; case PhysicsServer2D::BODY_PARAM_INERTIA: { - if (p_value <= 0) { - user_inertia = false; - _update_inertia(); + real_t inertia_value = p_value; + if (inertia_value <= 0.0) { + calculate_inertia = true; + if (mode == PhysicsServer2D::BODY_MODE_DYNAMIC) { + _mass_properties_changed(); + } } else { - user_inertia = true; - inertia = p_value; - _inv_inertia = 1.0 / p_value; + calculate_inertia = false; + inertia = inertia_value; + if (mode == PhysicsServer2D::BODY_MODE_DYNAMIC) { + _inv_inertia = 1.0 / inertia; + } } } break; + case PhysicsServer2D::BODY_PARAM_CENTER_OF_MASS: { + calculate_center_of_mass = false; + center_of_mass = p_value; + } break; case PhysicsServer2D::BODY_PARAM_GRAVITY_SCALE: { gravity_scale = p_value; } break; @@ -156,7 +195,7 @@ void Body2DSW::set_param(PhysicsServer2D::BodyParameter p_param, real_t p_value) } } -real_t Body2DSW::get_param(PhysicsServer2D::BodyParameter p_param) const { +Variant Body2DSW::get_param(PhysicsServer2D::BodyParameter p_param) const { switch (p_param) { case PhysicsServer2D::BODY_PARAM_BOUNCE: { return bounce; @@ -170,6 +209,9 @@ real_t Body2DSW::get_param(PhysicsServer2D::BodyParameter p_param) const { case PhysicsServer2D::BODY_PARAM_INERTIA: { return inertia; } + case PhysicsServer2D::BODY_PARAM_CENTER_OF_MASS: { + return center_of_mass; + } case PhysicsServer2D::BODY_PARAM_GRAVITY_SCALE: { return gravity_scale; } @@ -207,7 +249,10 @@ void Body2DSW::set_mode(PhysicsServer2D::BodyMode p_mode) { } break; case PhysicsServer2D::BODY_MODE_DYNAMIC: { _inv_mass = mass > 0 ? (1.0 / mass) : 0; - _inv_inertia = inertia > 0 ? (1.0 / inertia) : 0; + if (!calculate_inertia) { + _inv_inertia = 1.0 / inertia; + } + _mass_properties_changed(); _set_static(false); set_active(true); @@ -215,18 +260,11 @@ void Body2DSW::set_mode(PhysicsServer2D::BodyMode p_mode) { case PhysicsServer2D::BODY_MODE_DYNAMIC_LOCKED: { _inv_mass = mass > 0 ? (1.0 / mass) : 0; _inv_inertia = 0; + angular_velocity = 0; _set_static(false); set_active(true); - angular_velocity = 0; - } break; - } - if (p_mode == PhysicsServer2D::BODY_MODE_DYNAMIC && _inv_inertia == 0) { - _update_inertia(); + } } - /* - if (get_space()) - _update_queries(); - */ } PhysicsServer2D::BodyMode Body2DSW::get_mode() const { @@ -234,7 +272,7 @@ PhysicsServer2D::BodyMode Body2DSW::get_mode() const { } void Body2DSW::_shapes_changed() { - _update_inertia(); + _mass_properties_changed(); wakeup_neighbours(); } @@ -269,11 +307,13 @@ void Body2DSW::set_state(PhysicsServer2D::BodyState p_state, const Variant &p_va } break; case PhysicsServer2D::BODY_STATE_LINEAR_VELOCITY: { linear_velocity = p_variant; + constant_linear_velocity = linear_velocity; wakeup(); } break; case PhysicsServer2D::BODY_STATE_ANGULAR_VELOCITY: { angular_velocity = p_variant; + constant_angular_velocity = angular_velocity; wakeup(); } break; @@ -296,7 +336,7 @@ void Body2DSW::set_state(PhysicsServer2D::BodyState p_state, const Variant &p_va } break; case PhysicsServer2D::BODY_STATE_CAN_SLEEP: { can_sleep = p_variant; - if (mode == PhysicsServer2D::BODY_MODE_DYNAMIC && !active && !can_sleep) { + if (mode >= PhysicsServer2D::BODY_MODE_DYNAMIC && !active && !can_sleep) { set_active(true); } @@ -330,8 +370,8 @@ void Body2DSW::set_space(Space2DSW *p_space) { if (get_space()) { wakeup_neighbours(); - if (inertia_update_list.in_list()) { - get_space()->body_remove_from_inertia_update_list(&inertia_update_list); + if (mass_properties_update_list.in_list()) { + get_space()->body_remove_from_mass_properties_update_list(&mass_properties_update_list); } if (active_list.in_list()) { get_space()->body_remove_from_active_list(&active_list); @@ -344,13 +384,11 @@ void Body2DSW::set_space(Space2DSW *p_space) { _set_space(p_space); if (get_space()) { - _update_inertia(); + _mass_properties_changed(); if (active) { get_space()->body_add_to_active_list(&active_list); } } - - first_integration = false; } void Body2DSW::_compute_area_gravity_and_damping(const Area2DSW *p_area) { @@ -429,10 +467,10 @@ void Body2DSW::integrate_forces(real_t p_step) { if (mode == PhysicsServer2D::BODY_MODE_KINEMATIC) { //compute motion, angular and etc. velocities from prev transform motion = new_transform.get_origin() - get_transform().get_origin(); - linear_velocity = motion / p_step; + linear_velocity = constant_linear_velocity + motion / p_step; real_t rot = new_transform.get_rotation() - get_transform().get_rotation(); - angular_velocity = remainder(rot, 2.0 * Math_PI) / p_step; + angular_velocity = constant_angular_velocity + remainder(rot, 2.0 * Math_PI) / p_step; do_motion = true; @@ -444,7 +482,7 @@ void Body2DSW::integrate_forces(real_t p_step) { */ } else { - if (!omit_force_integration && !first_integration) { + if (!omit_force_integration) { //overridden by direct state query Vector2 force = gravity * mass; @@ -478,7 +516,6 @@ void Body2DSW::integrate_forces(real_t p_step) { //motion=linear_velocity*p_step; - first_integration = false; biased_angular_velocity = 0; biased_linear_velocity = Vector2(); @@ -515,14 +552,22 @@ void Body2DSW::integrate_velocities(real_t p_step) { real_t angle = get_transform().get_rotation() + total_angular_velocity * p_step; Vector2 pos = get_transform().get_origin() + total_linear_velocity * p_step; + real_t center_of_mass_distance = center_of_mass.length(); + if (center_of_mass_distance > CMP_EPSILON) { + // Calculate displacement due to center of mass offset. + real_t prev_angle = get_transform().get_rotation(); + real_t angle_base = Math::atan2(center_of_mass.y, center_of_mass.x); + Vector2 point1(Math::cos(angle_base + prev_angle), Math::sin(angle_base + prev_angle)); + Vector2 point2(Math::cos(angle_base + angle), Math::sin(angle_base + angle)); + pos += center_of_mass_distance * (point1 - point2); + } + _set_transform(Transform2D(angle, pos), continuous_cd_mode == PhysicsServer2D::CCD_MODE_DISABLED); _set_inv_transform(get_transform().inverse()); if (continuous_cd_mode != PhysicsServer2D::CCD_MODE_DISABLED) { new_transform = get_transform(); } - - //_update_inertia_tensor(); } void Body2DSW::wakeup_neighbours() { @@ -536,7 +581,7 @@ void Body2DSW::wakeup_neighbours() { continue; } Body2DSW *b = n[i]; - if (b->mode != PhysicsServer2D::BODY_MODE_DYNAMIC) { + if (b->mode < PhysicsServer2D::BODY_MODE_DYNAMIC) { continue; } @@ -617,35 +662,9 @@ PhysicsDirectBodyState2DSW *Body2DSW::get_direct_state() { Body2DSW::Body2DSW() : CollisionObject2DSW(TYPE_BODY), active_list(this), - inertia_update_list(this), + mass_properties_update_list(this), direct_state_query_list(this) { - mode = PhysicsServer2D::BODY_MODE_DYNAMIC; - active = true; - angular_velocity = 0; - biased_angular_velocity = 0; - mass = 1; - inertia = 0; - user_inertia = false; - _inv_inertia = 0; - _inv_mass = 1; - bounce = 0; - friction = 1; - omit_force_integration = false; - applied_torque = 0; - island_step = 0; _set_static(false); - first_time_kinematic = false; - linear_damp = -1; - angular_damp = -1; - area_angular_damp = 0; - area_linear_damp = 0; - contact_count = 0; - gravity_scale = 1.0; - first_integration = false; - - still_time = 0; - continuous_cd_mode = PhysicsServer2D::CCD_MODE_DISABLED; - can_sleep = true; } Body2DSW::~Body2DSW() { diff --git a/servers/physics_2d/body_2d_sw.h b/servers/physics_2d/body_2d_sw.h index 9cd53ceca1..95e89786cd 100644 --- a/servers/physics_2d/body_2d_sw.h +++ b/servers/physics_2d/body_2d_sw.h @@ -41,48 +41,55 @@ class Constraint2DSW; class PhysicsDirectBodyState2DSW; class Body2DSW : public CollisionObject2DSW { - PhysicsServer2D::BodyMode mode; + PhysicsServer2D::BodyMode mode = PhysicsServer2D::BODY_MODE_DYNAMIC; Vector2 biased_linear_velocity; - real_t biased_angular_velocity; + real_t biased_angular_velocity = 0.0; Vector2 linear_velocity; - real_t angular_velocity; + real_t angular_velocity = 0.0; - real_t linear_damp; - real_t angular_damp; - real_t gravity_scale; + Vector2 constant_linear_velocity; + real_t constant_angular_velocity = 0.0; - real_t mass; - real_t inertia; - real_t bounce; - real_t friction; + real_t linear_damp = -1.0; + real_t angular_damp = -1.0; + real_t gravity_scale = 1.0; - real_t _inv_mass; - real_t _inv_inertia; - bool user_inertia; + real_t bounce = 0.0; + real_t friction = 1.0; + + real_t mass = 1.0; + real_t _inv_mass = 1.0; + + real_t inertia = 0.0; + real_t _inv_inertia = 0.0; + + Vector2 center_of_mass; + + bool calculate_inertia = true; + bool calculate_center_of_mass = true; Vector2 gravity; - real_t area_linear_damp; - real_t area_angular_damp; + real_t area_linear_damp = 0.0; + real_t area_angular_damp = 0.0; - real_t still_time; + real_t still_time = 0.0; Vector2 applied_force; - real_t applied_torque; + real_t applied_torque = 0.0; SelfList<Body2DSW> active_list; - SelfList<Body2DSW> inertia_update_list; + SelfList<Body2DSW> mass_properties_update_list; SelfList<Body2DSW> direct_state_query_list; VSet<RID> exceptions; - PhysicsServer2D::CCDMode continuous_cd_mode; - bool omit_force_integration; - bool active; - bool can_sleep; - bool first_time_kinematic; - bool first_integration; - void _update_inertia(); + PhysicsServer2D::CCDMode continuous_cd_mode = PhysicsServer2D::CCD_MODE_DISABLED; + bool omit_force_integration = false; + bool active = true; + bool can_sleep = true; + bool first_time_kinematic = false; + void _mass_properties_changed(); virtual void _shapes_changed(); Transform2D new_transform; @@ -115,7 +122,7 @@ class Body2DSW : public CollisionObject2DSW { }; Vector<Contact> contacts; //no contacts by default - int contact_count; + int contact_count = 0; void *body_state_callback_instance = nullptr; PhysicsServer2D::BodyStateCallback body_state_callback = nullptr; @@ -129,7 +136,7 @@ class Body2DSW : public CollisionObject2DSW { PhysicsDirectBodyState2DSW *direct_state = nullptr; - uint64_t island_step; + uint64_t island_step = 0; _FORCE_INLINE_ void _compute_area_gravity_and_damping(const Area2DSW *p_area); @@ -207,7 +214,7 @@ public: _FORCE_INLINE_ void apply_impulse(const Vector2 &p_impulse, const Vector2 &p_position = Vector2()) { linear_velocity += p_impulse * _inv_mass; - angular_velocity += _inv_inertia * p_position.cross(p_impulse); + angular_velocity += _inv_inertia * (p_position - center_of_mass).cross(p_impulse); } _FORCE_INLINE_ void apply_torque_impulse(real_t p_torque) { @@ -216,7 +223,7 @@ public: _FORCE_INLINE_ void apply_bias_impulse(const Vector2 &p_impulse, const Vector2 &p_position = Vector2()) { biased_linear_velocity += p_impulse * _inv_mass; - biased_angular_velocity += _inv_inertia * p_position.cross(p_impulse); + biased_angular_velocity += _inv_inertia * (p_position - center_of_mass).cross(p_impulse); } void set_active(bool p_active); @@ -229,8 +236,8 @@ public: set_active(true); } - void set_param(PhysicsServer2D::BodyParameter p_param, real_t); - real_t get_param(PhysicsServer2D::BodyParameter p_param) const; + void set_param(PhysicsServer2D::BodyParameter p_param, const Variant &p_value); + Variant get_param(PhysicsServer2D::BodyParameter p_param) const; void set_mode(PhysicsServer2D::BodyMode p_mode); PhysicsServer2D::BodyMode get_mode() const; @@ -250,7 +257,7 @@ public: _FORCE_INLINE_ void add_force(const Vector2 &p_force, const Vector2 &p_position = Vector2()) { applied_force += p_force; - applied_torque += p_position.cross(p_force); + applied_torque += (p_position - center_of_mass).cross(p_force); } _FORCE_INLINE_ void add_torque(real_t p_torque) { @@ -262,8 +269,10 @@ public: void set_space(Space2DSW *p_space); - void update_inertias(); + void update_mass_properties(); + void reset_mass_properties(); + _FORCE_INLINE_ Vector2 get_center_of_mass() const { return center_of_mass; } _FORCE_INLINE_ real_t get_inv_mass() const { return _inv_mass; } _FORCE_INLINE_ real_t get_inv_inertia() const { return _inv_inertia; } _FORCE_INLINE_ real_t get_friction() const { return friction; } diff --git a/servers/physics_2d/body_direct_state_2d_sw.cpp b/servers/physics_2d/body_direct_state_2d_sw.cpp index 1f68cca843..58250c3077 100644 --- a/servers/physics_2d/body_direct_state_2d_sw.cpp +++ b/servers/physics_2d/body_direct_state_2d_sw.cpp @@ -46,6 +46,10 @@ real_t PhysicsDirectBodyState2DSW::get_total_linear_damp() const { return body->area_linear_damp; } +Vector2 PhysicsDirectBodyState2DSW::get_center_of_mass() const { + return body->get_center_of_mass(); +} + real_t PhysicsDirectBodyState2DSW::get_inverse_mass() const { return body->get_inv_mass(); } diff --git a/servers/physics_2d/body_direct_state_2d_sw.h b/servers/physics_2d/body_direct_state_2d_sw.h index aef9186086..34faa174d8 100644 --- a/servers/physics_2d/body_direct_state_2d_sw.h +++ b/servers/physics_2d/body_direct_state_2d_sw.h @@ -45,6 +45,7 @@ public: virtual real_t get_total_angular_damp() const override; virtual real_t get_total_linear_damp() const override; + virtual Vector2 get_center_of_mass() const override; virtual real_t get_inverse_mass() const override; virtual real_t get_inverse_inertia() const override; diff --git a/servers/physics_2d/joints_2d_sw.cpp b/servers/physics_2d/joints_2d_sw.cpp index 5a0a628fbc..fa8499a81d 100644 --- a/servers/physics_2d/joints_2d_sw.cpp +++ b/servers/physics_2d/joints_2d_sw.cpp @@ -68,13 +68,13 @@ static inline real_t k_scalar(Body2DSW *a, Body2DSW *b, const Vector2 &rA, const { value += a->get_inv_mass(); - real_t rcn = rA.cross(n); + real_t rcn = (rA - a->get_center_of_mass()).cross(n); value += a->get_inv_inertia() * rcn * rcn; } if (b) { value += b->get_inv_mass(); - real_t rcn = rB.cross(n); + real_t rcn = (rB - b->get_center_of_mass()).cross(n); value += b->get_inv_inertia() * rcn * rcn; } @@ -83,9 +83,9 @@ static inline real_t k_scalar(Body2DSW *a, Body2DSW *b, const Vector2 &rA, const static inline Vector2 relative_velocity(Body2DSW *a, Body2DSW *b, Vector2 rA, Vector2 rB) { - Vector2 sum = a->get_linear_velocity() - rA.orthogonal() * a->get_angular_velocity(); + Vector2 sum = a->get_linear_velocity() - (rA - a->get_center_of_mass()).orthogonal() * a->get_angular_velocity(); if (b) { - return (b->get_linear_velocity() - rB.orthogonal() * b->get_angular_velocity()) - sum; + return (b->get_linear_velocity() - (rB - b->get_center_of_mass()).orthogonal() * b->get_angular_velocity()) - sum; } else { return -sum; } @@ -172,11 +172,11 @@ bool PinJoint2DSW::pre_solve(real_t p_step) { void PinJoint2DSW::solve(real_t p_step) { // compute relative velocity - Vector2 vA = A->get_linear_velocity() - custom_cross(rA, A->get_angular_velocity()); + Vector2 vA = A->get_linear_velocity() - custom_cross(rA - A->get_center_of_mass(), A->get_angular_velocity()); Vector2 rel_vel; if (B) { - rel_vel = B->get_linear_velocity() - custom_cross(rB, B->get_angular_velocity()) - vA; + rel_vel = B->get_linear_velocity() - custom_cross(rB - B->get_center_of_mass(), B->get_angular_velocity()) - vA; } else { rel_vel = -vA; } @@ -238,6 +238,9 @@ k_tensor(Body2DSW *a, Body2DSW *b, Vector2 r1, Vector2 r2, Vector2 *k1, Vector2 k21 = 0.0f; k22 = m_sum; + r1 -= a->get_center_of_mass(); + r2 -= b->get_center_of_mass(); + // add the influence from r1 real_t a_i_inv = a->get_inv_inertia(); real_t r1xsq = r1.x * r1.x * a_i_inv; diff --git a/servers/physics_2d/physics_server_2d_sw.cpp b/servers/physics_2d/physics_server_2d_sw.cpp index 85b5e40752..d0a42ca95b 100644 --- a/servers/physics_2d/physics_server_2d_sw.cpp +++ b/servers/physics_2d/physics_server_2d_sw.cpp @@ -743,20 +743,27 @@ uint32_t PhysicsServer2DSW::body_get_collision_mask(RID p_body) const { return body->get_collision_mask(); }; -void PhysicsServer2DSW::body_set_param(RID p_body, BodyParameter p_param, real_t p_value) { +void PhysicsServer2DSW::body_set_param(RID p_body, BodyParameter p_param, const Variant &p_value) { Body2DSW *body = body_owner.getornull(p_body); ERR_FAIL_COND(!body); body->set_param(p_param, p_value); }; -real_t PhysicsServer2DSW::body_get_param(RID p_body, BodyParameter p_param) const { +Variant PhysicsServer2DSW::body_get_param(RID p_body, BodyParameter p_param) const { Body2DSW *body = body_owner.getornull(p_body); ERR_FAIL_COND_V(!body, 0); return body->get_param(p_param); }; +void PhysicsServer2DSW::body_reset_mass_properties(RID p_body) { + Body2DSW *body = body_owner.getornull(p_body); + ERR_FAIL_COND(!body); + + return body->reset_mass_properties(); +} + void PhysicsServer2DSW::body_set_state(RID p_body, BodyState p_state, const Variant &p_variant) { Body2DSW *body = body_owner.getornull(p_body); ERR_FAIL_COND(!body); diff --git a/servers/physics_2d/physics_server_2d_sw.h b/servers/physics_2d/physics_server_2d_sw.h index ef1306cf59..6a2d9e37e0 100644 --- a/servers/physics_2d/physics_server_2d_sw.h +++ b/servers/physics_2d/physics_server_2d_sw.h @@ -205,8 +205,10 @@ public: virtual void body_set_collision_mask(RID p_body, uint32_t p_mask) override; virtual uint32_t body_get_collision_mask(RID p_body) const override; - virtual void body_set_param(RID p_body, BodyParameter p_param, real_t p_value) override; - virtual real_t body_get_param(RID p_body, BodyParameter p_param) const override; + virtual void body_set_param(RID p_body, BodyParameter p_param, const Variant &p_value) override; + virtual Variant body_get_param(RID p_body, BodyParameter p_param) const override; + + virtual void body_reset_mass_properties(RID p_body) override; virtual void body_set_state(RID p_body, BodyState p_state, const Variant &p_variant) override; virtual Variant body_get_state(RID p_body, BodyState p_state) const override; diff --git a/servers/physics_2d/physics_server_2d_wrap_mt.h b/servers/physics_2d/physics_server_2d_wrap_mt.h index 6761e37daa..e65c4f5f3a 100644 --- a/servers/physics_2d/physics_server_2d_wrap_mt.h +++ b/servers/physics_2d/physics_server_2d_wrap_mt.h @@ -212,8 +212,10 @@ public: FUNC2(body_set_collision_mask, RID, uint32_t); FUNC1RC(uint32_t, body_get_collision_mask, RID); - FUNC3(body_set_param, RID, BodyParameter, real_t); - FUNC2RC(real_t, body_get_param, RID, BodyParameter); + FUNC3(body_set_param, RID, BodyParameter, const Variant &); + FUNC2RC(Variant, body_get_param, RID, BodyParameter); + + FUNC1(body_reset_mass_properties, RID); FUNC3(body_set_state, RID, BodyState, const Variant &); FUNC2RC(Variant, body_get_state, RID, BodyState); diff --git a/servers/physics_2d/space_2d_sw.cpp b/servers/physics_2d/space_2d_sw.cpp index 6c23f49928..7dbd1243cc 100644 --- a/servers/physics_2d/space_2d_sw.cpp +++ b/servers/physics_2d/space_2d_sw.cpp @@ -482,7 +482,7 @@ bool PhysicsDirectSpaceState2DSW::rest_info(RID p_shape, const Transform2D &p_sh r_info->metadata = rcd.best_object->get_shape_metadata(rcd.best_shape); if (rcd.best_object->get_type() == CollisionObject2DSW::TYPE_BODY) { const Body2DSW *body = static_cast<const Body2DSW *>(rcd.best_object); - Vector2 rel_vec = r_info->point - body->get_transform().get_origin(); + Vector2 rel_vec = r_info->point - (body->get_transform().get_origin() + body->get_center_of_mass()); r_info->linear_velocity = Vector2(-body->get_angular_velocity() * rel_vec.y, body->get_angular_velocity() * rel_vec.x) + body->get_linear_velocity(); } else { @@ -961,7 +961,7 @@ bool Space2DSW::test_body_motion(Body2DSW *p_body, const Transform2D &p_from, co r_result->collider_metadata = rcd.best_object->get_shape_metadata(rcd.best_shape); const Body2DSW *body = static_cast<const Body2DSW *>(rcd.best_object); - Vector2 rel_vec = r_result->collision_point - body->get_transform().get_origin(); + Vector2 rel_vec = r_result->collision_point - (body->get_transform().get_origin() + body->get_center_of_mass()); r_result->collider_velocity = Vector2(-body->get_angular_velocity() * rel_vec.y, body->get_angular_velocity() * rel_vec.x) + body->get_linear_velocity(); r_result->travel = safe * p_motion; @@ -1041,12 +1041,12 @@ void Space2DSW::body_remove_from_active_list(SelfList<Body2DSW> *p_body) { active_list.remove(p_body); } -void Space2DSW::body_add_to_inertia_update_list(SelfList<Body2DSW> *p_body) { - inertia_update_list.add(p_body); +void Space2DSW::body_add_to_mass_properties_update_list(SelfList<Body2DSW> *p_body) { + mass_properties_update_list.add(p_body); } -void Space2DSW::body_remove_from_inertia_update_list(SelfList<Body2DSW> *p_body) { - inertia_update_list.remove(p_body); +void Space2DSW::body_remove_from_mass_properties_update_list(SelfList<Body2DSW> *p_body) { + mass_properties_update_list.remove(p_body); } BroadPhase2DSW *Space2DSW::get_broadphase() { @@ -1112,9 +1112,9 @@ void Space2DSW::call_queries() { void Space2DSW::setup() { contact_debug_count = 0; - while (inertia_update_list.first()) { - inertia_update_list.first()->self()->update_inertias(); - inertia_update_list.remove(inertia_update_list.first()); + while (mass_properties_update_list.first()) { + mass_properties_update_list.first()->self()->update_mass_properties(); + mass_properties_update_list.remove(mass_properties_update_list.first()); } } diff --git a/servers/physics_2d/space_2d_sw.h b/servers/physics_2d/space_2d_sw.h index 5de071a475..ad82a14af5 100644 --- a/servers/physics_2d/space_2d_sw.h +++ b/servers/physics_2d/space_2d_sw.h @@ -49,13 +49,13 @@ class PhysicsDirectSpaceState2DSW : public PhysicsDirectSpaceState2D { public: Space2DSW *space; - virtual int intersect_point(const Vector2 &p_point, ShapeResult *r_results, int p_result_max, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = 0xFFFFFFFF, bool p_collide_with_bodies = true, bool p_collide_with_areas = false, bool p_pick_point = false) override; - virtual int intersect_point_on_canvas(const Vector2 &p_point, ObjectID p_canvas_instance_id, ShapeResult *r_results, int p_result_max, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = 0xFFFFFFFF, bool p_collide_with_bodies = true, bool p_collide_with_areas = false, bool p_pick_point = false) override; - virtual bool intersect_ray(const Vector2 &p_from, const Vector2 &p_to, RayResult &r_result, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = 0xFFFFFFFF, bool p_collide_with_bodies = true, bool p_collide_with_areas = false) override; - virtual int intersect_shape(const RID &p_shape, const Transform2D &p_xform, const Vector2 &p_motion, real_t p_margin, ShapeResult *r_results, int p_result_max, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = 0xFFFFFFFF, bool p_collide_with_bodies = true, bool p_collide_with_areas = false) override; - virtual bool cast_motion(const RID &p_shape, const Transform2D &p_xform, const Vector2 &p_motion, real_t p_margin, real_t &p_closest_safe, real_t &p_closest_unsafe, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = 0xFFFFFFFF, bool p_collide_with_bodies = true, bool p_collide_with_areas = false) override; - virtual bool collide_shape(RID p_shape, const Transform2D &p_shape_xform, const Vector2 &p_motion, real_t p_margin, Vector2 *r_results, int p_result_max, int &r_result_count, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = 0xFFFFFFFF, bool p_collide_with_bodies = true, bool p_collide_with_areas = false) override; - virtual bool rest_info(RID p_shape, const Transform2D &p_shape_xform, const Vector2 &p_motion, real_t p_margin, ShapeRestInfo *r_info, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = 0xFFFFFFFF, bool p_collide_with_bodies = true, bool p_collide_with_areas = false) override; + virtual int intersect_point(const Vector2 &p_point, ShapeResult *r_results, int p_result_max, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = UINT32_MAX, bool p_collide_with_bodies = true, bool p_collide_with_areas = false, bool p_pick_point = false) override; + virtual int intersect_point_on_canvas(const Vector2 &p_point, ObjectID p_canvas_instance_id, ShapeResult *r_results, int p_result_max, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = UINT32_MAX, bool p_collide_with_bodies = true, bool p_collide_with_areas = false, bool p_pick_point = false) override; + virtual bool intersect_ray(const Vector2 &p_from, const Vector2 &p_to, RayResult &r_result, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = UINT32_MAX, bool p_collide_with_bodies = true, bool p_collide_with_areas = false) override; + virtual int intersect_shape(const RID &p_shape, const Transform2D &p_xform, const Vector2 &p_motion, real_t p_margin, ShapeResult *r_results, int p_result_max, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = UINT32_MAX, bool p_collide_with_bodies = true, bool p_collide_with_areas = false) override; + virtual bool cast_motion(const RID &p_shape, const Transform2D &p_xform, const Vector2 &p_motion, real_t p_margin, real_t &p_closest_safe, real_t &p_closest_unsafe, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = UINT32_MAX, bool p_collide_with_bodies = true, bool p_collide_with_areas = false) override; + virtual bool collide_shape(RID p_shape, const Transform2D &p_shape_xform, const Vector2 &p_motion, real_t p_margin, Vector2 *r_results, int p_result_max, int &r_result_count, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = UINT32_MAX, bool p_collide_with_bodies = true, bool p_collide_with_areas = false) override; + virtual bool rest_info(RID p_shape, const Transform2D &p_shape_xform, const Vector2 &p_motion, real_t p_margin, ShapeRestInfo *r_info, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = UINT32_MAX, bool p_collide_with_bodies = true, bool p_collide_with_areas = false) override; PhysicsDirectSpaceState2DSW(); }; @@ -86,7 +86,7 @@ private: BroadPhase2DSW *broadphase; SelfList<Body2DSW>::List active_list; - SelfList<Body2DSW>::List inertia_update_list; + SelfList<Body2DSW>::List mass_properties_update_list; SelfList<Body2DSW>::List state_query_list; SelfList<Area2DSW>::List monitor_query_list; SelfList<Area2DSW>::List area_moved_list; @@ -140,8 +140,8 @@ public: const SelfList<Body2DSW>::List &get_active_body_list() const; void body_add_to_active_list(SelfList<Body2DSW> *p_body); void body_remove_from_active_list(SelfList<Body2DSW> *p_body); - void body_add_to_inertia_update_list(SelfList<Body2DSW> *p_body); - void body_remove_from_inertia_update_list(SelfList<Body2DSW> *p_body); + void body_add_to_mass_properties_update_list(SelfList<Body2DSW> *p_body); + void body_remove_from_mass_properties_update_list(SelfList<Body2DSW> *p_body); void area_add_to_moved_list(SelfList<Area2DSW> *p_area); void area_remove_from_moved_list(SelfList<Area2DSW> *p_area); const SelfList<Area2DSW>::List &get_moved_area_list() const; diff --git a/servers/physics_3d/body_3d_sw.cpp b/servers/physics_3d/body_3d_sw.cpp index 7f62e4eb85..41745545d8 100644 --- a/servers/physics_3d/body_3d_sw.cpp +++ b/servers/physics_3d/body_3d_sw.cpp @@ -34,9 +34,9 @@ #include "body_direct_state_3d_sw.h" #include "space_3d_sw.h" -void Body3DSW::_update_inertia() { - if (get_space() && !inertia_update_list.in_list()) { - get_space()->body_add_to_inertia_update_list(&inertia_update_list); +void Body3DSW::_mass_properties_changed() { + if (get_space() && !mass_properties_update_list.in_list() && (calculate_inertia || calculate_center_of_mass)) { + get_space()->body_add_to_mass_properties_update_list(&mass_properties_update_list); } } @@ -44,7 +44,7 @@ void Body3DSW::_update_transform_dependant() { center_of_mass = get_transform().basis.xform(center_of_mass_local); principal_inertia_axes = get_transform().basis * principal_inertia_axes_local; - // update inertia tensor + // Update inertia tensor. Basis tb = principal_inertia_axes; Basis tbt = tb.transposed(); Basis diag; @@ -52,74 +52,95 @@ void Body3DSW::_update_transform_dependant() { _inv_inertia_tensor = tb * diag * tbt; } -void Body3DSW::update_inertias() { +void Body3DSW::update_mass_properties() { // Update shapes and motions. switch (mode) { case PhysicsServer3D::BODY_MODE_DYNAMIC: { - // Update tensor for all shapes, not the best way but should be somehow OK. (inspired from bullet) real_t total_area = 0; - for (int i = 0; i < get_shape_count(); i++) { + if (is_shape_disabled(i)) { + continue; + } + total_area += get_shape_area(i); } - // We have to recompute the center of mass. - center_of_mass_local.zero(); + if (calculate_center_of_mass) { + // We have to recompute the center of mass. + center_of_mass_local.zero(); - if (total_area != 0.0) { - for (int i = 0; i < get_shape_count(); i++) { - real_t area = get_shape_area(i); + if (total_area != 0.0) { + for (int i = 0; i < get_shape_count(); i++) { + if (is_shape_disabled(i)) { + continue; + } - real_t mass = area * this->mass / total_area; + real_t area = get_shape_area(i); - // NOTE: we assume that the shape origin is also its center of mass. - center_of_mass_local += mass * get_shape_transform(i).origin; - } + real_t mass = area * this->mass / total_area; + + // NOTE: we assume that the shape origin is also its center of mass. + center_of_mass_local += mass * get_shape_transform(i).origin; + } - center_of_mass_local /= mass; + center_of_mass_local /= mass; + } } - // Recompute the inertia tensor. - Basis inertia_tensor; - inertia_tensor.set_zero(); - bool inertia_set = false; + if (calculate_inertia) { + // Recompute the inertia tensor. + Basis inertia_tensor; + inertia_tensor.set_zero(); + bool inertia_set = false; - for (int i = 0; i < get_shape_count(); i++) { - if (is_shape_disabled(i)) { - continue; - } + for (int i = 0; i < get_shape_count(); i++) { + if (is_shape_disabled(i)) { + continue; + } - real_t area = get_shape_area(i); - if (area == 0.0) { - continue; - } + real_t area = get_shape_area(i); + if (area == 0.0) { + continue; + } - inertia_set = true; + inertia_set = true; - const Shape3DSW *shape = get_shape(i); + const Shape3DSW *shape = get_shape(i); - real_t mass = area * this->mass / total_area; + real_t mass = area * this->mass / total_area; - Basis shape_inertia_tensor = shape->get_moment_of_inertia(mass).to_diagonal_matrix(); - Transform3D shape_transform = get_shape_transform(i); - Basis shape_basis = shape_transform.basis.orthonormalized(); + Basis shape_inertia_tensor = shape->get_moment_of_inertia(mass).to_diagonal_matrix(); + Transform3D shape_transform = get_shape_transform(i); + Basis shape_basis = shape_transform.basis.orthonormalized(); - // NOTE: we don't take the scale of collision shapes into account when computing the inertia tensor! - shape_inertia_tensor = shape_basis * shape_inertia_tensor * shape_basis.transposed(); + // NOTE: we don't take the scale of collision shapes into account when computing the inertia tensor! + shape_inertia_tensor = shape_basis * shape_inertia_tensor * shape_basis.transposed(); - Vector3 shape_origin = shape_transform.origin - center_of_mass_local; - inertia_tensor += shape_inertia_tensor + (Basis() * shape_origin.dot(shape_origin) - shape_origin.outer(shape_origin)) * mass; - } + Vector3 shape_origin = shape_transform.origin - center_of_mass_local; + inertia_tensor += shape_inertia_tensor + (Basis() * shape_origin.dot(shape_origin) - shape_origin.outer(shape_origin)) * mass; + } - // Set the inertia to a valid value when there are no valid shapes. - if (!inertia_set) { - inertia_tensor.set_diagonal(Vector3(1.0, 1.0, 1.0)); - } + // Set the inertia to a valid value when there are no valid shapes. + if (!inertia_set) { + inertia_tensor.set_diagonal(Vector3(1.0, 1.0, 1.0)); + } - // Compute the principal axes of inertia. - principal_inertia_axes_local = inertia_tensor.diagonalize().transposed(); - _inv_inertia = inertia_tensor.get_main_diagonal().inverse(); + // Handle partial custom inertia. + if (inertia.x > 0.0) { + inertia_tensor[0][0] = inertia.x; + } + if (inertia.y > 0.0) { + inertia_tensor[1][1] = inertia.y; + } + if (inertia.z > 0.0) { + inertia_tensor[2][2] = inertia.z; + } + + // Compute the principal axes of inertia. + principal_inertia_axes_local = inertia_tensor.diagonalize().transposed(); + _inv_inertia = inertia_tensor.get_main_diagonal().inverse(); + } if (mass) { _inv_mass = 1.0 / mass; @@ -128,10 +149,9 @@ void Body3DSW::update_inertias() { } } break; - case PhysicsServer3D::BODY_MODE_KINEMATIC: case PhysicsServer3D::BODY_MODE_STATIC: { - _inv_inertia_tensor.set_zero(); + _inv_inertia = Vector3(); _inv_mass = 0; } break; case PhysicsServer3D::BODY_MODE_DYNAMIC_LOCKED: { @@ -141,11 +161,15 @@ void Body3DSW::update_inertias() { } break; } - //_update_shapes(); - _update_transform_dependant(); } +void Body3DSW::reset_mass_properties() { + calculate_inertia = true; + calculate_center_of_mass = true; + _mass_properties_changed(); +} + void Body3DSW::set_active(bool p_active) { if (active == p_active) { return; @@ -165,7 +189,7 @@ void Body3DSW::set_active(bool p_active) { } } -void Body3DSW::set_param(PhysicsServer3D::BodyParameter p_param, real_t p_value) { +void Body3DSW::set_param(PhysicsServer3D::BodyParameter p_param, const Variant &p_value) { switch (p_param) { case PhysicsServer3D::BODY_PARAM_BOUNCE: { bounce = p_value; @@ -174,10 +198,33 @@ void Body3DSW::set_param(PhysicsServer3D::BodyParameter p_param, real_t p_value) friction = p_value; } break; case PhysicsServer3D::BODY_PARAM_MASS: { - ERR_FAIL_COND(p_value <= 0); - mass = p_value; - _update_inertia(); - + real_t mass_value = p_value; + ERR_FAIL_COND(mass_value <= 0); + mass = mass_value; + if (mode >= PhysicsServer3D::BODY_MODE_DYNAMIC) { + _mass_properties_changed(); + } + } break; + case PhysicsServer3D::BODY_PARAM_INERTIA: { + inertia = p_value; + if ((inertia.x <= 0.0) || (inertia.y <= 0.0) || (inertia.z <= 0.0)) { + calculate_inertia = true; + if (mode == PhysicsServer3D::BODY_MODE_DYNAMIC) { + _mass_properties_changed(); + } + } else { + calculate_inertia = false; + if (mode == PhysicsServer3D::BODY_MODE_DYNAMIC) { + principal_inertia_axes_local.set_diagonal(Vector3(1.0, 1.0, 1.0)); + _inv_inertia = inertia.inverse(); + _update_transform_dependant(); + } + } + } break; + case PhysicsServer3D::BODY_PARAM_CENTER_OF_MASS: { + calculate_center_of_mass = false; + center_of_mass_local = p_value; + _update_transform_dependant(); } break; case PhysicsServer3D::BODY_PARAM_GRAVITY_SCALE: { gravity_scale = p_value; @@ -193,7 +240,7 @@ void Body3DSW::set_param(PhysicsServer3D::BodyParameter p_param, real_t p_value) } } -real_t Body3DSW::get_param(PhysicsServer3D::BodyParameter p_param) const { +Variant Body3DSW::get_param(PhysicsServer3D::BodyParameter p_param) const { switch (p_param) { case PhysicsServer3D::BODY_PARAM_BOUNCE: { return bounce; @@ -204,6 +251,16 @@ real_t Body3DSW::get_param(PhysicsServer3D::BodyParameter p_param) const { case PhysicsServer3D::BODY_PARAM_MASS: { return mass; } break; + case PhysicsServer3D::BODY_PARAM_INERTIA: { + if (mode == PhysicsServer3D::BODY_MODE_DYNAMIC) { + return _inv_inertia.inverse(); + } else { + return Vector3(); + } + } break; + case PhysicsServer3D::BODY_PARAM_CENTER_OF_MASS: { + return center_of_mass; + } break; case PhysicsServer3D::BODY_PARAM_GRAVITY_SCALE: { return gravity_scale; } break; @@ -226,40 +283,42 @@ void Body3DSW::set_mode(PhysicsServer3D::BodyMode p_mode) { mode = p_mode; switch (p_mode) { - //CLEAR UP EVERYTHING IN CASE IT NOT WORKS! case PhysicsServer3D::BODY_MODE_STATIC: case PhysicsServer3D::BODY_MODE_KINEMATIC: { _set_inv_transform(get_transform().affine_inverse()); _inv_mass = 0; + _inv_inertia = Vector3(); _set_static(p_mode == PhysicsServer3D::BODY_MODE_STATIC); - //set_active(p_mode==PhysicsServer3D::BODY_MODE_KINEMATIC); set_active(p_mode == PhysicsServer3D::BODY_MODE_KINEMATIC && contacts.size()); linear_velocity = Vector3(); angular_velocity = Vector3(); if (mode == PhysicsServer3D::BODY_MODE_KINEMATIC && prev != mode) { first_time_kinematic = true; } + _update_transform_dependant(); } break; case PhysicsServer3D::BODY_MODE_DYNAMIC: { _inv_mass = mass > 0 ? (1.0 / mass) : 0; + if (!calculate_inertia) { + principal_inertia_axes_local.set_diagonal(Vector3(1.0, 1.0, 1.0)); + _inv_inertia = inertia.inverse(); + _update_transform_dependant(); + } + _mass_properties_changed(); _set_static(false); set_active(true); } break; case PhysicsServer3D::BODY_MODE_DYNAMIC_LOCKED: { _inv_mass = mass > 0 ? (1.0 / mass) : 0; + _inv_inertia = Vector3(); + angular_velocity = Vector3(); + _update_transform_dependant(); _set_static(false); set_active(true); - angular_velocity = Vector3(); - } break; + } } - - _update_inertia(); - /* - if (get_space()) - _update_queries(); - */ } PhysicsServer3D::BodyMode Body3DSW::get_mode() const { @@ -267,7 +326,7 @@ PhysicsServer3D::BodyMode Body3DSW::get_mode() const { } void Body3DSW::_shapes_changed() { - _update_inertia(); + _mass_properties_changed(); } void Body3DSW::set_state(PhysicsServer3D::BodyState p_state, const Variant &p_variant) { @@ -302,10 +361,12 @@ void Body3DSW::set_state(PhysicsServer3D::BodyState p_state, const Variant &p_va } break; case PhysicsServer3D::BODY_STATE_LINEAR_VELOCITY: { linear_velocity = p_variant; + constant_linear_velocity = linear_velocity; wakeup(); } break; case PhysicsServer3D::BODY_STATE_ANGULAR_VELOCITY: { angular_velocity = p_variant; + constant_angular_velocity = angular_velocity; wakeup(); } break; @@ -326,7 +387,7 @@ void Body3DSW::set_state(PhysicsServer3D::BodyState p_state, const Variant &p_va } break; case PhysicsServer3D::BODY_STATE_CAN_SLEEP: { can_sleep = p_variant; - if (mode == PhysicsServer3D::BODY_MODE_DYNAMIC && !active && !can_sleep) { + if (mode >= PhysicsServer3D::BODY_MODE_DYNAMIC && !active && !can_sleep) { set_active(true); } @@ -358,8 +419,8 @@ Variant Body3DSW::get_state(PhysicsServer3D::BodyState p_state) const { void Body3DSW::set_space(Space3DSW *p_space) { if (get_space()) { - if (inertia_update_list.in_list()) { - get_space()->body_remove_from_inertia_update_list(&inertia_update_list); + if (mass_properties_update_list.in_list()) { + get_space()->body_remove_from_mass_properties_update_list(&mass_properties_update_list); } if (active_list.in_list()) { get_space()->body_remove_from_active_list(&active_list); @@ -372,13 +433,11 @@ void Body3DSW::set_space(Space3DSW *p_space) { _set_space(p_space); if (get_space()) { - _update_inertia(); + _mass_properties_changed(); if (active) { get_space()->body_add_to_active_list(&active_list); } } - - first_integration = true; } void Body3DSW::_compute_area_gravity_and_damping(const Area3DSW *p_area) { @@ -473,7 +532,7 @@ void Body3DSW::integrate_forces(real_t p_step) { //compute motion, angular and etc. velocities from prev transform motion = new_transform.origin - get_transform().origin; do_motion = true; - linear_velocity = motion / p_step; + linear_velocity = constant_linear_velocity + motion / p_step; //compute a FAKE angular velocity, not so easy Basis rot = new_transform.basis.orthonormalized() * get_transform().basis.orthonormalized().transposed(); @@ -482,9 +541,9 @@ void Body3DSW::integrate_forces(real_t p_step) { rot.get_axis_angle(axis, angle); axis.normalize(); - angular_velocity = axis * (angle / p_step); + angular_velocity = constant_angular_velocity + axis * (angle / p_step); } else { - if (!omit_force_integration && !first_integration) { + if (!omit_force_integration) { //overridden by direct state query Vector3 force = gravity * mass; @@ -518,7 +577,6 @@ void Body3DSW::integrate_forces(real_t p_step) { applied_force = Vector3(); applied_torque = Vector3(); - first_integration = false; //motion=linear_velocity*p_step; @@ -640,7 +698,7 @@ void Body3DSW::wakeup_neighbours() { continue; } Body3DSW *b = n[i]; - if (b->mode != PhysicsServer3D::BODY_MODE_DYNAMIC) { + if (b->mode < PhysicsServer3D::BODY_MODE_DYNAMIC) { continue; } @@ -717,32 +775,9 @@ PhysicsDirectBodyState3DSW *Body3DSW::get_direct_state() { Body3DSW::Body3DSW() : CollisionObject3DSW(TYPE_BODY), active_list(this), - inertia_update_list(this), + mass_properties_update_list(this), direct_state_query_list(this) { - mode = PhysicsServer3D::BODY_MODE_DYNAMIC; - active = true; - - mass = 1; - _inv_mass = 1; - bounce = 0; - friction = 1; - omit_force_integration = false; - //applied_torque=0; - island_step = 0; - first_time_kinematic = false; - first_integration = false; _set_static(false); - - contact_count = 0; - gravity_scale = 1.0; - linear_damp = -1; - angular_damp = -1; - area_angular_damp = 0; - area_linear_damp = 0; - - still_time = 0; - continuous_cd = false; - can_sleep = true; } Body3DSW::~Body3DSW() { diff --git a/servers/physics_3d/body_3d_sw.h b/servers/physics_3d/body_3d_sw.h index f58f40652b..8b74c7e5b9 100644 --- a/servers/physics_3d/body_3d_sw.h +++ b/servers/physics_3d/body_3d_sw.h @@ -39,24 +39,28 @@ class Constraint3DSW; class PhysicsDirectBodyState3DSW; class Body3DSW : public CollisionObject3DSW { - PhysicsServer3D::BodyMode mode; + PhysicsServer3D::BodyMode mode = PhysicsServer3D::BODY_MODE_DYNAMIC; Vector3 linear_velocity; Vector3 angular_velocity; + Vector3 constant_linear_velocity; + Vector3 constant_angular_velocity; + Vector3 biased_linear_velocity; Vector3 biased_angular_velocity; - real_t mass; - real_t bounce; - real_t friction; + real_t mass = 1.0; + real_t bounce = 0.0; + real_t friction = 1.0; + Vector3 inertia; - real_t linear_damp; - real_t angular_damp; - real_t gravity_scale; + real_t linear_damp = -1.0; + real_t angular_damp = -1.0; + real_t gravity_scale = 1.0; uint16_t locked_axis = 0; - real_t _inv_mass; + real_t _inv_mass = 1.0; Vector3 _inv_inertia; // Relative to the principal axes of inertia // Relative to the local frame of reference @@ -68,30 +72,32 @@ class Body3DSW : public CollisionObject3DSW { Basis principal_inertia_axes; Vector3 center_of_mass; + bool calculate_inertia = true; + bool calculate_center_of_mass = true; + Vector3 gravity; - real_t still_time; + real_t still_time = 0.0; Vector3 applied_force; Vector3 applied_torque; - real_t area_angular_damp; - real_t area_linear_damp; + real_t area_angular_damp = 0.0; + real_t area_linear_damp = 0.0; SelfList<Body3DSW> active_list; - SelfList<Body3DSW> inertia_update_list; + SelfList<Body3DSW> mass_properties_update_list; SelfList<Body3DSW> direct_state_query_list; VSet<RID> exceptions; - bool omit_force_integration; - bool active; + bool omit_force_integration = false; + bool active = true; - bool first_integration; + bool continuous_cd = false; + bool can_sleep = true; + bool first_time_kinematic = false; - bool continuous_cd; - bool can_sleep; - bool first_time_kinematic; - void _update_inertia(); + void _mass_properties_changed(); virtual void _shapes_changed(); Transform3D new_transform; @@ -112,7 +118,7 @@ class Body3DSW : public CollisionObject3DSW { }; Vector<Contact> contacts; //no contacts by default - int contact_count; + int contact_count = 0; void *body_state_callback_instance = nullptr; PhysicsServer3D::BodyStateCallback body_state_callback = nullptr; @@ -126,7 +132,7 @@ class Body3DSW : public CollisionObject3DSW { PhysicsDirectBodyState3DSW *direct_state = nullptr; - uint64_t island_step; + uint64_t island_step = 0; _FORCE_INLINE_ void _compute_area_gravity_and_damping(const Area3DSW *p_area); @@ -251,8 +257,8 @@ public: set_active(true); } - void set_param(PhysicsServer3D::BodyParameter p_param, real_t); - real_t get_param(PhysicsServer3D::BodyParameter p_param) const; + void set_param(PhysicsServer3D::BodyParameter p_param, const Variant &p_value); + Variant get_param(PhysicsServer3D::BodyParameter p_param) const; void set_mode(PhysicsServer3D::BodyMode p_mode); PhysicsServer3D::BodyMode get_mode() const; @@ -271,7 +277,8 @@ public: void set_space(Space3DSW *p_space); - void update_inertias(); + void update_mass_properties(); + void reset_mass_properties(); _FORCE_INLINE_ real_t get_inv_mass() const { return _inv_mass; } _FORCE_INLINE_ const Vector3 &get_inv_inertia() const { return _inv_inertia; } diff --git a/servers/physics_3d/physics_server_3d_sw.cpp b/servers/physics_3d/physics_server_3d_sw.cpp index a214e80c6c..8de54c5c48 100644 --- a/servers/physics_3d/physics_server_3d_sw.cpp +++ b/servers/physics_3d/physics_server_3d_sw.cpp @@ -643,20 +643,27 @@ uint32_t PhysicsServer3DSW::body_get_user_flags(RID p_body) const { return 0; }; -void PhysicsServer3DSW::body_set_param(RID p_body, BodyParameter p_param, real_t p_value) { +void PhysicsServer3DSW::body_set_param(RID p_body, BodyParameter p_param, const Variant &p_value) { Body3DSW *body = body_owner.getornull(p_body); ERR_FAIL_COND(!body); body->set_param(p_param, p_value); }; -real_t PhysicsServer3DSW::body_get_param(RID p_body, BodyParameter p_param) const { +Variant PhysicsServer3DSW::body_get_param(RID p_body, BodyParameter p_param) const { Body3DSW *body = body_owner.getornull(p_body); ERR_FAIL_COND_V(!body, 0); return body->get_param(p_param); }; +void PhysicsServer3DSW::body_reset_mass_properties(RID p_body) { + Body3DSW *body = body_owner.getornull(p_body); + ERR_FAIL_COND(!body); + + return body->reset_mass_properties(); +} + void PhysicsServer3DSW::body_set_state(RID p_body, BodyState p_state, const Variant &p_variant) { Body3DSW *body = body_owner.getornull(p_body); ERR_FAIL_COND(!body); diff --git a/servers/physics_3d/physics_server_3d_sw.h b/servers/physics_3d/physics_server_3d_sw.h index f283f83112..071ad0a694 100644 --- a/servers/physics_3d/physics_server_3d_sw.h +++ b/servers/physics_3d/physics_server_3d_sw.h @@ -198,8 +198,10 @@ public: virtual void body_set_user_flags(RID p_body, uint32_t p_flags) override; virtual uint32_t body_get_user_flags(RID p_body) const override; - virtual void body_set_param(RID p_body, BodyParameter p_param, real_t p_value) override; - virtual real_t body_get_param(RID p_body, BodyParameter p_param) const override; + virtual void body_set_param(RID p_body, BodyParameter p_param, const Variant &p_value) override; + virtual Variant body_get_param(RID p_body, BodyParameter p_param) const override; + + virtual void body_reset_mass_properties(RID p_body) override; virtual void body_set_state(RID p_body, BodyState p_state, const Variant &p_variant) override; virtual Variant body_get_state(RID p_body, BodyState p_state) const override; diff --git a/servers/physics_3d/physics_server_3d_wrap_mt.h b/servers/physics_3d/physics_server_3d_wrap_mt.h index 8865bd4bd2..58986969d4 100644 --- a/servers/physics_3d/physics_server_3d_wrap_mt.h +++ b/servers/physics_3d/physics_server_3d_wrap_mt.h @@ -210,8 +210,10 @@ public: FUNC2(body_set_user_flags, RID, uint32_t); FUNC1RC(uint32_t, body_get_user_flags, RID); - FUNC3(body_set_param, RID, BodyParameter, real_t); - FUNC2RC(real_t, body_get_param, RID, BodyParameter); + FUNC3(body_set_param, RID, BodyParameter, const Variant &); + FUNC2RC(Variant, body_get_param, RID, BodyParameter); + + FUNC1(body_reset_mass_properties, RID); FUNC3(body_set_state, RID, BodyState, const Variant &); FUNC2RC(Variant, body_get_state, RID, BodyState); diff --git a/servers/physics_3d/shape_3d_sw.cpp b/servers/physics_3d/shape_3d_sw.cpp index b81d3272c3..945d0120be 100644 --- a/servers/physics_3d/shape_3d_sw.cpp +++ b/servers/physics_3d/shape_3d_sw.cpp @@ -1676,6 +1676,17 @@ struct _HeightmapSegmentCullParams { FaceShape3DSW *face = nullptr; }; +struct _HeightmapGridCullState { + real_t length = 0.0; + real_t length_flat = 0.0; + + real_t dist = 0.0; + real_t prev_dist = 0.0; + + int x = 0; + int z = 0; +}; + _FORCE_INLINE_ bool _heightmap_face_cull_segment(_HeightmapSegmentCullParams &p_params) { Vector3 res; Vector3 normal; @@ -1688,11 +1699,11 @@ _FORCE_INLINE_ bool _heightmap_face_cull_segment(_HeightmapSegmentCullParams &p_ return false; } -_FORCE_INLINE_ bool _heightmap_cell_cull_segment(_HeightmapSegmentCullParams &p_params, int p_x, int p_z) { +_FORCE_INLINE_ bool _heightmap_cell_cull_segment(_HeightmapSegmentCullParams &p_params, const _HeightmapGridCullState &p_state) { // First triangle. - p_params.heightmap->_get_point(p_x, p_z, p_params.face->vertex[0]); - p_params.heightmap->_get_point(p_x + 1, p_z, p_params.face->vertex[1]); - p_params.heightmap->_get_point(p_x, p_z + 1, p_params.face->vertex[2]); + p_params.heightmap->_get_point(p_state.x, p_state.z, p_params.face->vertex[0]); + p_params.heightmap->_get_point(p_state.x + 1, p_state.z, p_params.face->vertex[1]); + p_params.heightmap->_get_point(p_state.x, p_state.z + 1, p_params.face->vertex[2]); p_params.face->normal = Plane(p_params.face->vertex[0], p_params.face->vertex[1], p_params.face->vertex[2]).normal; if (_heightmap_face_cull_segment(p_params)) { return true; @@ -1700,7 +1711,7 @@ _FORCE_INLINE_ bool _heightmap_cell_cull_segment(_HeightmapSegmentCullParams &p_ // Second triangle. p_params.face->vertex[0] = p_params.face->vertex[1]; - p_params.heightmap->_get_point(p_x + 1, p_z + 1, p_params.face->vertex[1]); + p_params.heightmap->_get_point(p_state.x + 1, p_state.z + 1, p_params.face->vertex[1]); p_params.face->normal = Plane(p_params.face->vertex[0], p_params.face->vertex[1], p_params.face->vertex[2]).normal; if (_heightmap_face_cull_segment(p_params)) { return true; @@ -1709,13 +1720,51 @@ _FORCE_INLINE_ bool _heightmap_cell_cull_segment(_HeightmapSegmentCullParams &p_ return false; } -bool HeightMapShape3DSW::intersect_segment(const Vector3 &p_begin, const Vector3 &p_end, Vector3 &r_point, Vector3 &r_normal) const { - if (heights.is_empty()) { +_FORCE_INLINE_ bool _heightmap_chunk_cull_segment(_HeightmapSegmentCullParams &p_params, const _HeightmapGridCullState &p_state) { + const HeightMapShape3DSW::Range &chunk = p_params.heightmap->_get_bounds_chunk(p_state.x, p_state.z); + + Vector3 enter_pos; + Vector3 exit_pos; + + if (p_state.length_flat > CMP_EPSILON) { + real_t flat_to_3d = p_state.length / p_state.length_flat; + real_t enter_param = p_state.prev_dist * flat_to_3d; + real_t exit_param = p_state.dist * flat_to_3d; + enter_pos = p_params.from + p_params.dir * enter_param; + exit_pos = p_params.from + p_params.dir * exit_param; + } else { + // Consider the ray vertical. + // (though we shouldn't reach this often because there is an early check up-front) + enter_pos = p_params.from; + exit_pos = p_params.to; + } + + // Transform positions to heightmap space. + enter_pos *= HeightMapShape3DSW::BOUNDS_CHUNK_SIZE; + exit_pos *= HeightMapShape3DSW::BOUNDS_CHUNK_SIZE; + + // We did enter the flat projection of the AABB, + // but we have to check if we intersect it on the vertical axis. + if ((enter_pos.y > chunk.max) && (exit_pos.y > chunk.max)) { + return false; + } + if ((enter_pos.y < chunk.min) && (exit_pos.y < chunk.min)) { return false; } - Vector3 local_begin = p_begin + local_origin; - Vector3 local_end = p_end + local_origin; + return p_params.heightmap->_intersect_grid_segment(_heightmap_cell_cull_segment, enter_pos, exit_pos, p_params.heightmap->width, p_params.heightmap->depth, p_params.heightmap->local_origin, p_params.result, p_params.normal); +} + +template <typename ProcessFunction> +bool HeightMapShape3DSW::_intersect_grid_segment(ProcessFunction &p_process, const Vector3 &p_begin, const Vector3 &p_end, int p_width, int p_depth, const Vector3 &offset, Vector3 &r_point, Vector3 &r_normal) const { + Vector3 delta = (p_end - p_begin); + real_t length = delta.length(); + + if (length < CMP_EPSILON) { + return false; + } + + Vector3 local_begin = p_begin + offset; FaceShape3DSW face; face.backface_collision = false; @@ -1723,136 +1772,181 @@ bool HeightMapShape3DSW::intersect_segment(const Vector3 &p_begin, const Vector3 _HeightmapSegmentCullParams params; params.from = p_begin; params.to = p_end; - params.dir = (p_end - p_begin).normalized(); + params.dir = delta / length; params.heightmap = this; params.face = &face; - // Quantize the ray begin/end. - int begin_x = floor(local_begin.x); - int begin_z = floor(local_begin.z); - int end_x = floor(local_end.x); - int end_z = floor(local_end.z); + _HeightmapGridCullState state; - if ((begin_x == end_x) && (begin_z == end_z)) { - // Simple case for rays that don't traverse the grid horizontally. - // Just perform a test on the given cell. - int x = CLAMP(begin_x, 0, width - 2); - int z = CLAMP(begin_z, 0, depth - 2); - if (_heightmap_cell_cull_segment(params, x, z)) { - r_point = params.result; - r_normal = params.normal; - return true; - } - } else { - // Perform grid query from projected ray. - Vector2 ray_dir_proj(local_end.x - local_begin.x, local_end.z - local_begin.z); - real_t ray_dist_proj = ray_dir_proj.length(); + // Perform grid query from projected ray. + Vector2 ray_dir_flat(delta.x, delta.z); + state.length = length; + state.length_flat = ray_dir_flat.length(); - if (ray_dist_proj < CMP_EPSILON) { - ray_dir_proj = Vector2(); - } else { - ray_dir_proj /= ray_dist_proj; - } + if (state.length_flat < CMP_EPSILON) { + ray_dir_flat = Vector2(); + } else { + ray_dir_flat /= state.length_flat; + } - const int x_step = (ray_dir_proj.x > CMP_EPSILON) ? 1 : ((ray_dir_proj.x < -CMP_EPSILON) ? -1 : 0); - const int z_step = (ray_dir_proj.y > CMP_EPSILON) ? 1 : ((ray_dir_proj.y < -CMP_EPSILON) ? -1 : 0); + const int x_step = (ray_dir_flat.x > CMP_EPSILON) ? 1 : ((ray_dir_flat.x < -CMP_EPSILON) ? -1 : 0); + const int z_step = (ray_dir_flat.y > CMP_EPSILON) ? 1 : ((ray_dir_flat.y < -CMP_EPSILON) ? -1 : 0); - const real_t infinite = 1e20; - const real_t delta_x = (x_step != 0) ? 1.f / Math::abs(ray_dir_proj.x) : infinite; - const real_t delta_z = (z_step != 0) ? 1.f / Math::abs(ray_dir_proj.y) : infinite; + const real_t infinite = 1e20; + const real_t delta_x = (x_step != 0) ? 1.f / Math::abs(ray_dir_flat.x) : infinite; + const real_t delta_z = (z_step != 0) ? 1.f / Math::abs(ray_dir_flat.y) : infinite; - real_t cross_x; // At which value of `param` we will cross a x-axis lane? - real_t cross_z; // At which value of `param` we will cross a z-axis lane? + real_t cross_x; // At which value of `param` we will cross a x-axis lane? + real_t cross_z; // At which value of `param` we will cross a z-axis lane? - // X initialization. - if (x_step != 0) { - if (x_step == 1) { - cross_x = (ceil(local_begin.x) - local_begin.x) * delta_x; - } else { - cross_x = (local_begin.x - floor(local_begin.x)) * delta_x; - } + // X initialization. + if (x_step != 0) { + if (x_step == 1) { + cross_x = (Math::ceil(local_begin.x) - local_begin.x) * delta_x; } else { - cross_x = infinite; // Will never cross on X. + cross_x = (local_begin.x - Math::floor(local_begin.x)) * delta_x; } + } else { + cross_x = infinite; // Will never cross on X. + } - // Z initialization. - if (z_step != 0) { - if (z_step == 1) { - cross_z = (ceil(local_begin.z) - local_begin.z) * delta_z; - } else { - cross_z = (local_begin.z - floor(local_begin.z)) * delta_z; - } + // Z initialization. + if (z_step != 0) { + if (z_step == 1) { + cross_z = (Math::ceil(local_begin.z) - local_begin.z) * delta_z; } else { - cross_z = infinite; // Will never cross on Z. + cross_z = (local_begin.z - Math::floor(local_begin.z)) * delta_z; } + } else { + cross_z = infinite; // Will never cross on Z. + } - int x = floor(local_begin.x); - int z = floor(local_begin.z); + int x = Math::floor(local_begin.x); + int z = Math::floor(local_begin.z); - // Workaround cases where the ray starts at an integer position. - if (Math::is_zero_approx(cross_x)) { - cross_x += delta_x; - // If going backwards, we should ignore the position we would get by the above flooring, - // because the ray is not heading in that direction. - if (x_step == -1) { - x -= 1; - } + // Workaround cases where the ray starts at an integer position. + if (Math::is_zero_approx(cross_x)) { + cross_x += delta_x; + // If going backwards, we should ignore the position we would get by the above flooring, + // because the ray is not heading in that direction. + if (x_step == -1) { + x -= 1; } + } - if (Math::is_zero_approx(cross_z)) { - cross_z += delta_z; - if (z_step == -1) { - z -= 1; - } + if (Math::is_zero_approx(cross_z)) { + cross_z += delta_z; + if (z_step == -1) { + z -= 1; } + } + + // Start inside the grid. + int x_start = MAX(MIN(x, p_width - 2), 0); + int z_start = MAX(MIN(z, p_depth - 2), 0); - // Start inside the grid. - int x_start = CLAMP(x, 0, width - 2); - int z_start = CLAMP(z, 0, depth - 2); + // Adjust initial cross values. + cross_x += delta_x * x_step * (x_start - x); + cross_z += delta_z * z_step * (z_start - z); - // Adjust initial cross values. - cross_x += delta_x * x_step * (x_start - x); - cross_z += delta_z * z_step * (z_start - z); + x = x_start; + z = z_start; - x = x_start; - z = z_start; + while (true) { + state.prev_dist = state.dist; + state.x = x; + state.z = z; - if (_heightmap_cell_cull_segment(params, x, z)) { + if (cross_x < cross_z) { + // X lane. + x += x_step; + // Assign before advancing the param, + // to be in sync with the initialization step. + state.dist = cross_x; + cross_x += delta_x; + } else { + // Z lane. + z += z_step; + state.dist = cross_z; + cross_z += delta_z; + } + + if (state.dist > state.length_flat) { + state.dist = state.length_flat; + if (p_process(params, state)) { + r_point = params.result; + r_normal = params.normal; + return true; + } + break; + } + + if (p_process(params, state)) { r_point = params.result; r_normal = params.normal; return true; } - real_t dist = 0.0; - while (true) { - if (cross_x < cross_z) { - // X lane. - x += x_step; - // Assign before advancing the param, - // to be in sync with the initialization step. - dist = cross_x; - cross_x += delta_x; - } else { - // Z lane. - z += z_step; - dist = cross_z; - cross_z += delta_z; - } + // Stop when outside the grid. + if ((x < 0) || (z < 0) || (x >= p_width - 1) || (z >= p_depth - 1)) { + break; + } + } - // Stop when outside the grid. - if ((x < 0) || (z < 0) || (x >= width - 1) || (z >= depth - 1)) { - break; - } + return false; +} - if (_heightmap_cell_cull_segment(params, x, z)) { - r_point = params.result; - r_normal = params.normal; - return true; - } +bool HeightMapShape3DSW::intersect_segment(const Vector3 &p_begin, const Vector3 &p_end, Vector3 &r_point, Vector3 &r_normal) const { + if (heights.is_empty()) { + return false; + } - if (dist > ray_dist_proj) { - break; - } + Vector3 local_begin = p_begin + local_origin; + Vector3 local_end = p_end + local_origin; + + // Quantize the ray begin/end. + int begin_x = Math::floor(local_begin.x); + int begin_z = Math::floor(local_begin.z); + int end_x = Math::floor(local_end.x); + int end_z = Math::floor(local_end.z); + + if ((begin_x == end_x) && (begin_z == end_z)) { + // Simple case for rays that don't traverse the grid horizontally. + // Just perform a test on the given cell. + FaceShape3DSW face; + face.backface_collision = false; + + _HeightmapSegmentCullParams params; + params.from = p_begin; + params.to = p_end; + params.dir = (p_end - p_begin).normalized(); + + params.heightmap = this; + params.face = &face; + + _HeightmapGridCullState state; + state.x = MAX(MIN(begin_x, width - 2), 0); + state.z = MAX(MIN(begin_z, depth - 2), 0); + if (_heightmap_cell_cull_segment(params, state)) { + r_point = params.result; + r_normal = params.normal; + return true; + } + } else if (bounds_grid.is_empty()) { + // Process all cells intersecting the flat projection of the ray. + return _intersect_grid_segment(_heightmap_cell_cull_segment, p_begin, p_end, width, depth, local_origin, r_point, r_normal); + } else { + Vector3 ray_diff = (p_end - p_begin); + real_t length_flat_sqr = ray_diff.x * ray_diff.x + ray_diff.z * ray_diff.z; + if (length_flat_sqr < BOUNDS_CHUNK_SIZE * BOUNDS_CHUNK_SIZE) { + // Don't use chunks, the ray is too short in the plane. + return _intersect_grid_segment(_heightmap_cell_cull_segment, p_begin, p_end, width, depth, local_origin, r_point, r_normal); + } else { + // The ray is long, run raycast on a higher-level grid. + Vector3 bounds_from = p_begin / BOUNDS_CHUNK_SIZE; + Vector3 bounds_to = p_end / BOUNDS_CHUNK_SIZE; + Vector3 bounds_offset = local_origin / BOUNDS_CHUNK_SIZE; + return _intersect_grid_segment(_heightmap_chunk_cull_segment, bounds_from, bounds_to, bounds_grid_width, bounds_grid_depth, bounds_offset, r_point, r_normal); } } @@ -1917,7 +2011,7 @@ void HeightMapShape3DSW::cull(const AABB &p_local_aabb, QueryCallback p_callback _get_point(x, z, face.vertex[0]); _get_point(x + 1, z, face.vertex[1]); _get_point(x, z + 1, face.vertex[2]); - face.normal = Plane(face.vertex[0], face.vertex[2], face.vertex[1]).normal; + face.normal = Plane(face.vertex[0], face.vertex[1], face.vertex[2]).normal; if (p_callback(p_userdata, &face)) { return; } @@ -1925,7 +2019,7 @@ void HeightMapShape3DSW::cull(const AABB &p_local_aabb, QueryCallback p_callback // Second triangle. face.vertex[0] = face.vertex[1]; _get_point(x + 1, z + 1, face.vertex[1]); - face.normal = Plane(face.vertex[0], face.vertex[2], face.vertex[1]).normal; + face.normal = Plane(face.vertex[0], face.vertex[1], face.vertex[2]).normal; if (p_callback(p_userdata, &face)) { return; } @@ -1943,6 +2037,75 @@ Vector3 HeightMapShape3DSW::get_moment_of_inertia(real_t p_mass) const { (p_mass / 3.0) * (extents.x * extents.x + extents.y * extents.y)); } +void HeightMapShape3DSW::_build_accelerator() { + bounds_grid.clear(); + + bounds_grid_width = width / BOUNDS_CHUNK_SIZE; + bounds_grid_depth = depth / BOUNDS_CHUNK_SIZE; + + if (width % BOUNDS_CHUNK_SIZE > 0) { + ++bounds_grid_width; // In case terrain size isn't dividable by chunk size. + } + + if (depth % BOUNDS_CHUNK_SIZE > 0) { + ++bounds_grid_depth; + } + + uint32_t bound_grid_size = (uint32_t)(bounds_grid_width * bounds_grid_depth); + + if (bound_grid_size < 2) { + // Grid is empty or just one chunk. + return; + } + + bounds_grid.resize(bound_grid_size); + + // Compute min and max height for all chunks. + for (int cz = 0; cz < bounds_grid_depth; ++cz) { + int z0 = cz * BOUNDS_CHUNK_SIZE; + + for (int cx = 0; cx < bounds_grid_width; ++cx) { + int x0 = cx * BOUNDS_CHUNK_SIZE; + + Range r; + + r.min = _get_height(x0, z0); + r.max = r.min; + + // Compute min and max height for this chunk. + // We have to include one extra cell to account for neighbors. + // Here is why: + // Say we have a flat terrain, and a plateau that fits a chunk perfectly. + // + // Left Right + // 0---0---0---1---1---1 + // | | | | | | + // 0---0---0---1---1---1 + // | | | | | | + // 0---0---0---1---1---1 + // x + // + // If the AABB for the Left chunk did not share vertices with the Right, + // then we would fail collision tests at x due to a gap. + // + int z_max = MIN(z0 + BOUNDS_CHUNK_SIZE + 1, depth); + int x_max = MIN(x0 + BOUNDS_CHUNK_SIZE + 1, width); + for (int z = z0; z < z_max; ++z) { + for (int x = x0; x < x_max; ++x) { + real_t height = _get_height(x, z); + if (height < r.min) { + r.min = height; + } else if (height > r.max) { + r.max = height; + } + } + } + + bounds_grid[cx + cz * bounds_grid_width] = r; + } + } +} + void HeightMapShape3DSW::_setup(const Vector<real_t> &p_heights, int p_width, int p_depth, real_t p_min_height, real_t p_max_height) { heights = p_heights; width = p_width; @@ -1959,6 +2122,8 @@ void HeightMapShape3DSW::_setup(const Vector<real_t> &p_heights, int p_width, in aabb.position -= local_origin; + _build_accelerator(); + configure(aabb); } @@ -2017,7 +2182,7 @@ void HeightMapShape3DSW::set_data(const Variant &p_data) { } else { int heights_size = heights.size(); for (int i = 0; i < heights_size; ++i) { - float h = heights[i]; + real_t h = heights[i]; if (h < min_height) { min_height = h; } else if (h > max_height) { diff --git a/servers/physics_3d/shape_3d_sw.h b/servers/physics_3d/shape_3d_sw.h index b05f65f268..73eddc469c 100644 --- a/servers/physics_3d/shape_3d_sw.h +++ b/servers/physics_3d/shape_3d_sw.h @@ -32,6 +32,7 @@ #define SHAPE_SW_H #include "core/math/geometry_3d.h" +#include "core/templates/local_vector.h" #include "servers/physics_server_3d.h" class Shape3DSW; @@ -385,6 +386,21 @@ struct HeightMapShape3DSW : public ConcaveShape3DSW { int depth = 0; Vector3 local_origin; + // Accelerator. + struct Range { + real_t min = 0.0; + real_t max = 0.0; + }; + LocalVector<Range> bounds_grid; + int bounds_grid_width = 0; + int bounds_grid_depth = 0; + + static const int BOUNDS_CHUNK_SIZE = 16; + + _FORCE_INLINE_ const Range &_get_bounds_chunk(int p_x, int p_z) const { + return bounds_grid[(p_z * bounds_grid_width) + p_x]; + } + _FORCE_INLINE_ real_t _get_height(int p_x, int p_z) const { return heights[(p_z * width) + p_x]; } @@ -397,6 +413,11 @@ struct HeightMapShape3DSW : public ConcaveShape3DSW { void _get_cell(const Vector3 &p_point, int &r_x, int &r_y, int &r_z) const; + void _build_accelerator(); + + template <typename ProcessFunction> + bool _intersect_grid_segment(ProcessFunction &p_process, const Vector3 &p_begin, const Vector3 &p_end, int p_width, int p_depth, const Vector3 &offset, Vector3 &r_point, Vector3 &r_normal) const; + void _setup(const Vector<real_t> &p_heights, int p_width, int p_depth, real_t p_min_height, real_t p_max_height); public: diff --git a/servers/physics_3d/space_3d_sw.cpp b/servers/physics_3d/space_3d_sw.cpp index 37dee436df..3b08184e00 100644 --- a/servers/physics_3d/space_3d_sw.cpp +++ b/servers/physics_3d/space_3d_sw.cpp @@ -977,12 +977,12 @@ void Space3DSW::body_remove_from_active_list(SelfList<Body3DSW> *p_body) { active_list.remove(p_body); } -void Space3DSW::body_add_to_inertia_update_list(SelfList<Body3DSW> *p_body) { - inertia_update_list.add(p_body); +void Space3DSW::body_add_to_mass_properties_update_list(SelfList<Body3DSW> *p_body) { + mass_properties_update_list.add(p_body); } -void Space3DSW::body_remove_from_inertia_update_list(SelfList<Body3DSW> *p_body) { - inertia_update_list.remove(p_body); +void Space3DSW::body_remove_from_mass_properties_update_list(SelfList<Body3DSW> *p_body) { + mass_properties_update_list.remove(p_body); } BroadPhase3DSW *Space3DSW::get_broadphase() { @@ -1059,9 +1059,9 @@ void Space3DSW::call_queries() { void Space3DSW::setup() { contact_debug_count = 0; - while (inertia_update_list.first()) { - inertia_update_list.first()->self()->update_inertias(); - inertia_update_list.remove(inertia_update_list.first()); + while (mass_properties_update_list.first()) { + mass_properties_update_list.first()->self()->update_mass_properties(); + mass_properties_update_list.remove(mass_properties_update_list.first()); } } diff --git a/servers/physics_3d/space_3d_sw.h b/servers/physics_3d/space_3d_sw.h index eff494a7bc..98c335cb80 100644 --- a/servers/physics_3d/space_3d_sw.h +++ b/servers/physics_3d/space_3d_sw.h @@ -48,12 +48,12 @@ class PhysicsDirectSpaceState3DSW : public PhysicsDirectSpaceState3D { public: Space3DSW *space; - virtual int intersect_point(const Vector3 &p_point, ShapeResult *r_results, int p_result_max, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = 0xFFFFFFFF, bool p_collide_with_bodies = true, bool p_collide_with_areas = false) override; - virtual bool intersect_ray(const Vector3 &p_from, const Vector3 &p_to, RayResult &r_result, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = 0xFFFFFFFF, bool p_collide_with_bodies = true, bool p_collide_with_areas = false, bool p_pick_ray = false) override; - virtual int intersect_shape(const RID &p_shape, const Transform3D &p_xform, real_t p_margin, ShapeResult *r_results, int p_result_max, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = 0xFFFFFFFF, bool p_collide_with_bodies = true, bool p_collide_with_areas = false) override; - virtual bool cast_motion(const RID &p_shape, const Transform3D &p_xform, const Vector3 &p_motion, real_t p_margin, real_t &p_closest_safe, real_t &p_closest_unsafe, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = 0xFFFFFFFF, bool p_collide_with_bodies = true, bool p_collide_with_areas = false, ShapeRestInfo *r_info = nullptr) override; - virtual bool collide_shape(RID p_shape, const Transform3D &p_shape_xform, real_t p_margin, Vector3 *r_results, int p_result_max, int &r_result_count, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = 0xFFFFFFFF, bool p_collide_with_bodies = true, bool p_collide_with_areas = false) override; - virtual bool rest_info(RID p_shape, const Transform3D &p_shape_xform, real_t p_margin, ShapeRestInfo *r_info, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = 0xFFFFFFFF, bool p_collide_with_bodies = true, bool p_collide_with_areas = false) override; + virtual int intersect_point(const Vector3 &p_point, ShapeResult *r_results, int p_result_max, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = UINT32_MAX, bool p_collide_with_bodies = true, bool p_collide_with_areas = false) override; + virtual bool intersect_ray(const Vector3 &p_from, const Vector3 &p_to, RayResult &r_result, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = UINT32_MAX, bool p_collide_with_bodies = true, bool p_collide_with_areas = false, bool p_pick_ray = false) override; + virtual int intersect_shape(const RID &p_shape, const Transform3D &p_xform, real_t p_margin, ShapeResult *r_results, int p_result_max, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = UINT32_MAX, bool p_collide_with_bodies = true, bool p_collide_with_areas = false) override; + virtual bool cast_motion(const RID &p_shape, const Transform3D &p_xform, const Vector3 &p_motion, real_t p_margin, real_t &p_closest_safe, real_t &p_closest_unsafe, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = UINT32_MAX, bool p_collide_with_bodies = true, bool p_collide_with_areas = false, ShapeRestInfo *r_info = nullptr) override; + virtual bool collide_shape(RID p_shape, const Transform3D &p_shape_xform, real_t p_margin, Vector3 *r_results, int p_result_max, int &r_result_count, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = UINT32_MAX, bool p_collide_with_bodies = true, bool p_collide_with_areas = false) override; + virtual bool rest_info(RID p_shape, const Transform3D &p_shape_xform, real_t p_margin, ShapeRestInfo *r_info, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = UINT32_MAX, bool p_collide_with_bodies = true, bool p_collide_with_areas = false) override; virtual Vector3 get_closest_point_to_object_volume(RID p_object, const Vector3 p_point) const override; PhysicsDirectSpaceState3DSW(); @@ -79,7 +79,7 @@ private: BroadPhase3DSW *broadphase; SelfList<Body3DSW>::List active_list; - SelfList<Body3DSW>::List inertia_update_list; + SelfList<Body3DSW>::List mass_properties_update_list; SelfList<Body3DSW>::List state_query_list; SelfList<Area3DSW>::List monitor_query_list; SelfList<Area3DSW>::List area_moved_list; @@ -137,8 +137,8 @@ public: const SelfList<Body3DSW>::List &get_active_body_list() const; void body_add_to_active_list(SelfList<Body3DSW> *p_body); void body_remove_from_active_list(SelfList<Body3DSW> *p_body); - void body_add_to_inertia_update_list(SelfList<Body3DSW> *p_body); - void body_remove_from_inertia_update_list(SelfList<Body3DSW> *p_body); + void body_add_to_mass_properties_update_list(SelfList<Body3DSW> *p_body); + void body_remove_from_mass_properties_update_list(SelfList<Body3DSW> *p_body); void body_add_to_state_query_list(SelfList<Body3DSW> *p_body); void body_remove_from_state_query_list(SelfList<Body3DSW> *p_body); diff --git a/servers/physics_server_2d.cpp b/servers/physics_server_2d.cpp index 4660b83f4d..2656ef1d6d 100644 --- a/servers/physics_server_2d.cpp +++ b/servers/physics_server_2d.cpp @@ -77,6 +77,7 @@ void PhysicsDirectBodyState2D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_total_linear_damp"), &PhysicsDirectBodyState2D::get_total_linear_damp); ClassDB::bind_method(D_METHOD("get_total_angular_damp"), &PhysicsDirectBodyState2D::get_total_angular_damp); + ClassDB::bind_method(D_METHOD("get_center_of_mass"), &PhysicsDirectBodyState2D::get_center_of_mass); ClassDB::bind_method(D_METHOD("get_inverse_mass"), &PhysicsDirectBodyState2D::get_inverse_mass); ClassDB::bind_method(D_METHOD("get_inverse_inertia"), &PhysicsDirectBodyState2D::get_inverse_inertia); @@ -123,6 +124,7 @@ void PhysicsDirectBodyState2D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "total_angular_damp"), "", "get_total_angular_damp"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "total_linear_damp"), "", "get_total_linear_damp"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "total_gravity"), "", "get_total_gravity"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "center_of_mass"), "", "get_center_of_mass"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "angular_velocity"), "set_angular_velocity", "get_angular_velocity"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "linear_velocity"), "set_linear_velocity", "get_linear_velocity"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "sleeping"), "set_sleep_state", "is_sleeping"); @@ -257,13 +259,6 @@ void PhysicsShapeQueryParameters2D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "collide_with_areas"), "set_collide_with_areas", "is_collide_with_areas_enabled"); } -PhysicsShapeQueryParameters2D::PhysicsShapeQueryParameters2D() { - margin = 0; - collision_mask = 0x7FFFFFFF; - collide_with_bodies = true; - collide_with_areas = false; -} - Dictionary PhysicsDirectSpaceState2D::_intersect_ray(const Vector2 &p_from, const Vector2 &p_to, const Vector<RID> &p_exclude, uint32_t p_layers, bool p_collide_with_bodies, bool p_collide_with_areas) { RayResult inters; Set<RID> exclude; @@ -411,9 +406,9 @@ PhysicsDirectSpaceState2D::PhysicsDirectSpaceState2D() { } void PhysicsDirectSpaceState2D::_bind_methods() { - ClassDB::bind_method(D_METHOD("intersect_point", "point", "max_results", "exclude", "collision_mask", "collide_with_bodies", "collide_with_areas"), &PhysicsDirectSpaceState2D::_intersect_point, DEFVAL(32), DEFVAL(Array()), DEFVAL(0x7FFFFFFF), DEFVAL(true), DEFVAL(false)); - ClassDB::bind_method(D_METHOD("intersect_point_on_canvas", "point", "canvas_instance_id", "max_results", "exclude", "collision_mask", "collide_with_bodies", "collide_with_areas"), &PhysicsDirectSpaceState2D::_intersect_point_on_canvas, DEFVAL(32), DEFVAL(Array()), DEFVAL(0x7FFFFFFF), DEFVAL(true), DEFVAL(false)); - ClassDB::bind_method(D_METHOD("intersect_ray", "from", "to", "exclude", "collision_mask", "collide_with_bodies", "collide_with_areas"), &PhysicsDirectSpaceState2D::_intersect_ray, DEFVAL(Array()), DEFVAL(0x7FFFFFFF), DEFVAL(true), DEFVAL(false)); + ClassDB::bind_method(D_METHOD("intersect_point", "point", "max_results", "exclude", "collision_mask", "collide_with_bodies", "collide_with_areas"), &PhysicsDirectSpaceState2D::_intersect_point, DEFVAL(32), DEFVAL(Array()), DEFVAL(UINT32_MAX), DEFVAL(true), DEFVAL(false)); + ClassDB::bind_method(D_METHOD("intersect_point_on_canvas", "point", "canvas_instance_id", "max_results", "exclude", "collision_mask", "collide_with_bodies", "collide_with_areas"), &PhysicsDirectSpaceState2D::_intersect_point_on_canvas, DEFVAL(32), DEFVAL(Array()), DEFVAL(UINT32_MAX), DEFVAL(true), DEFVAL(false)); + ClassDB::bind_method(D_METHOD("intersect_ray", "from", "to", "exclude", "collision_mask", "collide_with_bodies", "collide_with_areas"), &PhysicsDirectSpaceState2D::_intersect_ray, DEFVAL(Array()), DEFVAL(UINT32_MAX), DEFVAL(true), DEFVAL(false)); ClassDB::bind_method(D_METHOD("intersect_shape", "shape", "max_results"), &PhysicsDirectSpaceState2D::_intersect_shape, DEFVAL(32)); ClassDB::bind_method(D_METHOD("cast_motion", "shape"), &PhysicsDirectSpaceState2D::_cast_motion); ClassDB::bind_method(D_METHOD("collide_shape", "shape", "max_results"), &PhysicsDirectSpaceState2D::_collide_shape, DEFVAL(32)); @@ -614,6 +609,8 @@ void PhysicsServer2D::_bind_methods() { ClassDB::bind_method(D_METHOD("body_set_param", "body", "param", "value"), &PhysicsServer2D::body_set_param); ClassDB::bind_method(D_METHOD("body_get_param", "body", "param"), &PhysicsServer2D::body_get_param); + ClassDB::bind_method(D_METHOD("body_reset_mass_properties", "body"), &PhysicsServer2D::body_reset_mass_properties); + ClassDB::bind_method(D_METHOD("body_set_state", "body", "state", "value"), &PhysicsServer2D::body_set_state); ClassDB::bind_method(D_METHOD("body_get_state", "body", "state"), &PhysicsServer2D::body_get_state); @@ -709,6 +706,7 @@ void PhysicsServer2D::_bind_methods() { BIND_ENUM_CONSTANT(BODY_PARAM_FRICTION); BIND_ENUM_CONSTANT(BODY_PARAM_MASS); BIND_ENUM_CONSTANT(BODY_PARAM_INERTIA); + BIND_ENUM_CONSTANT(BODY_PARAM_CENTER_OF_MASS); BIND_ENUM_CONSTANT(BODY_PARAM_GRAVITY_SCALE); BIND_ENUM_CONSTANT(BODY_PARAM_LINEAR_DAMP); BIND_ENUM_CONSTANT(BODY_PARAM_ANGULAR_DAMP); diff --git a/servers/physics_server_2d.h b/servers/physics_server_2d.h index 021d7be7c0..1145bb8b91 100644 --- a/servers/physics_server_2d.h +++ b/servers/physics_server_2d.h @@ -48,6 +48,7 @@ public: virtual real_t get_total_linear_damp() const = 0; // get density of this body space/area virtual real_t get_total_angular_damp() const = 0; // get density of this body space/area + virtual Vector2 get_center_of_mass() const = 0; virtual real_t get_inverse_mass() const = 0; // get the mass virtual real_t get_inverse_inertia() const = 0; // get density of this body space @@ -103,12 +104,12 @@ class PhysicsShapeQueryParameters2D : public RefCounted { RID shape; Transform2D transform; Vector2 motion; - real_t margin; + real_t margin = 0.0; Set<RID> exclude; - uint32_t collision_mask; + uint32_t collision_mask = UINT32_MAX; - bool collide_with_bodies; - bool collide_with_areas; + bool collide_with_bodies = true; + bool collide_with_areas = false; protected: static void _bind_methods(); @@ -139,8 +140,6 @@ public: void set_exclude(const Vector<RID> &p_exclude); Vector<RID> get_exclude() const; - - PhysicsShapeQueryParameters2D(); }; class PhysicsDirectSpaceState2D : public Object { @@ -169,7 +168,7 @@ public: Variant metadata; }; - virtual bool intersect_ray(const Vector2 &p_from, const Vector2 &p_to, RayResult &r_result, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_layer = 0xFFFFFFFF, bool p_collide_with_bodies = true, bool p_collide_with_areas = false) = 0; + virtual bool intersect_ray(const Vector2 &p_from, const Vector2 &p_to, RayResult &r_result, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_layer = UINT32_MAX, bool p_collide_with_bodies = true, bool p_collide_with_areas = false) = 0; struct ShapeResult { RID rid; @@ -179,14 +178,14 @@ public: Variant metadata; }; - virtual int intersect_point(const Vector2 &p_point, ShapeResult *r_results, int p_result_max, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_layer = 0xFFFFFFFF, bool p_collide_with_bodies = true, bool p_collide_with_areas = false, bool p_pick_point = false) = 0; - virtual int intersect_point_on_canvas(const Vector2 &p_point, ObjectID p_canvas_instance_id, ShapeResult *r_results, int p_result_max, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_layer = 0xFFFFFFFF, bool p_collide_with_bodies = true, bool p_collide_with_areas = false, bool p_pick_point = false) = 0; + virtual int intersect_point(const Vector2 &p_point, ShapeResult *r_results, int p_result_max, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_layer = UINT32_MAX, bool p_collide_with_bodies = true, bool p_collide_with_areas = false, bool p_pick_point = false) = 0; + virtual int intersect_point_on_canvas(const Vector2 &p_point, ObjectID p_canvas_instance_id, ShapeResult *r_results, int p_result_max, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_layer = UINT32_MAX, bool p_collide_with_bodies = true, bool p_collide_with_areas = false, bool p_pick_point = false) = 0; - virtual int intersect_shape(const RID &p_shape, const Transform2D &p_xform, const Vector2 &p_motion, real_t p_margin, ShapeResult *r_results, int p_result_max, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_layer = 0xFFFFFFFF, bool p_collide_with_bodies = true, bool p_collide_with_areas = false) = 0; + virtual int intersect_shape(const RID &p_shape, const Transform2D &p_xform, const Vector2 &p_motion, real_t p_margin, ShapeResult *r_results, int p_result_max, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_layer = UINT32_MAX, bool p_collide_with_bodies = true, bool p_collide_with_areas = false) = 0; - virtual bool cast_motion(const RID &p_shape, const Transform2D &p_xform, const Vector2 &p_motion, real_t p_margin, real_t &p_closest_safe, real_t &p_closest_unsafe, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_layer = 0xFFFFFFFF, bool p_collide_with_bodies = true, bool p_collide_with_areas = false) = 0; + virtual bool cast_motion(const RID &p_shape, const Transform2D &p_xform, const Vector2 &p_motion, real_t p_margin, real_t &p_closest_safe, real_t &p_closest_unsafe, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_layer = UINT32_MAX, bool p_collide_with_bodies = true, bool p_collide_with_areas = false) = 0; - virtual bool collide_shape(RID p_shape, const Transform2D &p_shape_xform, const Vector2 &p_motion, real_t p_margin, Vector2 *r_results, int p_result_max, int &r_result_count, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_layer = 0xFFFFFFFF, bool p_collide_with_bodies = true, bool p_collide_with_areas = false) = 0; + virtual bool collide_shape(RID p_shape, const Transform2D &p_shape_xform, const Vector2 &p_motion, real_t p_margin, Vector2 *r_results, int p_result_max, int &r_result_count, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_layer = UINT32_MAX, bool p_collide_with_bodies = true, bool p_collide_with_areas = false) = 0; struct ShapeRestInfo { Vector2 point; @@ -198,7 +197,7 @@ public: Variant metadata; }; - virtual bool rest_info(RID p_shape, const Transform2D &p_shape_xform, const Vector2 &p_motion, real_t p_margin, ShapeRestInfo *r_info, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_layer = 0xFFFFFFFF, bool p_collide_with_bodies = true, bool p_collide_with_areas = false) = 0; + virtual bool rest_info(RID p_shape, const Transform2D &p_shape_xform, const Vector2 &p_motion, real_t p_margin, ShapeRestInfo *r_info, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_layer = UINT32_MAX, bool p_collide_with_bodies = true, bool p_collide_with_areas = false) = 0; PhysicsDirectSpaceState2D(); }; @@ -404,15 +403,18 @@ public: BODY_PARAM_BOUNCE, BODY_PARAM_FRICTION, BODY_PARAM_MASS, ///< unused for static, always infinite - BODY_PARAM_INERTIA, // read-only: computed from mass & shapes + BODY_PARAM_INERTIA, + BODY_PARAM_CENTER_OF_MASS, BODY_PARAM_GRAVITY_SCALE, BODY_PARAM_LINEAR_DAMP, BODY_PARAM_ANGULAR_DAMP, BODY_PARAM_MAX, }; - virtual void body_set_param(RID p_body, BodyParameter p_param, real_t p_value) = 0; - virtual real_t body_get_param(RID p_body, BodyParameter p_param) const = 0; + virtual void body_set_param(RID p_body, BodyParameter p_param, const Variant &p_value) = 0; + virtual Variant body_get_param(RID p_body, BodyParameter p_param) const = 0; + + virtual void body_reset_mass_properties(RID p_body) = 0; //state enum BodyState { diff --git a/servers/physics_server_3d.cpp b/servers/physics_server_3d.cpp index 6cde4977f4..0c487b83ea 100644 --- a/servers/physics_server_3d.cpp +++ b/servers/physics_server_3d.cpp @@ -249,13 +249,6 @@ void PhysicsShapeQueryParameters3D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "collide_with_areas"), "set_collide_with_areas", "is_collide_with_areas_enabled"); } -PhysicsShapeQueryParameters3D::PhysicsShapeQueryParameters3D() { - margin = 0; - collision_mask = 0x7FFFFFFF; - collide_with_bodies = true; - collide_with_areas = false; -} - ///////////////////////////////////// Dictionary PhysicsDirectSpaceState3D::_intersect_ray(const Vector3 &p_from, const Vector3 &p_to, const Vector<RID> &p_exclude, uint32_t p_collision_mask, bool p_collide_with_bodies, bool p_collide_with_areas) { @@ -360,7 +353,7 @@ PhysicsDirectSpaceState3D::PhysicsDirectSpaceState3D() { } void PhysicsDirectSpaceState3D::_bind_methods() { - ClassDB::bind_method(D_METHOD("intersect_ray", "from", "to", "exclude", "collision_mask", "collide_with_bodies", "collide_with_areas"), &PhysicsDirectSpaceState3D::_intersect_ray, DEFVAL(Array()), DEFVAL(0x7FFFFFFF), DEFVAL(true), DEFVAL(false)); + ClassDB::bind_method(D_METHOD("intersect_ray", "from", "to", "exclude", "collision_mask", "collide_with_bodies", "collide_with_areas"), &PhysicsDirectSpaceState3D::_intersect_ray, DEFVAL(Array()), DEFVAL(UINT32_MAX), DEFVAL(true), DEFVAL(false)); ClassDB::bind_method(D_METHOD("intersect_shape", "shape", "max_results"), &PhysicsDirectSpaceState3D::_intersect_shape, DEFVAL(32)); ClassDB::bind_method(D_METHOD("cast_motion", "shape", "motion"), &PhysicsDirectSpaceState3D::_cast_motion); ClassDB::bind_method(D_METHOD("collide_shape", "shape", "max_results"), &PhysicsDirectSpaceState3D::_collide_shape, DEFVAL(32)); @@ -584,6 +577,8 @@ void PhysicsServer3D::_bind_methods() { ClassDB::bind_method(D_METHOD("body_set_param", "body", "param", "value"), &PhysicsServer3D::body_set_param); ClassDB::bind_method(D_METHOD("body_get_param", "body", "param"), &PhysicsServer3D::body_get_param); + ClassDB::bind_method(D_METHOD("body_reset_mass_properties", "body"), &PhysicsServer3D::body_reset_mass_properties); + ClassDB::bind_method(D_METHOD("body_set_state", "body", "state", "value"), &PhysicsServer3D::body_set_state); ClassDB::bind_method(D_METHOD("body_get_state", "body", "state"), &PhysicsServer3D::body_get_state); @@ -789,6 +784,8 @@ void PhysicsServer3D::_bind_methods() { BIND_ENUM_CONSTANT(BODY_PARAM_BOUNCE); BIND_ENUM_CONSTANT(BODY_PARAM_FRICTION); BIND_ENUM_CONSTANT(BODY_PARAM_MASS); + BIND_ENUM_CONSTANT(BODY_PARAM_INERTIA); + BIND_ENUM_CONSTANT(BODY_PARAM_CENTER_OF_MASS); BIND_ENUM_CONSTANT(BODY_PARAM_GRAVITY_SCALE); BIND_ENUM_CONSTANT(BODY_PARAM_LINEAR_DAMP); BIND_ENUM_CONSTANT(BODY_PARAM_ANGULAR_DAMP); diff --git a/servers/physics_server_3d.h b/servers/physics_server_3d.h index a365802220..5677604682 100644 --- a/servers/physics_server_3d.h +++ b/servers/physics_server_3d.h @@ -103,12 +103,12 @@ class PhysicsShapeQueryParameters3D : public RefCounted { RES shape_ref; RID shape; Transform3D transform; - real_t margin; + real_t margin = 0.0; Set<RID> exclude; - uint32_t collision_mask; + uint32_t collision_mask = UINT32_MAX; - bool collide_with_bodies; - bool collide_with_areas; + bool collide_with_bodies = true; + bool collide_with_areas = false; protected: static void _bind_methods(); @@ -136,15 +136,13 @@ public: void set_collide_with_areas(bool p_enable); bool is_collide_with_areas_enabled() const; - - PhysicsShapeQueryParameters3D(); }; class PhysicsDirectSpaceState3D : public Object { GDCLASS(PhysicsDirectSpaceState3D, Object); private: - Dictionary _intersect_ray(const Vector3 &p_from, const Vector3 &p_to, const Vector<RID> &p_exclude = Vector<RID>(), uint32_t p_collision_mask = 0, bool p_collide_with_bodies = true, bool p_collide_with_areas = false); + Dictionary _intersect_ray(const Vector3 &p_from, const Vector3 &p_to, const Vector<RID> &p_exclude = Vector<RID>(), uint32_t p_collision_mask = UINT32_MAX, bool p_collide_with_bodies = true, bool p_collide_with_areas = false); Array _intersect_shape(const Ref<PhysicsShapeQueryParameters3D> &p_shape_query, int p_max_results = 32); Array _cast_motion(const Ref<PhysicsShapeQueryParameters3D> &p_shape_query, const Vector3 &p_motion); Array _collide_shape(const Ref<PhysicsShapeQueryParameters3D> &p_shape_query, int p_max_results = 32); @@ -161,7 +159,7 @@ public: int shape = 0; }; - virtual int intersect_point(const Vector3 &p_point, ShapeResult *r_results, int p_result_max, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = 0xFFFFFFFF, bool p_collide_with_bodies = true, bool p_collide_with_areas = false) = 0; + virtual int intersect_point(const Vector3 &p_point, ShapeResult *r_results, int p_result_max, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = UINT32_MAX, bool p_collide_with_bodies = true, bool p_collide_with_areas = false) = 0; struct RayResult { Vector3 position; @@ -172,9 +170,9 @@ public: int shape = 0; }; - virtual bool intersect_ray(const Vector3 &p_from, const Vector3 &p_to, RayResult &r_result, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = 0xFFFFFFFF, bool p_collide_with_bodies = true, bool p_collide_with_areas = false, bool p_pick_ray = false) = 0; + virtual bool intersect_ray(const Vector3 &p_from, const Vector3 &p_to, RayResult &r_result, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = UINT32_MAX, bool p_collide_with_bodies = true, bool p_collide_with_areas = false, bool p_pick_ray = false) = 0; - virtual int intersect_shape(const RID &p_shape, const Transform3D &p_xform, real_t p_margin, ShapeResult *r_results, int p_result_max, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = 0xFFFFFFFF, bool p_collide_with_bodies = true, bool p_collide_with_areas = false) = 0; + virtual int intersect_shape(const RID &p_shape, const Transform3D &p_xform, real_t p_margin, ShapeResult *r_results, int p_result_max, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = UINT32_MAX, bool p_collide_with_bodies = true, bool p_collide_with_areas = false) = 0; struct ShapeRestInfo { Vector3 point; @@ -185,11 +183,11 @@ public: Vector3 linear_velocity; //velocity at contact point }; - virtual bool cast_motion(const RID &p_shape, const Transform3D &p_xform, const Vector3 &p_motion, real_t p_margin, real_t &p_closest_safe, real_t &p_closest_unsafe, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = 0xFFFFFFFF, bool p_collide_with_bodies = true, bool p_collide_with_areas = false, ShapeRestInfo *r_info = nullptr) = 0; + virtual bool cast_motion(const RID &p_shape, const Transform3D &p_xform, const Vector3 &p_motion, real_t p_margin, real_t &p_closest_safe, real_t &p_closest_unsafe, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = UINT32_MAX, bool p_collide_with_bodies = true, bool p_collide_with_areas = false, ShapeRestInfo *r_info = nullptr) = 0; - virtual bool collide_shape(RID p_shape, const Transform3D &p_shape_xform, real_t p_margin, Vector3 *r_results, int p_result_max, int &r_result_count, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = 0xFFFFFFFF, bool p_collide_with_bodies = true, bool p_collide_with_areas = false) = 0; + virtual bool collide_shape(RID p_shape, const Transform3D &p_shape_xform, real_t p_margin, Vector3 *r_results, int p_result_max, int &r_result_count, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = UINT32_MAX, bool p_collide_with_bodies = true, bool p_collide_with_areas = false) = 0; - virtual bool rest_info(RID p_shape, const Transform3D &p_shape_xform, real_t p_margin, ShapeRestInfo *r_info, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = 0xFFFFFFFF, bool p_collide_with_bodies = true, bool p_collide_with_areas = false) = 0; + virtual bool rest_info(RID p_shape, const Transform3D &p_shape_xform, real_t p_margin, ShapeRestInfo *r_info, const Set<RID> &p_exclude = Set<RID>(), uint32_t p_collision_mask = UINT32_MAX, bool p_collide_with_bodies = true, bool p_collide_with_areas = false) = 0; virtual Vector3 get_closest_point_to_object_volume(RID p_object, const Vector3 p_point) const = 0; @@ -407,14 +405,18 @@ public: BODY_PARAM_BOUNCE, BODY_PARAM_FRICTION, BODY_PARAM_MASS, ///< unused for static, always infinite + BODY_PARAM_INERTIA, + BODY_PARAM_CENTER_OF_MASS, BODY_PARAM_GRAVITY_SCALE, BODY_PARAM_LINEAR_DAMP, BODY_PARAM_ANGULAR_DAMP, BODY_PARAM_MAX, }; - virtual void body_set_param(RID p_body, BodyParameter p_param, real_t p_value) = 0; - virtual real_t body_get_param(RID p_body, BodyParameter p_param) const = 0; + virtual void body_set_param(RID p_body, BodyParameter p_param, const Variant &p_value) = 0; + virtual Variant body_get_param(RID p_body, BodyParameter p_param) const = 0; + + virtual void body_reset_mass_properties(RID p_body) = 0; //state enum BodyState { diff --git a/servers/rendering/rasterizer_dummy.h b/servers/rendering/rasterizer_dummy.h index f58d124140..35bb7722e7 100644 --- a/servers/rendering/rasterizer_dummy.h +++ b/servers/rendering/rasterizer_dummy.h @@ -197,7 +197,7 @@ public: TypedArray<Image> bake_render_uv2(RID p_base, const Vector<RID> &p_material_overrides, const Size2i &p_image_size) override { return TypedArray<Image>(); } - bool free(RID p_rid) override { return true; } + bool free(RID p_rid) override { return false; } void update() override {} void sdfgi_set_debug_probe_select(const Vector3 &p_position, const Vector3 &p_dir) override {} @@ -664,8 +664,9 @@ public: DummyTexture *texture = texture_owner.getornull(p_rid); texture_owner.free(p_rid); memdelete(texture); + return true; } - return true; + return false; } virtual void update_memory_info() override {} diff --git a/servers/rendering/renderer_compositor.h b/servers/rendering/renderer_compositor.h index 6206849b66..1971c3e781 100644 --- a/servers/rendering/renderer_compositor.h +++ b/servers/rendering/renderer_compositor.h @@ -41,7 +41,8 @@ class RendererSceneRender; struct BlitToScreen { RID render_target; - Rect2i rect; + Rect2 src_rect = Rect2(0.0, 0.0, 1.0, 1.0); + Rect2i dst_rect; struct { bool use_layer = false; diff --git a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp index 1b730567d9..9201f917db 100644 --- a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp +++ b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp @@ -223,7 +223,6 @@ void RenderForwardClustered::RenderBufferDataForwardClustered::configure(RID p_c RD::TEXTURE_SAMPLES_2, RD::TEXTURE_SAMPLES_4, RD::TEXTURE_SAMPLES_8, - RD::TEXTURE_SAMPLES_16 }; texture_samples = ts[p_msaa]; @@ -1163,7 +1162,7 @@ void RenderForwardClustered::_render_scene(RenderDataRD *p_render_data, const Co render_buffer = (RenderBufferDataForwardClustered *)render_buffers_get_data(p_render_data->render_buffers); } RendererSceneEnvironmentRD *env = get_environment(p_render_data->environment); - static const int texture_multisamples[RS::VIEWPORT_MSAA_MAX] = { 1, 2, 4, 8, 16 }; + static const int texture_multisamples[RS::VIEWPORT_MSAA_MAX] = { 1, 2, 4, 8 }; //first of all, make a new render pass //fill up ubo diff --git a/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.cpp b/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.cpp index a24860996c..d0f02b44cb 100644 --- a/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.cpp +++ b/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.cpp @@ -601,10 +601,10 @@ void SceneShaderForwardClustered::init(RendererStorageRD *p_storage, const Strin actions.usage_defines["UV2"] = "#define UV2_USED\n"; actions.usage_defines["BONE_INDICES"] = "#define BONES_USED\n"; actions.usage_defines["BONE_WEIGHTS"] = "#define WEIGHTS_USED\n"; - actions.usage_defines["CUSTOM0"] = "#define CUSTOM0\n"; - actions.usage_defines["CUSTOM1"] = "#define CUSTOM1\n"; - actions.usage_defines["CUSTOM2"] = "#define CUSTOM2\n"; - actions.usage_defines["CUSTOM3"] = "#define CUSTOM3\n"; + actions.usage_defines["CUSTOM0"] = "#define CUSTOM0_USED\n"; + actions.usage_defines["CUSTOM1"] = "#define CUSTOM1_USED\n"; + actions.usage_defines["CUSTOM2"] = "#define CUSTOM2_USED\n"; + actions.usage_defines["CUSTOM3"] = "#define CUSTOM3_USED\n"; actions.usage_defines["NORMAL_MAP"] = "#define NORMAL_MAP_USED\n"; actions.usage_defines["NORMAL_MAP_DEPTH"] = "@NORMAL_MAP"; actions.usage_defines["COLOR"] = "#define COLOR_USED\n"; diff --git a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp index 276a44bc27..a5cc2db48f 100644 --- a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp +++ b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp @@ -159,7 +159,6 @@ void RenderForwardMobile::RenderBufferDataForwardMobile::configure(RID p_color_b RD::TEXTURE_SAMPLES_2, RD::TEXTURE_SAMPLES_4, RD::TEXTURE_SAMPLES_8, - RD::TEXTURE_SAMPLES_16 }; texture_samples = ts[p_msaa]; diff --git a/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.cpp b/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.cpp index 14b3b6d9aa..cd314d8c56 100644 --- a/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.cpp +++ b/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.cpp @@ -593,10 +593,10 @@ void SceneShaderForwardMobile::init(RendererStorageRD *p_storage, const String p actions.usage_defines["UV2"] = "#define UV2_USED\n"; actions.usage_defines["BONE_INDICES"] = "#define BONES_USED\n"; actions.usage_defines["BONE_WEIGHTS"] = "#define WEIGHTS_USED\n"; - actions.usage_defines["CUSTOM0"] = "#define CUSTOM0\n"; - actions.usage_defines["CUSTOM1"] = "#define CUSTOM1\n"; - actions.usage_defines["CUSTOM2"] = "#define CUSTOM2\n"; - actions.usage_defines["CUSTOM3"] = "#define CUSTOM3\n"; + actions.usage_defines["CUSTOM0"] = "#define CUSTOM0_USED\n"; + actions.usage_defines["CUSTOM1"] = "#define CUSTOM1_USED\n"; + actions.usage_defines["CUSTOM2"] = "#define CUSTOM2_USED\n"; + actions.usage_defines["CUSTOM3"] = "#define CUSTOM3_USED\n"; actions.usage_defines["NORMAL_MAP"] = "#define NORMAL_MAP_USED\n"; actions.usage_defines["NORMAL_MAP_DEPTH"] = "@NORMAL_MAP"; actions.usage_defines["COLOR"] = "#define COLOR_USED\n"; diff --git a/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp b/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp index 647c348d9f..3c66fadbe9 100644 --- a/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp +++ b/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp @@ -1086,7 +1086,7 @@ void RendererCanvasRenderRD::_render_items(RID p_to_render_target, int p_item_co } } - RID material = ci->material; + RID material = ci->material_owner == nullptr ? ci->material : ci->material_owner->material; if (material.is_null() && ci->canvas_group != nullptr) { material = default_canvas_group_material; @@ -1354,8 +1354,10 @@ void RendererCanvasRenderRD::canvas_render_items(RID p_to_render_target, Item *p } } - if (ci->material.is_valid()) { - MaterialData *md = (MaterialData *)storage->material_get_data(ci->material, RendererStorageRD::SHADER_TYPE_2D); + RID material = ci->material_owner == nullptr ? ci->material : ci->material_owner->material; + + if (material.is_valid()) { + MaterialData *md = (MaterialData *)storage->material_get_data(material, RendererStorageRD::SHADER_TYPE_2D); if (md && md->shader_data->valid) { if (md->shader_data->uses_screen_texture && canvas_group_owner == nullptr) { if (!material_screen_texture_found) { @@ -1375,7 +1377,7 @@ void RendererCanvasRenderRD::canvas_render_items(RID p_to_render_target, Item *p if (!RD::get_singleton()->uniform_set_is_valid(md->uniform_set)) { // uniform set may be gone because a dependency was erased. In this case, it will happen // if a texture is deleted, so just re-create it. - storage->material_force_update_textures(ci->material, RendererStorageRD::SHADER_TYPE_2D); + storage->material_force_update_textures(material, RendererStorageRD::SHADER_TYPE_2D); } } } diff --git a/servers/rendering/renderer_rd/renderer_compositor_rd.cpp b/servers/rendering/renderer_rd/renderer_compositor_rd.cpp index 62e9386f95..c53c202bab 100644 --- a/servers/rendering/renderer_rd/renderer_compositor_rd.cpp +++ b/servers/rendering/renderer_rd/renderer_compositor_rd.cpp @@ -46,6 +46,8 @@ void RendererCompositorRD::blit_render_targets_to_screen(DisplayServer::WindowID RID rd_texture = storage->texture_get_rd_texture(texture); ERR_CONTINUE(rd_texture.is_null()); + // TODO if keep_3d_linear was set when rendering to this render target we need to add a linear->sRGB conversion in. + if (!render_target_descriptors.has(rd_texture) || !RD::get_singleton()->uniform_set_is_valid(render_target_descriptors[rd_texture])) { Vector<RD::Uniform> uniforms; RD::Uniform u; @@ -65,10 +67,14 @@ void RendererCompositorRD::blit_render_targets_to_screen(DisplayServer::WindowID RD::get_singleton()->draw_list_bind_index_array(draw_list, blit.array); RD::get_singleton()->draw_list_bind_uniform_set(draw_list, render_target_descriptors[rd_texture], 0); - blit.push_constant.rect[0] = p_render_targets[i].rect.position.x / screen_size.width; - blit.push_constant.rect[1] = p_render_targets[i].rect.position.y / screen_size.height; - blit.push_constant.rect[2] = p_render_targets[i].rect.size.width / screen_size.width; - blit.push_constant.rect[3] = p_render_targets[i].rect.size.height / screen_size.height; + blit.push_constant.src_rect[0] = p_render_targets[i].src_rect.position.x; + blit.push_constant.src_rect[1] = p_render_targets[i].src_rect.position.y; + blit.push_constant.src_rect[2] = p_render_targets[i].src_rect.size.width; + blit.push_constant.src_rect[3] = p_render_targets[i].src_rect.size.height; + blit.push_constant.dst_rect[0] = p_render_targets[i].dst_rect.position.x / screen_size.width; + blit.push_constant.dst_rect[1] = p_render_targets[i].dst_rect.position.y / screen_size.height; + blit.push_constant.dst_rect[2] = p_render_targets[i].dst_rect.size.width / screen_size.width; + blit.push_constant.dst_rect[3] = p_render_targets[i].dst_rect.size.height / screen_size.height; blit.push_constant.layer = p_render_targets[i].multi_view.layer; blit.push_constant.eye_center[0] = p_render_targets[i].lens_distortion.eye_center.x; blit.push_constant.eye_center[1] = p_render_targets[i].lens_distortion.eye_center.y; @@ -203,10 +209,14 @@ void RendererCompositorRD::set_boot_image(const Ref<Image> &p_image, const Color RD::get_singleton()->draw_list_bind_index_array(draw_list, blit.array); RD::get_singleton()->draw_list_bind_uniform_set(draw_list, uset, 0); - blit.push_constant.rect[0] = screenrect.position.x; - blit.push_constant.rect[1] = screenrect.position.y; - blit.push_constant.rect[2] = screenrect.size.width; - blit.push_constant.rect[3] = screenrect.size.height; + blit.push_constant.src_rect[0] = 0.0; + blit.push_constant.src_rect[1] = 0.0; + blit.push_constant.src_rect[2] = 1.0; + blit.push_constant.src_rect[3] = 1.0; + blit.push_constant.dst_rect[0] = screenrect.position.x; + blit.push_constant.dst_rect[1] = screenrect.position.y; + blit.push_constant.dst_rect[2] = screenrect.size.width; + blit.push_constant.dst_rect[3] = screenrect.size.height; blit.push_constant.layer = 0; blit.push_constant.eye_center[0] = 0; blit.push_constant.eye_center[1] = 0; diff --git a/servers/rendering/renderer_rd/renderer_compositor_rd.h b/servers/rendering/renderer_rd/renderer_compositor_rd.h index 8639362da9..0230c46800 100644 --- a/servers/rendering/renderer_rd/renderer_compositor_rd.h +++ b/servers/rendering/renderer_rd/renderer_compositor_rd.h @@ -55,7 +55,8 @@ protected: }; struct BlitPushConstant { - float rect[4]; + float src_rect[4]; + float dst_rect[4]; float eye_center[2]; float k1; diff --git a/servers/rendering/renderer_rd/renderer_scene_gi_rd.cpp b/servers/rendering/renderer_rd/renderer_scene_gi_rd.cpp index 098e2a5c87..36943c5e5c 100644 --- a/servers/rendering/renderer_rd/renderer_scene_gi_rd.cpp +++ b/servers/rendering/renderer_rd/renderer_scene_gi_rd.cpp @@ -1469,7 +1469,7 @@ void RendererSceneGIRD::SDFGI::pre_process_gi(const Transform3D &p_transform, Re lights[idx].color[1] = color.g; lights[idx].color[2] = color.b; lights[idx].type = RS::LIGHT_DIRECTIONAL; - lights[idx].energy = storage->light_get_param(li->light, RS::LIGHT_PARAM_ENERGY); + lights[idx].energy = storage->light_get_param(li->light, RS::LIGHT_PARAM_ENERGY) * storage->light_get_param(li->light, RS::LIGHT_PARAM_INDIRECT_ENERGY); lights[idx].has_shadow = storage->light_has_shadow(li->light); idx++; @@ -1514,7 +1514,7 @@ void RendererSceneGIRD::SDFGI::pre_process_gi(const Transform3D &p_transform, Re lights[idx].color[1] = color.g; lights[idx].color[2] = color.b; lights[idx].type = storage->light_get_type(li->light); - lights[idx].energy = storage->light_get_param(li->light, RS::LIGHT_PARAM_ENERGY); + lights[idx].energy = storage->light_get_param(li->light, RS::LIGHT_PARAM_ENERGY) * storage->light_get_param(li->light, RS::LIGHT_PARAM_INDIRECT_ENERGY); lights[idx].has_shadow = storage->light_has_shadow(li->light); lights[idx].attenuation = storage->light_get_param(li->light, RS::LIGHT_PARAM_ATTENUATION); lights[idx].radius = storage->light_get_param(li->light, RS::LIGHT_PARAM_RANGE); @@ -1953,7 +1953,7 @@ void RendererSceneGIRD::SDFGI::render_static_lights(RID p_render_buffers, uint32 lights[idx].color[0] = color.r; lights[idx].color[1] = color.g; lights[idx].color[2] = color.b; - lights[idx].energy = storage->light_get_param(li->light, RS::LIGHT_PARAM_ENERGY); + lights[idx].energy = storage->light_get_param(li->light, RS::LIGHT_PARAM_ENERGY) * storage->light_get_param(li->light, RS::LIGHT_PARAM_INDIRECT_ENERGY); lights[idx].has_shadow = storage->light_has_shadow(li->light); lights[idx].attenuation = storage->light_get_param(li->light, RS::LIGHT_PARAM_ATTENUATION); lights[idx].radius = storage->light_get_param(li->light, RS::LIGHT_PARAM_RANGE); diff --git a/servers/rendering/renderer_rd/shaders/blit.glsl b/servers/rendering/renderer_rd/shaders/blit.glsl index 967da1e6e4..8051f96738 100644 --- a/servers/rendering/renderer_rd/shaders/blit.glsl +++ b/servers/rendering/renderer_rd/shaders/blit.glsl @@ -5,6 +5,7 @@ #VERSION_DEFINES layout(push_constant, binding = 0, std140) uniform Pos { + vec4 src_rect; vec4 dst_rect; vec2 eye_center; @@ -22,8 +23,8 @@ layout(location = 0) out vec2 uv; void main() { vec2 base_arr[4] = vec2[](vec2(0.0, 0.0), vec2(0.0, 1.0), vec2(1.0, 1.0), vec2(1.0, 0.0)); - uv = base_arr[gl_VertexIndex]; - vec2 vtx = data.dst_rect.xy + uv * data.dst_rect.zw; + uv = data.src_rect.xy + base_arr[gl_VertexIndex] * data.src_rect.zw; + vec2 vtx = data.dst_rect.xy + base_arr[gl_VertexIndex] * data.dst_rect.zw; gl_Position = vec4(vtx * 2.0 - 1.0, 0.0, 1.0); } @@ -34,6 +35,7 @@ void main() { #VERSION_DEFINES layout(push_constant, binding = 0, std140) uniform Pos { + vec4 src_rect; vec4 dst_rect; vec2 eye_center; diff --git a/servers/rendering/renderer_rd/shaders/scene_forward_clustered.glsl b/servers/rendering/renderer_rd/shaders/scene_forward_clustered.glsl index edbe1031b7..8cb56fbc83 100644 --- a/servers/rendering/renderer_rd/shaders/scene_forward_clustered.glsl +++ b/servers/rendering/renderer_rd/shaders/scene_forward_clustered.glsl @@ -903,6 +903,7 @@ void main() { if (scene_data.use_reflection_cubemap) { vec3 ref_vec = reflect(-view, normal); + float horizon = min(1.0 + dot(ref_vec, normal), 1.0); ref_vec = scene_data.radiance_inverse_xform * ref_vec; #ifdef USE_RADIANCE_CUBEMAP_ARRAY @@ -915,7 +916,6 @@ void main() { specular_light = textureLod(samplerCube(radiance_cubemap, material_samplers[SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP]), ref_vec, roughness * MAX_ROUGHNESS_LOD).rgb; #endif //USE_RADIANCE_CUBEMAP_ARRAY - float horizon = min(1.0 + dot(ref_vec, normal), 1.0); specular_light *= horizon * horizon; specular_light *= scene_data.ambient_light_color_energy.a; } diff --git a/servers/rendering/renderer_rd/shaders/scene_forward_mobile.glsl b/servers/rendering/renderer_rd/shaders/scene_forward_mobile.glsl index 518b0a6c7f..c3c4139450 100644 --- a/servers/rendering/renderer_rd/shaders/scene_forward_mobile.glsl +++ b/servers/rendering/renderer_rd/shaders/scene_forward_mobile.glsl @@ -868,6 +868,7 @@ void main() { if (scene_data.use_reflection_cubemap) { vec3 ref_vec = reflect(-view, normal); + float horizon = min(1.0 + dot(ref_vec, normal), 1.0); ref_vec = scene_data.radiance_inverse_xform * ref_vec; #ifdef USE_RADIANCE_CUBEMAP_ARRAY @@ -880,7 +881,6 @@ void main() { specular_light = textureLod(samplerCube(radiance_cubemap, material_samplers[SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP]), ref_vec, roughness * MAX_ROUGHNESS_LOD).rgb; #endif //USE_RADIANCE_CUBEMAP_ARRAY - float horizon = min(1.0 + dot(ref_vec, normal), 1.0); specular_light *= horizon * horizon; specular_light *= scene_data.ambient_light_color_energy.a; } diff --git a/servers/rendering/renderer_rd/shaders/tonemap.glsl b/servers/rendering/renderer_rd/shaders/tonemap.glsl index 4411587116..1ce3e04421 100644 --- a/servers/rendering/renderer_rd/shaders/tonemap.glsl +++ b/servers/rendering/renderer_rd/shaders/tonemap.glsl @@ -169,16 +169,33 @@ vec3 tonemap_filmic(vec3 color, float white) { return color_tonemapped / white_tonemapped; } +// Adapted from https://github.com/TheRealMJP/BakingLab/blob/master/BakingLab/ACES.hlsl +// (MIT License). vec3 tonemap_aces(vec3 color, float white) { - const float exposure_bias = 0.85f; - const float A = 2.51f * exposure_bias * exposure_bias; - const float B = 0.03f * exposure_bias; - const float C = 2.43f * exposure_bias * exposure_bias; - const float D = 0.59f * exposure_bias; - const float E = 0.14f; - - vec3 color_tonemapped = (color * (A * color + B)) / (color * (C * color + D) + E); - float white_tonemapped = (white * (A * white + B)) / (white * (C * white + D) + E); + const float exposure_bias = 1.8f; + const float A = 0.0245786f; + const float B = 0.000090537f; + const float C = 0.983729f; + const float D = 0.432951f; + const float E = 0.238081f; + + // Exposure bias baked into transform to save shader instructions. Equivalent to `color *= exposure_bias` + const mat3 rgb_to_rrt = mat3( + vec3(0.59719f * exposure_bias, 0.35458f * exposure_bias, 0.04823f * exposure_bias), + vec3(0.07600f * exposure_bias, 0.90834f * exposure_bias, 0.01566f * exposure_bias), + vec3(0.02840f * exposure_bias, 0.13383f * exposure_bias, 0.83777f * exposure_bias)); + + const mat3 odt_to_rgb = mat3( + vec3(1.60475f, -0.53108f, -0.07367f), + vec3(-0.10208f, 1.10813f, -0.00605f), + vec3(-0.00327f, -0.07276f, 1.07602f)); + + color *= rgb_to_rrt; + vec3 color_tonemapped = (color * (color + A) - B) / (color * (C * color + D) + E); + color_tonemapped *= odt_to_rgb; + + white *= exposure_bias; + float white_tonemapped = (white * (white + A) - B) / (white * (C * white + D) + E); return color_tonemapped / white_tonemapped; } @@ -200,15 +217,16 @@ vec3 linear_to_srgb(vec3 color) { #define TONEMAPPER_ACES 3 vec3 apply_tonemapping(vec3 color, float white) { // inputs are LINEAR, always outputs clamped [0;1] color - + // Ensure color values passed to tonemappers are positive. + // They can be negative in the case of negative lights, which leads to undesired behavior. if (params.tonemapper == TONEMAPPER_LINEAR) { return color; } else if (params.tonemapper == TONEMAPPER_REINHARD) { - return tonemap_reinhard(color, white); + return tonemap_reinhard(max(vec3(0.0f), color), white); } else if (params.tonemapper == TONEMAPPER_FILMIC) { - return tonemap_filmic(color, white); + return tonemap_filmic(max(vec3(0.0f), color), white); } else { // TONEMAPPER_ACES - return tonemap_aces(color, white); + return tonemap_aces(max(vec3(0.0f), color), white); } } @@ -401,9 +419,7 @@ void main() { color += screen_space_dither(gl_FragCoord.xy); } - // Ensure color values passed to tonemappers are positive. - // They can be negative in the case of negative lights, which leads to undesired behavior. - color = apply_tonemapping(max(vec3(0.0), color), params.white); + color = apply_tonemapping(color, params.white); color = linear_to_srgb(color); // regular linear -> SRGB conversion diff --git a/servers/rendering/renderer_viewport.cpp b/servers/rendering/renderer_viewport.cpp index 3ede9fed2d..8af2049ab3 100644 --- a/servers/rendering/renderer_viewport.cpp +++ b/servers/rendering/renderer_viewport.cpp @@ -626,10 +626,10 @@ void RendererViewport::draw_viewports() { BlitToScreen blit; blit.render_target = vp->render_target; if (vp->viewport_to_screen_rect != Rect2()) { - blit.rect = vp->viewport_to_screen_rect; + blit.dst_rect = vp->viewport_to_screen_rect; } else { - blit.rect.position = Vector2(); - blit.rect.size = vp->size; + blit.dst_rect.position = Vector2(); + blit.dst_rect.size = vp->size; } if (!blit_to_screen_list.has(vp->viewport_to_screen)) { diff --git a/servers/rendering/rendering_device.cpp b/servers/rendering/rendering_device.cpp index b302c6b793..70f676e5ac 100644 --- a/servers/rendering/rendering_device.cpp +++ b/servers/rendering/rendering_device.cpp @@ -478,12 +478,28 @@ void RenderingDevice::_bind_methods() { ClassDB::bind_method(D_METHOD("get_memory_usage"), &RenderingDevice::get_memory_usage); + ClassDB::bind_method(D_METHOD("get_driver_resource", "resource", "rid", "index"), &RenderingDevice::get_driver_resource); + BIND_CONSTANT(BARRIER_MASK_RASTER); BIND_CONSTANT(BARRIER_MASK_COMPUTE); BIND_CONSTANT(BARRIER_MASK_TRANSFER); BIND_CONSTANT(BARRIER_MASK_ALL); BIND_CONSTANT(BARRIER_MASK_NO_BARRIER); + BIND_ENUM_CONSTANT(DRIVER_RESOURCE_VULKAN_DEVICE); + BIND_ENUM_CONSTANT(DRIVER_RESOURCE_VULKAN_PHYSICAL_DEVICE); + BIND_ENUM_CONSTANT(DRIVER_RESOURCE_VULKAN_INSTANCE); + BIND_ENUM_CONSTANT(DRIVER_RESOURCE_VULKAN_QUEUE); + BIND_ENUM_CONSTANT(DRIVER_RESOURCE_VULKAN_QUEUE_FAMILY_INDEX); + BIND_ENUM_CONSTANT(DRIVER_RESOURCE_VULKAN_IMAGE); + BIND_ENUM_CONSTANT(DRIVER_RESOURCE_VULKAN_IMAGE_VIEW); + BIND_ENUM_CONSTANT(DRIVER_RESOURCE_VULKAN_IMAGE_NATIVE_TEXTURE_FORMAT); + BIND_ENUM_CONSTANT(DRIVER_RESOURCE_VULKAN_SAMPLER); + BIND_ENUM_CONSTANT(DRIVER_RESOURCE_VULKAN_DESCRIPTOR_SET); + BIND_ENUM_CONSTANT(DRIVER_RESOURCE_VULKAN_BUFFER); + BIND_ENUM_CONSTANT(DRIVER_RESOURCE_VULKAN_COMPUTE_PIPELINE); + BIND_ENUM_CONSTANT(DRIVER_RESOURCE_VULKAN_RENDER_PIPELINE); + BIND_ENUM_CONSTANT(DATA_FORMAT_R4G4_UNORM_PACK8); BIND_ENUM_CONSTANT(DATA_FORMAT_R4G4B4A4_UNORM_PACK16); BIND_ENUM_CONSTANT(DATA_FORMAT_B4G4R4A4_UNORM_PACK16); diff --git a/servers/rendering/rendering_device.h b/servers/rendering/rendering_device.h index 2cf1f165dd..5eb8f1cead 100644 --- a/servers/rendering/rendering_device.h +++ b/servers/rendering/rendering_device.h @@ -60,6 +60,23 @@ public: DEVICE_DIRECTX }; + enum DriverResource { + DRIVER_RESOURCE_VULKAN_DEVICE = 0, + DRIVER_RESOURCE_VULKAN_PHYSICAL_DEVICE, + DRIVER_RESOURCE_VULKAN_INSTANCE, + DRIVER_RESOURCE_VULKAN_QUEUE, + DRIVER_RESOURCE_VULKAN_QUEUE_FAMILY_INDEX, + DRIVER_RESOURCE_VULKAN_IMAGE, + DRIVER_RESOURCE_VULKAN_IMAGE_VIEW, + DRIVER_RESOURCE_VULKAN_IMAGE_NATIVE_TEXTURE_FORMAT, + DRIVER_RESOURCE_VULKAN_SAMPLER, + DRIVER_RESOURCE_VULKAN_DESCRIPTOR_SET, + DRIVER_RESOURCE_VULKAN_BUFFER, + DRIVER_RESOURCE_VULKAN_COMPUTE_PIPELINE, + DRIVER_RESOURCE_VULKAN_RENDER_PIPELINE, + //next driver continue enum from 1000 to keep order + }; + enum ShaderStage { SHADER_STAGE_VERTEX, SHADER_STAGE_FRAGMENT, @@ -1183,6 +1200,8 @@ public: virtual String get_device_name() const = 0; virtual String get_device_pipeline_cache_uuid() const = 0; + virtual uint64_t get_driver_resource(DriverResource p_resource, RID p_rid = RID(), uint64_t p_index = 0) = 0; + static RenderingDevice *get_singleton(); RenderingDevice(); @@ -1217,6 +1236,7 @@ protected: Vector<int64_t> _draw_list_switch_to_next_pass_split(uint32_t p_splits); }; +VARIANT_ENUM_CAST(RenderingDevice::DriverResource) VARIANT_ENUM_CAST(RenderingDevice::ShaderStage) VARIANT_ENUM_CAST(RenderingDevice::ShaderLanguage) VARIANT_ENUM_CAST(RenderingDevice::CompareOperator) diff --git a/servers/rendering_server.cpp b/servers/rendering_server.cpp index 78604dfe8c..1b10e4dcbe 100644 --- a/servers/rendering_server.cpp +++ b/servers/rendering_server.cpp @@ -500,7 +500,7 @@ Error RenderingServer::_surface_set_data(Array p_arrays, uint32_t p_format, uint case RS::ARRAY_CUSTOM1: case RS::ARRAY_CUSTOM2: case RS::ARRAY_CUSTOM3: { - uint32_t type = (p_format >> (ARRAY_FORMAT_CUSTOM_BASE + ARRAY_FORMAT_CUSTOM_BITS * (RS::ARRAY_CUSTOM0 - ai))) & ARRAY_FORMAT_CUSTOM_MASK; + uint32_t type = (p_format >> (ARRAY_FORMAT_CUSTOM_BASE + ARRAY_FORMAT_CUSTOM_BITS * (ai - RS::ARRAY_CUSTOM0))) & ARRAY_FORMAT_CUSTOM_MASK; switch (type) { case ARRAY_CUSTOM_RGBA8_UNORM: case ARRAY_CUSTOM_RGBA8_SNORM: @@ -541,14 +541,14 @@ Error RenderingServer::_surface_set_data(Array p_arrays, uint32_t p_format, uint ERR_FAIL_COND_V(p_arrays[ai].get_type() != Variant::PACKED_FLOAT32_ARRAY, ERR_INVALID_PARAMETER); Vector<float> array = p_arrays[ai]; - int32_t s = ARRAY_CUSTOM_R_FLOAT - ai + 1; + int32_t s = type - ARRAY_CUSTOM_R_FLOAT + 1; ERR_FAIL_COND_V(array.size() != p_vertex_array_len * s, ERR_INVALID_PARAMETER); const float *src = array.ptr(); for (int i = 0; i < p_vertex_array_len; i++) { - memcpy(&aw[p_offsets[ai] + i * p_attrib_stride], &src[i * s], 4 * s); + memcpy(&aw[p_offsets[ai] + i * p_attrib_stride], &src[i * s], sizeof(float) * s); } } break; default: { @@ -938,6 +938,13 @@ Error RenderingServer::mesh_create_surface_data_from_arrays(SurfaceData *r_surfa } } + for (uint32_t i = 0; i < RS::ARRAY_CUSTOM_COUNT; ++i) { + // include custom array format type. + if (format & (1 << (ARRAY_CUSTOM0 + i))) { + format |= (RS::ARRAY_FORMAT_CUSTOM_MASK << (RS::ARRAY_FORMAT_CUSTOM_BASE + i * RS::ARRAY_FORMAT_CUSTOM_BITS)) & p_compress_format; + } + } + uint32_t offsets[RS::ARRAY_MAX]; uint32_t vertex_element_size; @@ -1191,7 +1198,7 @@ Array RenderingServer::_get_array_from_surface(uint32_t p_format, Vector<uint8_t case RS::ARRAY_CUSTOM1: case RS::ARRAY_CUSTOM2: case RS::ARRAY_CUSTOM3: { - uint32_t type = (p_format >> (ARRAY_FORMAT_CUSTOM_BASE + ARRAY_FORMAT_CUSTOM_BITS * (RS::ARRAY_CUSTOM0 - i))) & ARRAY_FORMAT_CUSTOM_MASK; + uint32_t type = (p_format >> (ARRAY_FORMAT_CUSTOM_BASE + ARRAY_FORMAT_CUSTOM_BITS * (i - RS::ARRAY_CUSTOM0))) & ARRAY_FORMAT_CUSTOM_MASK; switch (type) { case ARRAY_CUSTOM_RGBA8_UNORM: case ARRAY_CUSTOM_RGBA8_SNORM: @@ -1219,6 +1226,8 @@ Array RenderingServer::_get_array_from_surface(uint32_t p_format, Vector<uint8_t uint32_t s = type - ARRAY_CUSTOM_R_FLOAT + 1; Vector<float> arr; + arr.resize(s * p_vertex_len); + float *w = arr.ptrw(); for (int j = 0; j < p_vertex_len; j++) { @@ -1467,6 +1476,11 @@ ShaderLanguage::DataType RenderingServer::global_variable_type_get_shader_dataty } } +RenderingDevice *RenderingServer::get_rendering_device() const { + // return the rendering device we're using globally + return RenderingDevice::get_singleton(); +} + RenderingDevice *RenderingServer::create_local_rendering_device() const { return RenderingDevice::get_singleton()->create_local_device(); } @@ -2211,7 +2225,6 @@ void RenderingServer::_bind_methods() { BIND_ENUM_CONSTANT(VIEWPORT_MSAA_2X); BIND_ENUM_CONSTANT(VIEWPORT_MSAA_4X); BIND_ENUM_CONSTANT(VIEWPORT_MSAA_8X); - BIND_ENUM_CONSTANT(VIEWPORT_MSAA_16X); BIND_ENUM_CONSTANT(VIEWPORT_MSAA_MAX); BIND_ENUM_CONSTANT(VIEWPORT_SCREEN_SPACE_AA_DISABLED); @@ -2714,6 +2727,7 @@ void RenderingServer::_bind_methods() { ClassDB::bind_method(D_METHOD("force_sync"), &RenderingServer::sync); ClassDB::bind_method(D_METHOD("force_draw", "swap_buffers", "frame_step"), &RenderingServer::draw, DEFVAL(true), DEFVAL(0.0)); + ClassDB::bind_method(D_METHOD("get_rendering_device"), &RenderingServer::get_rendering_device); ClassDB::bind_method(D_METHOD("create_local_rendering_device"), &RenderingServer::create_local_rendering_device); } diff --git a/servers/rendering_server.h b/servers/rendering_server.h index 8fbf231d9b..1b04a6e5e2 100644 --- a/servers/rendering_server.h +++ b/servers/rendering_server.h @@ -836,7 +836,6 @@ public: VIEWPORT_MSAA_2X, VIEWPORT_MSAA_4X, VIEWPORT_MSAA_8X, - VIEWPORT_MSAA_16X, VIEWPORT_MSAA_MAX, }; @@ -1493,6 +1492,7 @@ public: virtual void set_print_gpu_profile(bool p_enable) = 0; + RenderingDevice *get_rendering_device() const; RenderingDevice *create_local_rendering_device() const; bool is_render_loop_enabled() const; diff --git a/servers/xr/xr_interface.cpp b/servers/xr/xr_interface.cpp index fc1d82a964..bf54158905 100644 --- a/servers/xr/xr_interface.cpp +++ b/servers/xr/xr_interface.cpp @@ -29,7 +29,7 @@ /*************************************************************************/ #include "xr_interface.h" -#include "servers/rendering/renderer_compositor.h" +// #include "servers/rendering/renderer_compositor.h" void XRInterface::_bind_methods() { ClassDB::bind_method(D_METHOD("get_name"), &XRInterface::get_name); diff --git a/servers/xr/xr_interface_extension.cpp b/servers/xr/xr_interface_extension.cpp index e1519d1463..6340485bde 100644 --- a/servers/xr/xr_interface_extension.cpp +++ b/servers/xr/xr_interface_extension.cpp @@ -29,7 +29,8 @@ /*************************************************************************/ #include "xr_interface_extension.h" -#include "servers/rendering/renderer_compositor.h" +#include "servers/rendering/renderer_storage.h" +#include "servers/rendering/rendering_server_globals.h" void XRInterfaceExtension::_bind_methods() { GDVIRTUAL_BIND(_get_name); @@ -41,15 +42,13 @@ void XRInterfaceExtension::_bind_methods() { GDVIRTUAL_BIND(_get_tracking_status); - ClassDB::bind_method(D_METHOD("add_blit", "render_target", "rect", "use_layer", "layer", "apply_lens_distortion", "eye_center", "k1", "k2", "upscale", "aspect_ratio"), &XRInterfaceExtension::add_blit); - GDVIRTUAL_BIND(_get_render_target_size); GDVIRTUAL_BIND(_get_view_count); GDVIRTUAL_BIND(_get_camera_transform); GDVIRTUAL_BIND(_get_transform_for_view, "view", "cam_transform"); GDVIRTUAL_BIND(_get_projection_for_view, "view", "aspect", "z_near", "z_far"); - GDVIRTUAL_BIND(_commit_views); + GDVIRTUAL_BIND(_commit_views, "render_target", "screen_rect"); GDVIRTUAL_BIND(_process); GDVIRTUAL_BIND(_notification, "what"); @@ -60,6 +59,11 @@ void XRInterfaceExtension::_bind_methods() { GDVIRTUAL_BIND(_get_anchor_detection_is_enabled); GDVIRTUAL_BIND(_set_anchor_detection_is_enabled, "enabled"); GDVIRTUAL_BIND(_get_camera_feed_id); + + // helper methods + ClassDB::bind_method(D_METHOD("add_blit", "render_target", "src_rect", "dst_rect", "use_layer", "layer", "apply_lens_distortion", "eye_center", "k1", "k2", "upscale", "aspect_ratio"), &XRInterfaceExtension::add_blit); + ClassDB::bind_method(D_METHOD("get_render_target_texture", "render_target"), &XRInterfaceExtension::get_render_target_texture); + // ClassDB::bind_method(D_METHOD("get_render_target_depth", "render_target"), &XRInterfaceExtension::get_render_target_depth); } StringName XRInterfaceExtension::get_name() const { @@ -198,13 +202,14 @@ CameraMatrix XRInterfaceExtension::get_projection_for_view(uint32_t p_view, real return CameraMatrix(); } -void XRInterfaceExtension::add_blit(RID p_render_target, Rect2i p_rect, bool p_use_layer, uint32_t p_layer, bool p_apply_lens_distortion, Vector2 p_eye_center, float p_k1, float p_k2, float p_upscale, float p_aspect_ratio) { +void XRInterfaceExtension::add_blit(RID p_render_target, Rect2 p_src_rect, Rect2i p_dst_rect, bool p_use_layer, uint32_t p_layer, bool p_apply_lens_distortion, Vector2 p_eye_center, float p_k1, float p_k2, float p_upscale, float p_aspect_ratio) { BlitToScreen blit; ERR_FAIL_COND_MSG(!can_add_blits, "add_blit can only be called from an XR plugin from within _commit_views!"); blit.render_target = p_render_target; - blit.rect = p_rect; + blit.src_rect = p_src_rect; + blit.dst_rect = p_dst_rect; blit.multi_view.use_layer = p_use_layer; blit.multi_view.layer = p_layer; @@ -239,3 +244,19 @@ void XRInterfaceExtension::process() { void XRInterfaceExtension::notification(int p_what) { GDVIRTUAL_CALL(_notification, p_what); } + +RID XRInterfaceExtension::get_render_target_texture(RID p_render_target) { + RendererStorage *storage = RSG::storage; + ERR_FAIL_NULL_V_MSG(storage, RID(), "Renderer storage not setup"); + + return storage->render_target_get_texture(p_render_target); +} + +/* +RID XRInterfaceExtension::get_render_target_depth(RID p_render_target) { + RendererStorage *storage = RSG::storage; + ERR_FAIL_NULL_V_MSG(storage, RID(), "Renderer storage not setup"); + + return storage->render_target_get_depth(p_render_target); +} +*/ diff --git a/servers/xr/xr_interface_extension.h b/servers/xr/xr_interface_extension.h index ab4d90bfe6..94914a7b3f 100644 --- a/servers/xr/xr_interface_extension.h +++ b/servers/xr/xr_interface_extension.h @@ -91,7 +91,7 @@ public: GDVIRTUAL2R(Transform3D, _get_transform_for_view, uint32_t, const Transform3D &); GDVIRTUAL4R(PackedFloat64Array, _get_projection_for_view, uint32_t, real_t, real_t, real_t); - void add_blit(RID p_render_target, Rect2i p_rect, bool p_use_layer = false, uint32_t p_layer = 0, bool p_apply_lens_distortion = false, Vector2 p_eye_center = Vector2(), float p_k1 = 0.0, float p_k2 = 0.0, float p_upscale = 1.0, float p_aspect_ratio = 1.0); + void add_blit(RID p_render_target, Rect2 p_src_rect, Rect2i p_dst_rect, bool p_use_layer = false, uint32_t p_layer = 0, bool p_apply_lens_distortion = false, Vector2 p_eye_center = Vector2(), float p_k1 = 0.0, float p_k2 = 0.0, float p_upscale = 1.0, float p_aspect_ratio = 1.0); virtual Vector<BlitToScreen> commit_views(RID p_render_target, const Rect2 &p_screen_rect) override; GDVIRTUAL2(_commit_views, RID, const Rect2 &); @@ -100,6 +100,10 @@ public: GDVIRTUAL0(_process); GDVIRTUAL1(_notification, int); + + /* access to some internals we need */ + RID get_render_target_texture(RID p_render_target); + // RID get_render_target_depth(RID p_render_target); }; #endif // !XR_INTERFACE_EXTENSION_H diff --git a/tests/test_class_db.h b/tests/test_class_db.h index ea680da5d6..20397bb144 100644 --- a/tests/test_class_db.h +++ b/tests/test_class_db.h @@ -527,7 +527,7 @@ void add_exposed_classes(Context &r_context) { Map<StringName, StringName> accessor_methods; for (const PropertyInfo &property : property_list) { - if (property.usage & PROPERTY_USAGE_GROUP || property.usage & PROPERTY_USAGE_SUBGROUP || property.usage & PROPERTY_USAGE_CATEGORY) { + if (property.usage & PROPERTY_USAGE_GROUP || property.usage & PROPERTY_USAGE_SUBGROUP || property.usage & PROPERTY_USAGE_CATEGORY || (property.type == Variant::NIL && property.usage & PROPERTY_USAGE_ARRAY)) { continue; } diff --git a/tests/test_code_edit.h b/tests/test_code_edit.h new file mode 100644 index 0000000000..9579d8ebef --- /dev/null +++ b/tests/test_code_edit.h @@ -0,0 +1,813 @@ +/*************************************************************************/ +/* test_code_edit.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef TEST_CODE_EDIT_H +#define TEST_CODE_EDIT_H + +#include "core/input/input_map.h" +#include "core/object/message_queue.h" +#include "core/os/keyboard.h" +#include "core/string/string_builder.h" +#include "scene/gui/code_edit.h" +#include "scene/resources/default_theme/default_theme.h" + +#include "tests/test_macros.h" + +namespace TestCodeEdit { + +TEST_CASE("[SceneTree][CodeEdit] line gutters") { + CodeEdit *code_edit = memnew(CodeEdit); + SceneTree::get_singleton()->get_root()->add_child(code_edit); + + SUBCASE("[CodeEdit] breakpoints") { + SIGNAL_WATCH(code_edit, "breakpoint_toggled"); + + SUBCASE("[CodeEdit] draw breakpoints gutter") { + code_edit->set_draw_breakpoints_gutter(false); + CHECK_FALSE(code_edit->is_drawing_breakpoints_gutter()); + + code_edit->set_draw_breakpoints_gutter(true); + CHECK(code_edit->is_drawing_breakpoints_gutter()); + } + + SUBCASE("[CodeEdit] set line as breakpoint") { + /* Out of bounds. */ + ERR_PRINT_OFF; + + code_edit->set_line_as_breakpoint(-1, true); + CHECK_FALSE(code_edit->is_line_breakpointed(-1)); + SIGNAL_CHECK_FALSE("breakpoint_toggled"); + + code_edit->set_line_as_breakpoint(1, true); + CHECK_FALSE(code_edit->is_line_breakpointed(1)); + SIGNAL_CHECK_FALSE("breakpoint_toggled"); + + ERR_PRINT_ON; + + Array arg1; + arg1.push_back(0); + Array args; + args.push_back(arg1); + + code_edit->set_line_as_breakpoint(0, true); + CHECK(code_edit->is_line_breakpointed(0)); + CHECK(code_edit->get_breakpointed_lines()[0] == Variant(0)); + SIGNAL_CHECK("breakpoint_toggled", args); + + code_edit->set_line_as_breakpoint(0, false); + CHECK_FALSE(code_edit->is_line_breakpointed(0)); + SIGNAL_CHECK("breakpoint_toggled", args); + } + + SUBCASE("[CodeEdit] clear breakpointed lines") { + code_edit->clear_breakpointed_lines(); + SIGNAL_CHECK_FALSE("breakpoint_toggled"); + + Array arg1; + arg1.push_back(0); + Array args; + args.push_back(arg1); + + code_edit->set_line_as_breakpoint(0, true); + CHECK(code_edit->is_line_breakpointed(0)); + SIGNAL_CHECK("breakpoint_toggled", args); + + code_edit->clear_breakpointed_lines(); + CHECK_FALSE(code_edit->is_line_breakpointed(0)); + SIGNAL_CHECK("breakpoint_toggled", args); + } + + SUBCASE("[CodeEdit] breakpoints and set text") { + Array arg1; + arg1.push_back(0); + Array args; + args.push_back(arg1); + + code_edit->set_text("test\nline"); + code_edit->set_line_as_breakpoint(0, true); + CHECK(code_edit->is_line_breakpointed(0)); + SIGNAL_CHECK("breakpoint_toggled", args); + + /* breakpoint on lines that still exist are kept. */ + code_edit->set_text(""); + MessageQueue::get_singleton()->flush(); + CHECK(code_edit->is_line_breakpointed(0)); + SIGNAL_CHECK_FALSE("breakpoint_toggled"); + + /* breakpoint on lines that are removed should also be removed. */ + code_edit->clear_breakpointed_lines(); + SIGNAL_DISCARD("breakpoint_toggled") + + ((Array)args[0])[0] = 1; + code_edit->set_text("test\nline"); + code_edit->set_line_as_breakpoint(1, true); + CHECK(code_edit->is_line_breakpointed(1)); + SIGNAL_CHECK("breakpoint_toggled", args); + + code_edit->set_text(""); + MessageQueue::get_singleton()->flush(); + CHECK_FALSE(code_edit->is_line_breakpointed(0)); + ERR_PRINT_OFF; + CHECK_FALSE(code_edit->is_line_breakpointed(1)); + ERR_PRINT_ON; + SIGNAL_CHECK("breakpoint_toggled", args); + } + + SUBCASE("[CodeEdit] breakpoints and clear") { + Array arg1; + arg1.push_back(0); + Array args; + args.push_back(arg1); + + code_edit->set_text("test\nline"); + code_edit->set_line_as_breakpoint(0, true); + CHECK(code_edit->is_line_breakpointed(0)); + SIGNAL_CHECK("breakpoint_toggled", args); + + /* breakpoint on lines that still exist are removed. */ + code_edit->clear(); + MessageQueue::get_singleton()->flush(); + CHECK_FALSE(code_edit->is_line_breakpointed(0)); + SIGNAL_CHECK("breakpoint_toggled", args); + + /* breakpoint on lines that are removed should also be removed. */ + code_edit->clear_breakpointed_lines(); + SIGNAL_DISCARD("breakpoint_toggled") + + ((Array)args[0])[0] = 1; + code_edit->set_text("test\nline"); + code_edit->set_line_as_breakpoint(1, true); + CHECK(code_edit->is_line_breakpointed(1)); + SIGNAL_CHECK("breakpoint_toggled", args); + + code_edit->clear(); + MessageQueue::get_singleton()->flush(); + CHECK_FALSE(code_edit->is_line_breakpointed(0)); + ERR_PRINT_OFF; + CHECK_FALSE(code_edit->is_line_breakpointed(1)); + ERR_PRINT_ON; + SIGNAL_CHECK("breakpoint_toggled", args); + } + + SUBCASE("[CodeEdit] breakpoints and new lines no text") { + Array arg1; + arg1.push_back(0); + Array args; + args.push_back(arg1); + + /* No text moves breakpoint. */ + code_edit->set_line_as_breakpoint(0, true); + CHECK(code_edit->is_line_breakpointed(0)); + SIGNAL_CHECK("breakpoint_toggled", args); + + /* Normal. */ + ((Array)args[0])[0] = 0; + Array arg2; + arg2.push_back(1); + args.push_back(arg2); + + SEND_GUI_ACTION(code_edit, "ui_text_newline"); + CHECK(code_edit->get_line_count() == 2); + CHECK_FALSE(code_edit->is_line_breakpointed(0)); + CHECK(code_edit->is_line_breakpointed(1)); + SIGNAL_CHECK("breakpoint_toggled", args); + + /* Non-Breaking. */ + ((Array)args[0])[0] = 1; + ((Array)args[1])[0] = 2; + SEND_GUI_ACTION(code_edit, "ui_text_newline_blank"); + CHECK(code_edit->get_line_count() == 3); + CHECK_FALSE(code_edit->is_line_breakpointed(1)); + CHECK(code_edit->is_line_breakpointed(2)); + SIGNAL_CHECK("breakpoint_toggled", args); + + /* Above. */ + ((Array)args[0])[0] = 2; + ((Array)args[1])[0] = 3; + SEND_GUI_ACTION(code_edit, "ui_text_newline_above"); + CHECK(code_edit->get_line_count() == 4); + CHECK_FALSE(code_edit->is_line_breakpointed(2)); + CHECK(code_edit->is_line_breakpointed(3)); + SIGNAL_CHECK("breakpoint_toggled", args); + } + + SUBCASE("[CodeEdit] breakpoints and new lines with text") { + Array arg1; + arg1.push_back(0); + Array args; + args.push_back(arg1); + + /* Having text does not move breakpoint. */ + code_edit->insert_text_at_caret("text"); + code_edit->set_line_as_breakpoint(0, true); + CHECK(code_edit->is_line_breakpointed(0)); + SIGNAL_CHECK("breakpoint_toggled", args); + + /* Normal. */ + SEND_GUI_ACTION(code_edit, "ui_text_newline"); + CHECK(code_edit->get_line_count() == 2); + CHECK(code_edit->is_line_breakpointed(0)); + CHECK_FALSE(code_edit->is_line_breakpointed(1)); + SIGNAL_CHECK_FALSE("breakpoint_toggled"); + + /* Non-Breaking. */ + code_edit->set_caret_line(0); + SEND_GUI_ACTION(code_edit, "ui_text_newline_blank"); + CHECK(code_edit->get_line_count() == 3); + CHECK(code_edit->is_line_breakpointed(0)); + CHECK_FALSE(code_edit->is_line_breakpointed(1)); + SIGNAL_CHECK_FALSE("breakpoint_toggled"); + + /* Above does move. */ + ((Array)args[0])[0] = 0; + Array arg2; + arg2.push_back(1); + args.push_back(arg2); + + code_edit->set_caret_line(0); + SEND_GUI_ACTION(code_edit, "ui_text_newline_above"); + CHECK(code_edit->get_line_count() == 4); + CHECK_FALSE(code_edit->is_line_breakpointed(0)); + CHECK(code_edit->is_line_breakpointed(1)); + SIGNAL_CHECK("breakpoint_toggled", args); + } + + SUBCASE("[CodeEdit] breakpoints and backspace") { + Array arg1; + arg1.push_back(1); + Array args; + args.push_back(arg1); + + code_edit->set_text("\n\n"); + code_edit->set_line_as_breakpoint(1, true); + CHECK(code_edit->is_line_breakpointed(1)); + SIGNAL_CHECK("breakpoint_toggled", args); + + code_edit->set_caret_line(2); + + /* backspace onto line does not remove breakpoint */ + SEND_GUI_ACTION(code_edit, "ui_text_backspace"); + CHECK(code_edit->is_line_breakpointed(1)); + SIGNAL_CHECK_FALSE("breakpoint_toggled"); + + /* backspace on breakpointed line removes it */ + SEND_GUI_ACTION(code_edit, "ui_text_backspace"); + CHECK_FALSE(code_edit->is_line_breakpointed(0)); + ERR_PRINT_OFF; + CHECK_FALSE(code_edit->is_line_breakpointed(1)); + ERR_PRINT_ON; + SIGNAL_CHECK("breakpoint_toggled", args); + } + + SUBCASE("[CodeEdit] breakpoints and delete") { + Array arg1; + arg1.push_back(1); + Array args; + args.push_back(arg1); + + code_edit->set_text("\n\n"); + code_edit->set_line_as_breakpoint(1, true); + CHECK(code_edit->is_line_breakpointed(1)); + SIGNAL_CHECK("breakpoint_toggled", args); + code_edit->set_caret_line(1); + + /* Delete onto breakpointed lines does not remove it. */ + SEND_GUI_ACTION(code_edit, "ui_text_delete"); + CHECK(code_edit->get_line_count() == 2); + CHECK(code_edit->is_line_breakpointed(1)); + SIGNAL_CHECK_FALSE("breakpoint_toggled"); + + /* Delete moving breakpointed line up removes it. */ + code_edit->set_caret_line(0); + SEND_GUI_ACTION(code_edit, "ui_text_delete"); + CHECK(code_edit->get_line_count() == 1); + ERR_PRINT_OFF; + CHECK_FALSE(code_edit->is_line_breakpointed(1)); + ERR_PRINT_ON; + SIGNAL_CHECK("breakpoint_toggled", args); + } + + SUBCASE("[CodeEdit] breakpoints and delete selection") { + Array arg1; + arg1.push_back(1); + Array args; + args.push_back(arg1); + + code_edit->set_text("\n\n"); + code_edit->set_line_as_breakpoint(1, true); + CHECK(code_edit->is_line_breakpointed(1)); + SIGNAL_CHECK("breakpoint_toggled", args); + + code_edit->select(0, 0, 2, 0); + code_edit->delete_selection(); + MessageQueue::get_singleton()->flush(); + CHECK_FALSE(code_edit->is_line_breakpointed(0)); + SIGNAL_CHECK("breakpoint_toggled", args); + } + + SUBCASE("[CodeEdit] breakpoints and undo") { + Array arg1; + arg1.push_back(1); + Array args; + args.push_back(arg1); + + code_edit->set_text("\n\n"); + code_edit->set_line_as_breakpoint(1, true); + CHECK(code_edit->is_line_breakpointed(1)); + SIGNAL_CHECK("breakpoint_toggled", args); + + code_edit->select(0, 0, 2, 0); + code_edit->delete_selection(); + MessageQueue::get_singleton()->flush(); + CHECK_FALSE(code_edit->is_line_breakpointed(0)); + SIGNAL_CHECK("breakpoint_toggled", args); + + /* Undo does not restore breakpoint. */ + code_edit->undo(); + CHECK_FALSE(code_edit->is_line_breakpointed(1)); + SIGNAL_CHECK_FALSE("breakpoint_toggled"); + } + + SIGNAL_UNWATCH(code_edit, "breakpoint_toggled"); + } + + SUBCASE("[CodeEdit] bookmarks") { + SUBCASE("[CodeEdit] draw bookmarks gutter") { + code_edit->set_draw_bookmarks_gutter(false); + CHECK_FALSE(code_edit->is_drawing_bookmarks_gutter()); + + code_edit->set_draw_bookmarks_gutter(true); + CHECK(code_edit->is_drawing_bookmarks_gutter()); + } + + SUBCASE("[CodeEdit] set line as bookmarks") { + /* Out of bounds. */ + ERR_PRINT_OFF; + + code_edit->set_line_as_bookmarked(-1, true); + CHECK_FALSE(code_edit->is_line_bookmarked(-1)); + + code_edit->set_line_as_bookmarked(1, true); + CHECK_FALSE(code_edit->is_line_bookmarked(1)); + + ERR_PRINT_ON; + + code_edit->set_line_as_bookmarked(0, true); + CHECK(code_edit->get_bookmarked_lines()[0] == Variant(0)); + CHECK(code_edit->is_line_bookmarked(0)); + + code_edit->set_line_as_bookmarked(0, false); + CHECK_FALSE(code_edit->is_line_bookmarked(0)); + } + + SUBCASE("[CodeEdit] clear bookmarked lines") { + code_edit->clear_bookmarked_lines(); + + code_edit->set_line_as_bookmarked(0, true); + CHECK(code_edit->is_line_bookmarked(0)); + + code_edit->clear_bookmarked_lines(); + CHECK_FALSE(code_edit->is_line_bookmarked(0)); + } + + SUBCASE("[CodeEdit] bookmarks and set text") { + code_edit->set_text("test\nline"); + code_edit->set_line_as_bookmarked(0, true); + CHECK(code_edit->is_line_bookmarked(0)); + + /* bookmarks on lines that still exist are kept. */ + code_edit->set_text(""); + MessageQueue::get_singleton()->flush(); + CHECK(code_edit->is_line_bookmarked(0)); + + /* bookmarks on lines that are removed should also be removed. */ + code_edit->clear_bookmarked_lines(); + + code_edit->set_text("test\nline"); + code_edit->set_line_as_bookmarked(1, true); + CHECK(code_edit->is_line_bookmarked(1)); + + code_edit->set_text(""); + MessageQueue::get_singleton()->flush(); + CHECK_FALSE(code_edit->is_line_bookmarked(0)); + ERR_PRINT_OFF; + CHECK_FALSE(code_edit->is_line_bookmarked(1)); + ERR_PRINT_ON; + } + + SUBCASE("[CodeEdit] bookmarks and clear") { + code_edit->set_text("test\nline"); + code_edit->set_line_as_bookmarked(0, true); + CHECK(code_edit->is_line_bookmarked(0)); + + /* bookmarks on lines that still exist are removed. */ + code_edit->clear(); + MessageQueue::get_singleton()->flush(); + CHECK_FALSE(code_edit->is_line_bookmarked(0)); + + /* bookmarks on lines that are removed should also be removed. */ + code_edit->clear_bookmarked_lines(); + + code_edit->set_text("test\nline"); + code_edit->set_line_as_bookmarked(1, true); + CHECK(code_edit->is_line_bookmarked(1)); + + code_edit->clear(); + MessageQueue::get_singleton()->flush(); + CHECK_FALSE(code_edit->is_line_bookmarked(0)); + ERR_PRINT_OFF; + CHECK_FALSE(code_edit->is_line_bookmarked(1)); + ERR_PRINT_ON; + } + + SUBCASE("[CodeEdit] bookmarks and new lines no text") { + /* No text moves bookmarks. */ + code_edit->set_line_as_bookmarked(0, true); + CHECK(code_edit->is_line_bookmarked(0)); + + /* Normal. */ + SEND_GUI_ACTION(code_edit, "ui_text_newline"); + CHECK(code_edit->get_line_count() == 2); + CHECK_FALSE(code_edit->is_line_bookmarked(0)); + CHECK(code_edit->is_line_bookmarked(1)); + + /* Non-Breaking. */ + SEND_GUI_ACTION(code_edit, "ui_text_newline_blank"); + CHECK(code_edit->get_line_count() == 3); + CHECK_FALSE(code_edit->is_line_bookmarked(1)); + CHECK(code_edit->is_line_bookmarked(2)); + + /* Above. */ + SEND_GUI_ACTION(code_edit, "ui_text_newline_above"); + CHECK(code_edit->get_line_count() == 4); + CHECK_FALSE(code_edit->is_line_bookmarked(2)); + CHECK(code_edit->is_line_bookmarked(3)); + } + + SUBCASE("[CodeEdit] bookmarks and new lines with text") { + /* Having text does not move bookmark. */ + code_edit->insert_text_at_caret("text"); + code_edit->set_line_as_bookmarked(0, true); + CHECK(code_edit->is_line_bookmarked(0)); + + /* Normal. */ + SEND_GUI_ACTION(code_edit, "ui_text_newline"); + CHECK(code_edit->get_line_count() == 2); + CHECK(code_edit->is_line_bookmarked(0)); + CHECK_FALSE(code_edit->is_line_bookmarked(1)); + + /* Non-Breaking. */ + code_edit->set_caret_line(0); + SEND_GUI_ACTION(code_edit, "ui_text_newline_blank"); + CHECK(code_edit->get_line_count() == 3); + CHECK(code_edit->is_line_bookmarked(0)); + CHECK_FALSE(code_edit->is_line_bookmarked(1)); + + /* Above does move. */ + code_edit->set_caret_line(0); + SEND_GUI_ACTION(code_edit, "ui_text_newline_above"); + CHECK(code_edit->get_line_count() == 4); + CHECK_FALSE(code_edit->is_line_bookmarked(0)); + CHECK(code_edit->is_line_bookmarked(1)); + } + + SUBCASE("[CodeEdit] bookmarks and backspace") { + code_edit->set_text("\n\n"); + code_edit->set_line_as_bookmarked(1, true); + CHECK(code_edit->is_line_bookmarked(1)); + + code_edit->set_caret_line(2); + + /* backspace onto line does not remove bookmark */ + SEND_GUI_ACTION(code_edit, "ui_text_backspace"); + CHECK(code_edit->is_line_bookmarked(1)); + + /* backspace on bookmarked line removes it */ + SEND_GUI_ACTION(code_edit, "ui_text_backspace"); + CHECK_FALSE(code_edit->is_line_bookmarked(0)); + ERR_PRINT_OFF; + CHECK_FALSE(code_edit->is_line_bookmarked(1)); + ERR_PRINT_ON; + } + + SUBCASE("[CodeEdit] bookmarks and delete") { + code_edit->set_text("\n\n"); + code_edit->set_line_as_bookmarked(1, true); + CHECK(code_edit->is_line_bookmarked(1)); + code_edit->set_caret_line(1); + + /* Delete onto bookmarked lines does not remove it. */ + SEND_GUI_ACTION(code_edit, "ui_text_delete"); + CHECK(code_edit->get_line_count() == 2); + CHECK(code_edit->is_line_bookmarked(1)); + + /* Delete moving bookmarked line up removes it. */ + code_edit->set_caret_line(0); + SEND_GUI_ACTION(code_edit, "ui_text_delete"); + CHECK(code_edit->get_line_count() == 1); + ERR_PRINT_OFF; + CHECK_FALSE(code_edit->is_line_bookmarked(1)); + ERR_PRINT_ON; + } + + SUBCASE("[CodeEdit] bookmarks and delete selection") { + code_edit->set_text("\n\n"); + code_edit->set_line_as_bookmarked(1, true); + CHECK(code_edit->is_line_bookmarked(1)); + + code_edit->select(0, 0, 2, 0); + code_edit->delete_selection(); + MessageQueue::get_singleton()->flush(); + CHECK_FALSE(code_edit->is_line_bookmarked(0)); + } + + SUBCASE("[CodeEdit] bookmarks and undo") { + code_edit->set_text("\n\n"); + code_edit->set_line_as_bookmarked(1, true); + CHECK(code_edit->is_line_bookmarked(1)); + + code_edit->select(0, 0, 2, 0); + code_edit->delete_selection(); + MessageQueue::get_singleton()->flush(); + CHECK_FALSE(code_edit->is_line_bookmarked(0)); + + /* Undo does not restore bookmark. */ + code_edit->undo(); + CHECK_FALSE(code_edit->is_line_bookmarked(1)); + } + } + + SUBCASE("[CodeEdit] executing lines") { + SUBCASE("[CodeEdit] draw executing lines gutter") { + code_edit->set_draw_executing_lines_gutter(false); + CHECK_FALSE(code_edit->is_drawing_executing_lines_gutter()); + + code_edit->set_draw_executing_lines_gutter(true); + CHECK(code_edit->is_drawing_executing_lines_gutter()); + } + + SUBCASE("[CodeEdit] set line as executing lines") { + /* Out of bounds. */ + ERR_PRINT_OFF; + + code_edit->set_line_as_executing(-1, true); + CHECK_FALSE(code_edit->is_line_executing(-1)); + + code_edit->set_line_as_executing(1, true); + CHECK_FALSE(code_edit->is_line_executing(1)); + + ERR_PRINT_ON; + + code_edit->set_line_as_executing(0, true); + CHECK(code_edit->get_executing_lines()[0] == Variant(0)); + CHECK(code_edit->is_line_executing(0)); + + code_edit->set_line_as_executing(0, false); + CHECK_FALSE(code_edit->is_line_executing(0)); + } + + SUBCASE("[CodeEdit] clear executing lines lines") { + code_edit->clear_executing_lines(); + + code_edit->set_line_as_executing(0, true); + CHECK(code_edit->is_line_executing(0)); + + code_edit->clear_executing_lines(); + CHECK_FALSE(code_edit->is_line_executing(0)); + } + + SUBCASE("[CodeEdit] executing lines and set text") { + code_edit->set_text("test\nline"); + code_edit->set_line_as_executing(0, true); + CHECK(code_edit->is_line_executing(0)); + + /* executing on lines that still exist are kept. */ + code_edit->set_text(""); + MessageQueue::get_singleton()->flush(); + CHECK(code_edit->is_line_executing(0)); + + /* executing on lines that are removed should also be removed. */ + code_edit->clear_executing_lines(); + + code_edit->set_text("test\nline"); + code_edit->set_line_as_executing(1, true); + CHECK(code_edit->is_line_executing(1)); + + code_edit->set_text(""); + MessageQueue::get_singleton()->flush(); + CHECK_FALSE(code_edit->is_line_executing(0)); + ERR_PRINT_OFF; + CHECK_FALSE(code_edit->is_line_executing(1)); + ERR_PRINT_ON; + } + + SUBCASE("[CodeEdit] executing lines and clear") { + code_edit->set_text("test\nline"); + code_edit->set_line_as_executing(0, true); + CHECK(code_edit->is_line_executing(0)); + + /* executing on lines that still exist are removed. */ + code_edit->clear(); + MessageQueue::get_singleton()->flush(); + CHECK_FALSE(code_edit->is_line_executing(0)); + + /* executing on lines that are removed should also be removed. */ + code_edit->clear_executing_lines(); + + code_edit->set_text("test\nline"); + code_edit->set_line_as_executing(1, true); + CHECK(code_edit->is_line_executing(1)); + + code_edit->clear(); + MessageQueue::get_singleton()->flush(); + CHECK_FALSE(code_edit->is_line_executing(0)); + ERR_PRINT_OFF; + CHECK_FALSE(code_edit->is_line_executing(1)); + ERR_PRINT_ON; + } + + SUBCASE("[CodeEdit] executing lines and new lines no text") { + /* No text moves executing lines. */ + code_edit->set_line_as_executing(0, true); + CHECK(code_edit->is_line_executing(0)); + + /* Normal. */ + SEND_GUI_ACTION(code_edit, "ui_text_newline"); + CHECK(code_edit->get_line_count() == 2); + CHECK_FALSE(code_edit->is_line_executing(0)); + CHECK(code_edit->is_line_executing(1)); + + /* Non-Breaking. */ + SEND_GUI_ACTION(code_edit, "ui_text_newline_blank"); + CHECK(code_edit->get_line_count() == 3); + CHECK_FALSE(code_edit->is_line_executing(1)); + CHECK(code_edit->is_line_executing(2)); + + /* Above. */ + SEND_GUI_ACTION(code_edit, "ui_text_newline_above"); + CHECK(code_edit->get_line_count() == 4); + CHECK_FALSE(code_edit->is_line_executing(2)); + CHECK(code_edit->is_line_executing(3)); + } + + SUBCASE("[CodeEdit] executing lines and new lines with text") { + /* Having text does not move executing lines. */ + code_edit->insert_text_at_caret("text"); + code_edit->set_line_as_executing(0, true); + CHECK(code_edit->is_line_executing(0)); + + /* Normal. */ + SEND_GUI_ACTION(code_edit, "ui_text_newline"); + CHECK(code_edit->get_line_count() == 2); + CHECK(code_edit->is_line_executing(0)); + CHECK_FALSE(code_edit->is_line_executing(1)); + + /* Non-Breaking. */ + code_edit->set_caret_line(0); + SEND_GUI_ACTION(code_edit, "ui_text_newline_blank"); + CHECK(code_edit->get_line_count() == 3); + CHECK(code_edit->is_line_executing(0)); + CHECK_FALSE(code_edit->is_line_executing(1)); + + /* Above does move. */ + code_edit->set_caret_line(0); + SEND_GUI_ACTION(code_edit, "ui_text_newline_above"); + CHECK(code_edit->get_line_count() == 4); + CHECK_FALSE(code_edit->is_line_executing(0)); + CHECK(code_edit->is_line_executing(1)); + } + + SUBCASE("[CodeEdit] executing lines and backspace") { + code_edit->set_text("\n\n"); + code_edit->set_line_as_executing(1, true); + CHECK(code_edit->is_line_executing(1)); + + code_edit->set_caret_line(2); + + /* backspace onto line does not remove executing lines. */ + SEND_GUI_ACTION(code_edit, "ui_text_backspace"); + CHECK(code_edit->is_line_executing(1)); + + /* backspace on executing line removes it */ + SEND_GUI_ACTION(code_edit, "ui_text_backspace"); + CHECK_FALSE(code_edit->is_line_executing(0)); + ERR_PRINT_OFF; + CHECK_FALSE(code_edit->is_line_executing(1)); + ERR_PRINT_ON; + } + + SUBCASE("[CodeEdit] executing lines and delete") { + code_edit->set_text("\n\n"); + code_edit->set_line_as_executing(1, true); + CHECK(code_edit->is_line_executing(1)); + code_edit->set_caret_line(1); + + /* Delete onto executing lines does not remove it. */ + SEND_GUI_ACTION(code_edit, "ui_text_delete"); + CHECK(code_edit->get_line_count() == 2); + CHECK(code_edit->is_line_executing(1)); + + /* Delete moving executing line up removes it. */ + code_edit->set_caret_line(0); + SEND_GUI_ACTION(code_edit, "ui_text_delete"); + CHECK(code_edit->get_line_count() == 1); + ERR_PRINT_OFF; + CHECK_FALSE(code_edit->is_line_executing(1)); + ERR_PRINT_ON; + } + + SUBCASE("[CodeEdit] executing lines and delete selection") { + code_edit->set_text("\n\n"); + code_edit->set_line_as_executing(1, true); + CHECK(code_edit->is_line_executing(1)); + + code_edit->select(0, 0, 2, 0); + code_edit->delete_selection(); + MessageQueue::get_singleton()->flush(); + CHECK_FALSE(code_edit->is_line_executing(0)); + } + + SUBCASE("[CodeEdit] executing lines and undo") { + code_edit->set_text("\n\n"); + code_edit->set_line_as_executing(1, true); + CHECK(code_edit->is_line_executing(1)); + + code_edit->select(0, 0, 2, 0); + code_edit->delete_selection(); + MessageQueue::get_singleton()->flush(); + CHECK_FALSE(code_edit->is_line_executing(0)); + + /* Undo does not restore executing lines. */ + code_edit->undo(); + CHECK_FALSE(code_edit->is_line_executing(1)); + } + } + + SUBCASE("[CodeEdit] line numbers") { + SUBCASE("[CodeEdit] draw line numbers gutter and padding") { + code_edit->set_draw_line_numbers(false); + CHECK_FALSE(code_edit->is_draw_line_numbers_enabled()); + + code_edit->set_draw_line_numbers(true); + CHECK(code_edit->is_draw_line_numbers_enabled()); + + code_edit->set_line_numbers_zero_padded(false); + CHECK_FALSE(code_edit->is_line_numbers_zero_padded()); + + code_edit->set_line_numbers_zero_padded(true); + CHECK(code_edit->is_line_numbers_zero_padded()); + + code_edit->set_line_numbers_zero_padded(false); + CHECK_FALSE(code_edit->is_line_numbers_zero_padded()); + + code_edit->set_draw_line_numbers(false); + CHECK_FALSE(code_edit->is_draw_line_numbers_enabled()); + + code_edit->set_line_numbers_zero_padded(true); + CHECK(code_edit->is_line_numbers_zero_padded()); + } + } + + SUBCASE("[CodeEdit] line folding") { + SUBCASE("[CodeEdit] draw line folding gutter") { + code_edit->set_draw_fold_gutter(false); + CHECK_FALSE(code_edit->is_drawing_fold_gutter()); + + code_edit->set_draw_fold_gutter(true); + CHECK(code_edit->is_drawing_fold_gutter()); + } + } + + memdelete(code_edit); +} + +} // namespace TestCodeEdit + +#endif // TEST_CODE_EDIT_H diff --git a/tests/test_macros.h b/tests/test_macros.h index a1f1932db4..bf1001fa67 100644 --- a/tests/test_macros.h +++ b/tests/test_macros.h @@ -31,6 +31,8 @@ #ifndef TEST_MACROS_H #define TEST_MACROS_H +#include "core/object/callable_method_pointer.h" +#include "core/object/class_db.h" #include "core/templates/map.h" #include "core/variant/variant.h" @@ -129,4 +131,186 @@ int register_test_command(String p_command, TestFunc p_function); register_test_command(m_command, m_function); \ DOCTEST_GLOBAL_NO_WARNINGS_END() +// Utility macro to send an action event to a given object +// Requires Message Queue and InputMap to be setup. + +#define SEND_GUI_ACTION(m_object, m_action) \ + { \ + const List<Ref<InputEvent>> *events = InputMap::get_singleton()->action_get_events(m_action); \ + const List<Ref<InputEvent>>::Element *first_event = events->front(); \ + Ref<InputEventKey> event = first_event->get(); \ + event->set_pressed(true); \ + m_object->gui_input(event); \ + MessageQueue::get_singleton()->flush(); \ + } + +// Utility class / macros for testing signals +// +// Use SIGNAL_WATCH(*object, "signal_name") to start watching +// Makes sure to call SIGNAL_UNWATCH(*object, "signal_name") to stop watching in cleanup, this is not done automatically. +// +// The SignalWatcher will capture all signals and their args sent between checks. +// +// Use SIGNAL_CHECK("signal_name"), Vector<Vector<Variant>>), to check the arguments of all fired signals. +// The outer vector is each fired signal, the inner vector the list of arguments for that signal. Order does matter. +// +// Use SIGNAL_CHECK_FALSE("signal_name") to check if a signal was not fired. +// +// Use SIGNAL_DISCARD("signal_name") to discard records all of the given signal, use only in placed you don't need to check. +// +// All signals are automaticaly discared between test/sub test cases. + +class SignalWatcher : public Object { +private: + inline static SignalWatcher *singleton; + + /* Equal to: Map<String, Vector<Vector<Variant>>> */ + Map<String, Array> _signals; + void _add_signal_entry(const Array &p_args, const String &p_name) { + if (!_signals.has(p_name)) { + _signals[p_name] = Array(); + } + _signals[p_name].push_back(p_args); + } + + void _signal_callback_zero(const String &p_name) { + Array args; + _add_signal_entry(args, p_name); + } + + void _signal_callback_one(Variant p_arg1, const String &p_name) { + Array args; + args.push_back(p_arg1); + _add_signal_entry(args, p_name); + } + + void _signal_callback_two(Variant p_arg1, Variant p_arg2, const String &p_name) { + Array args; + args.push_back(p_arg1); + args.push_back(p_arg2); + _add_signal_entry(args, p_name); + } + + void _signal_callback_three(Variant p_arg1, Variant p_arg2, Variant p_arg3, const String &p_name) { + Array args; + args.push_back(p_arg1); + args.push_back(p_arg2); + args.push_back(p_arg3); + _add_signal_entry(args, p_name); + } + +public: + static SignalWatcher *get_singleton() { return singleton; } + + void watch_signal(Object *p_object, const String &p_signal) { + Vector<Variant> args; + args.push_back(p_signal); + MethodInfo method_info; + ClassDB::get_signal(p_object->get_class(), p_signal, &method_info); + switch (method_info.arguments.size()) { + case 0: { + p_object->connect(p_signal, callable_mp(this, &SignalWatcher::_signal_callback_zero), args); + } break; + case 1: { + p_object->connect(p_signal, callable_mp(this, &SignalWatcher::_signal_callback_one), args); + } break; + case 2: { + p_object->connect(p_signal, callable_mp(this, &SignalWatcher::_signal_callback_two), args); + } break; + case 3: { + p_object->connect(p_signal, callable_mp(this, &SignalWatcher::_signal_callback_three), args); + } break; + default: { + MESSAGE("Signal ", p_signal, " arg count not supported."); + } break; + } + } + + void unwatch_signal(Object *p_object, const String &p_signal) { + MethodInfo method_info; + ClassDB::get_signal(p_object->get_class(), p_signal, &method_info); + switch (method_info.arguments.size()) { + case 0: { + p_object->disconnect(p_signal, callable_mp(this, &SignalWatcher::_signal_callback_zero)); + } break; + case 1: { + p_object->disconnect(p_signal, callable_mp(this, &SignalWatcher::_signal_callback_one)); + } break; + case 2: { + p_object->disconnect(p_signal, callable_mp(this, &SignalWatcher::_signal_callback_two)); + } break; + case 3: { + p_object->disconnect(p_signal, callable_mp(this, &SignalWatcher::_signal_callback_three)); + } break; + default: { + MESSAGE("Signal ", p_signal, " arg count not supported."); + } break; + } + } + + bool check(const String &p_name, const Array &p_args) { + if (!_signals.has(p_name)) { + MESSAGE("Signal ", p_name, " not emitted"); + return false; + } + + if (p_args.size() != _signals[p_name].size()) { + MESSAGE("Signal has " << _signals[p_name] << " expected " << p_args); + discard_signal(p_name); + return false; + } + + bool match = true; + for (int i = 0; i < p_args.size(); i++) { + if (((Array)p_args[i]).size() != ((Array)_signals[p_name][i]).size()) { + MESSAGE("Signal has " << _signals[p_name][i] << " expected " << p_args[i]); + match = false; + continue; + } + + for (int j = 0; j < ((Array)p_args[i]).size(); j++) { + if (((Array)p_args[i])[j] != ((Array)_signals[p_name][i])[j]) { + MESSAGE("Signal has " << _signals[p_name][i] << " expected " << p_args[i]); + match = false; + break; + } + } + } + + discard_signal(p_name); + return match; + } + + bool check_false(const String &p_name) { + bool has = _signals.has(p_name); + discard_signal(p_name); + return !has; + } + + void discard_signal(const String &p_name) { + if (_signals.has(p_name)) { + _signals.erase(p_name); + } + } + + void _clear_signals() { + _signals.clear(); + } + + SignalWatcher() { + singleton = this; + } + + ~SignalWatcher() { + singleton = nullptr; + } +}; + +#define SIGNAL_WATCH(m_object, m_signal) SignalWatcher::get_singleton()->watch_signal(m_object, m_signal); +#define SIGNAL_UNWATCH(m_object, m_signal) SignalWatcher::get_singleton()->unwatch_signal(m_object, m_signal); + +#define SIGNAL_CHECK(m_signal, m_args) CHECK(SignalWatcher::get_singleton()->check(m_signal, m_args)); +#define SIGNAL_CHECK_FALSE(m_signal) CHECK(SignalWatcher::get_singleton()->check_false(m_signal)); +#define SIGNAL_DISCARD(m_signal) SignalWatcher::get_singleton()->discard_signal(m_signal); + #endif // TEST_MACROS_H diff --git a/tests/test_main.cpp b/tests/test_main.cpp index d0466d1e2d..e4aa4c38ff 100644 --- a/tests/test_main.cpp +++ b/tests/test_main.cpp @@ -37,6 +37,7 @@ #include "test_astar.h" #include "test_basis.h" #include "test_class_db.h" +#include "test_code_edit.h" #include "test_color.h" #include "test_command_queue.h" #include "test_config_file.h" @@ -146,3 +147,153 @@ int test_main(int argc, char *argv[]) { return test_context.run(); } + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#include "servers/navigation_server_2d.h" +#include "servers/navigation_server_3d.h" +#include "servers/rendering/rendering_server_default.h" + +struct GodotTestCaseListener : public doctest::IReporter { + GodotTestCaseListener(const doctest::ContextOptions &p_in) {} + + SignalWatcher *signal_watcher = nullptr; + + PhysicsServer3D *physics_3d_server = nullptr; + PhysicsServer2D *physics_2d_server = nullptr; + NavigationServer3D *navigation_3d_server = nullptr; + NavigationServer2D *navigation_2d_server = nullptr; + + void test_case_start(const doctest::TestCaseData &p_in) override { + SignalWatcher::get_singleton()->_clear_signals(); + + String name = String(p_in.m_name); + + if (name.find("[SceneTree]") != -1) { + GLOBAL_DEF("memory/limits/multithreaded_server/rid_pool_prealloc", 60); + memnew(MessageQueue); + + GLOBAL_DEF("internationalization/rendering/force_right_to_left_layout_direction", false); + memnew(TextServerManager); + Error err = OK; + TextServerManager::initialize(0, err); + + OS::get_singleton()->set_has_server_feature_callback(nullptr); + for (int i = 0; i < DisplayServer::get_create_function_count(); i++) { + if (String("headless") == DisplayServer::get_create_function_name(i)) { + DisplayServer::create(i, "", DisplayServer::WindowMode::WINDOW_MODE_MINIMIZED, DisplayServer::VSyncMode::VSYNC_ENABLED, 0, Vector2i(0, 0), err); + break; + } + } + memnew(RenderingServerDefault()); + RenderingServerDefault::get_singleton()->init(); + RenderingServerDefault::get_singleton()->set_render_loop_enabled(false); + + physics_3d_server = PhysicsServer3DManager::new_default_server(); + physics_3d_server->init(); + + physics_2d_server = PhysicsServer2DManager::new_default_server(); + physics_2d_server->init(); + + navigation_3d_server = NavigationServer3DManager::new_default_server(); + navigation_2d_server = memnew(NavigationServer2D); + + memnew(InputMap); + InputMap::get_singleton()->load_default(); + + make_default_theme(false, Ref<Font>()); + + memnew(SceneTree); + SceneTree::get_singleton()->initialize(); + return; + } + } + + void test_case_end(const doctest::CurrentTestCaseStats &) override { + if (SceneTree::get_singleton()) { + SceneTree::get_singleton()->finalize(); + } + + if (MessageQueue::get_singleton()) { + MessageQueue::get_singleton()->flush(); + } + + if (SceneTree::get_singleton()) { + memdelete(SceneTree::get_singleton()); + } + + clear_default_theme(); + + if (TextServerManager::get_singleton()) { + memdelete(TextServerManager::get_singleton()); + } + + if (navigation_3d_server) { + memdelete(navigation_3d_server); + navigation_3d_server = nullptr; + } + + if (navigation_2d_server) { + memdelete(navigation_2d_server); + navigation_2d_server = nullptr; + } + + if (physics_3d_server) { + physics_3d_server->finish(); + memdelete(physics_3d_server); + physics_3d_server = nullptr; + } + + if (physics_2d_server) { + physics_2d_server->finish(); + memdelete(physics_2d_server); + physics_2d_server = nullptr; + } + + if (RenderingServer::get_singleton()) { + RenderingServer::get_singleton()->sync(); + RenderingServer::get_singleton()->global_variables_clear(); + RenderingServer::get_singleton()->finish(); + memdelete(RenderingServer::get_singleton()); + } + + if (DisplayServer::get_singleton()) { + memdelete(DisplayServer::get_singleton()); + } + + if (InputMap::get_singleton()) { + memdelete(InputMap::get_singleton()); + } + + if (MessageQueue::get_singleton()) { + MessageQueue::get_singleton()->flush(); + memdelete(MessageQueue::get_singleton()); + } + } + + void test_run_start() override { + signal_watcher = memnew(SignalWatcher); + } + + void test_run_end(const doctest::TestRunStats &) override { + memdelete(signal_watcher); + } + + void test_case_reenter(const doctest::TestCaseData &) override { + SignalWatcher::get_singleton()->_clear_signals(); + } + + void subcase_start(const doctest::SubcaseSignature &) override { + SignalWatcher::get_singleton()->_clear_signals(); + } + + void report_query(const doctest::QueryData &) override {} + void test_case_exception(const doctest::TestCaseException &) override {} + void subcase_end() override {} + + void log_assert(const doctest::AssertData &in) override {} + void log_message(const doctest::MessageData &) override {} + void test_case_skipped(const doctest::TestCaseData &) override {} +}; + +REGISTER_LISTENER("GodotTestCaseListener", 1, GodotTestCaseListener); diff --git a/tests/test_object.h b/tests/test_object.h index a18adf31b6..8cb7116a20 100644 --- a/tests/test_object.h +++ b/tests/test_object.h @@ -93,8 +93,8 @@ public: Ref<Script> get_script() const override { return Ref<Script>(); } - const Vector<MultiplayerAPI::RPCConfig> get_rpc_methods() const override { - return Vector<MultiplayerAPI::RPCConfig>(); + const Vector<Multiplayer::RPCConfig> get_rpc_methods() const override { + return Vector<Multiplayer::RPCConfig>(); } ScriptLanguage *get_language() override { return nullptr; diff --git a/tests/test_string.h b/tests/test_string.h index 82b23d8a00..bcedaa0db7 100644 --- a/tests/test_string.h +++ b/tests/test_string.h @@ -1142,14 +1142,14 @@ TEST_CASE("[String] dedent") { } TEST_CASE("[String] Path functions") { - static const char *path[4] = { "C:\\Godot\\project\\test.tscn", "/Godot/project/test.xscn", "../Godot/project/test.scn", "Godot\\test.doc" }; - static const char *base_dir[4] = { "C:\\Godot\\project", "/Godot/project", "../Godot/project", "Godot" }; - static const char *base_name[4] = { "C:\\Godot\\project\\test", "/Godot/project/test", "../Godot/project/test", "Godot\\test" }; - static const char *ext[4] = { "tscn", "xscn", "scn", "doc" }; - static const char *file[4] = { "test.tscn", "test.xscn", "test.scn", "test.doc" }; - static const bool abs[4] = { true, true, false, false }; - - for (int i = 0; i < 4; i++) { + static const char *path[7] = { "C:\\Godot\\project\\test.tscn", "/Godot/project/test.xscn", "../Godot/project/test.scn", "Godot\\test.doc", "C:\\test.", "res://test", "/.test" }; + static const char *base_dir[7] = { "C:\\Godot\\project", "/Godot/project", "../Godot/project", "Godot", "C:\\", "res://", "/" }; + static const char *base_name[7] = { "C:\\Godot\\project\\test", "/Godot/project/test", "../Godot/project/test", "Godot\\test", "C:\\test", "res://test", "/" }; + static const char *ext[7] = { "tscn", "xscn", "scn", "doc", "", "", "test" }; + static const char *file[7] = { "test.tscn", "test.xscn", "test.scn", "test.doc", "test.", "test", ".test" }; + static const bool abs[7] = { true, true, false, false, true, true, true }; + + for (int i = 0; i < 7; i++) { CHECK(String(path[i]).get_base_dir() == base_dir[i]); CHECK(String(path[i]).get_basename() == base_name[i]); CHECK(String(path[i]).get_extension() == ext[i]); diff --git a/thirdparty/README.md b/thirdparty/README.md index dd2f509516..3b5ec77b73 100644 --- a/thirdparty/README.md +++ b/thirdparty/README.md @@ -472,10 +472,6 @@ Collection of single-file libraries used in Godot components. * Upstream: https://github.com/nothings/stb * Version: 1.00 (2bb4a0accd4003c1db4c24533981e01b1adfd656, 2019) * License: Public Domain or Unlicense or MIT -- `stb_vorbis.c` - * Upstream: https://github.com/nothings/stb - * Version: 1.20 (314d0a6f9af5af27e585336eecea333e95c5a2d8, 2020) - * License: Public Domain or Unlicense or MIT - `yuv2rgb.h` * Upstream: http://wss.co.uk/pinknoise/yuv2rgb/ (to check) * Version: ? diff --git a/thirdparty/misc/stb_vorbis.c b/thirdparty/misc/stb_vorbis.c deleted file mode 100644 index a8cbfa6c23..0000000000 --- a/thirdparty/misc/stb_vorbis.c +++ /dev/null @@ -1,5563 +0,0 @@ -// Ogg Vorbis audio decoder - v1.20 - public domain -// http://nothings.org/stb_vorbis/ -// -// Original version written by Sean Barrett in 2007. -// -// Originally sponsored by RAD Game Tools. Seeking implementation -// sponsored by Phillip Bennefall, Marc Andersen, Aaron Baker, -// Elias Software, Aras Pranckevicius, and Sean Barrett. -// -// LICENSE -// -// See end of file for license information. -// -// Limitations: -// -// - floor 0 not supported (used in old ogg vorbis files pre-2004) -// - lossless sample-truncation at beginning ignored -// - cannot concatenate multiple vorbis streams -// - sample positions are 32-bit, limiting seekable 192Khz -// files to around 6 hours (Ogg supports 64-bit) -// -// Feature contributors: -// Dougall Johnson (sample-exact seeking) -// -// Bugfix/warning contributors: -// Terje Mathisen Niklas Frykholm Andy Hill -// Casey Muratori John Bolton Gargaj -// Laurent Gomila Marc LeBlanc Ronny Chevalier -// Bernhard Wodo Evan Balster github:alxprd -// Tom Beaumont Ingo Leitgeb Nicolas Guillemot -// Phillip Bennefall Rohit Thiago Goulart -// github:manxorist saga musix github:infatum -// Timur Gagiev Maxwell Koo Peter Waller -// github:audinowho Dougall Johnson David Reid -// github:Clownacy Pedro J. Estebanez Remi Verschelde -// -// Partial history: -// 1.20 - 2020-07-11 - several small fixes -// 1.19 - 2020-02-05 - warnings -// 1.18 - 2020-02-02 - fix seek bugs; parse header comments; misc warnings etc. -// 1.17 - 2019-07-08 - fix CVE-2019-13217..CVE-2019-13223 (by ForAllSecure) -// 1.16 - 2019-03-04 - fix warnings -// 1.15 - 2019-02-07 - explicit failure if Ogg Skeleton data is found -// 1.14 - 2018-02-11 - delete bogus dealloca usage -// 1.13 - 2018-01-29 - fix truncation of last frame (hopefully) -// 1.12 - 2017-11-21 - limit residue begin/end to blocksize/2 to avoid large temp allocs in bad/corrupt files -// 1.11 - 2017-07-23 - fix MinGW compilation -// 1.10 - 2017-03-03 - more robust seeking; fix negative ilog(); clear error in open_memory -// 1.09 - 2016-04-04 - back out 'truncation of last frame' fix from previous version -// 1.08 - 2016-04-02 - warnings; setup memory leaks; truncation of last frame -// 1.07 - 2015-01-16 - fixes for crashes on invalid files; warning fixes; const -// 1.06 - 2015-08-31 - full, correct support for seeking API (Dougall Johnson) -// some crash fixes when out of memory or with corrupt files -// fix some inappropriately signed shifts -// 1.05 - 2015-04-19 - don't define __forceinline if it's redundant -// 1.04 - 2014-08-27 - fix missing const-correct case in API -// 1.03 - 2014-08-07 - warning fixes -// 1.02 - 2014-07-09 - declare qsort comparison as explicitly _cdecl in Windows -// 1.01 - 2014-06-18 - fix stb_vorbis_get_samples_float (interleaved was correct) -// 1.0 - 2014-05-26 - fix memory leaks; fix warnings; fix bugs in >2-channel; -// (API change) report sample rate for decode-full-file funcs -// -// See end of file for full version history. - - -////////////////////////////////////////////////////////////////////////////// -// -// HEADER BEGINS HERE -// - -#ifndef STB_VORBIS_INCLUDE_STB_VORBIS_H -#define STB_VORBIS_INCLUDE_STB_VORBIS_H - -#if defined(STB_VORBIS_NO_CRT) && !defined(STB_VORBIS_NO_STDIO) -#define STB_VORBIS_NO_STDIO 1 -#endif - -#ifndef STB_VORBIS_NO_STDIO -#include <stdio.h> -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -/////////// THREAD SAFETY - -// Individual stb_vorbis* handles are not thread-safe; you cannot decode from -// them from multiple threads at the same time. However, you can have multiple -// stb_vorbis* handles and decode from them independently in multiple thrads. - - -/////////// MEMORY ALLOCATION - -// normally stb_vorbis uses malloc() to allocate memory at startup, -// and alloca() to allocate temporary memory during a frame on the -// stack. (Memory consumption will depend on the amount of setup -// data in the file and how you set the compile flags for speed -// vs. size. In my test files the maximal-size usage is ~150KB.) -// -// You can modify the wrapper functions in the source (setup_malloc, -// setup_temp_malloc, temp_malloc) to change this behavior, or you -// can use a simpler allocation model: you pass in a buffer from -// which stb_vorbis will allocate _all_ its memory (including the -// temp memory). "open" may fail with a VORBIS_outofmem if you -// do not pass in enough data; there is no way to determine how -// much you do need except to succeed (at which point you can -// query get_info to find the exact amount required. yes I know -// this is lame). -// -// If you pass in a non-NULL buffer of the type below, allocation -// will occur from it as described above. Otherwise just pass NULL -// to use malloc()/alloca() - -typedef struct -{ - char *alloc_buffer; - int alloc_buffer_length_in_bytes; -} stb_vorbis_alloc; - - -/////////// FUNCTIONS USEABLE WITH ALL INPUT MODES - -typedef struct stb_vorbis stb_vorbis; - -typedef struct -{ - unsigned int sample_rate; - int channels; - - unsigned int setup_memory_required; - unsigned int setup_temp_memory_required; - unsigned int temp_memory_required; - - int max_frame_size; -} stb_vorbis_info; - -typedef struct -{ - char *vendor; - - int comment_list_length; - char **comment_list; -} stb_vorbis_comment; - -// get general information about the file -extern stb_vorbis_info stb_vorbis_get_info(stb_vorbis *f); - -// get ogg comments -extern stb_vorbis_comment stb_vorbis_get_comment(stb_vorbis *f); - -// get the last error detected (clears it, too) -extern int stb_vorbis_get_error(stb_vorbis *f); - -// close an ogg vorbis file and free all memory in use -extern void stb_vorbis_close(stb_vorbis *f); - -// this function returns the offset (in samples) from the beginning of the -// file that will be returned by the next decode, if it is known, or -1 -// otherwise. after a flush_pushdata() call, this may take a while before -// it becomes valid again. -// NOT WORKING YET after a seek with PULLDATA API -extern int stb_vorbis_get_sample_offset(stb_vorbis *f); - -// returns the current seek point within the file, or offset from the beginning -// of the memory buffer. In pushdata mode it returns 0. -extern unsigned int stb_vorbis_get_file_offset(stb_vorbis *f); - -/////////// PUSHDATA API - -#ifndef STB_VORBIS_NO_PUSHDATA_API - -// this API allows you to get blocks of data from any source and hand -// them to stb_vorbis. you have to buffer them; stb_vorbis will tell -// you how much it used, and you have to give it the rest next time; -// and stb_vorbis may not have enough data to work with and you will -// need to give it the same data again PLUS more. Note that the Vorbis -// specification does not bound the size of an individual frame. - -extern stb_vorbis *stb_vorbis_open_pushdata( - const unsigned char * datablock, int datablock_length_in_bytes, - int *datablock_memory_consumed_in_bytes, - int *error, - const stb_vorbis_alloc *alloc_buffer); -// create a vorbis decoder by passing in the initial data block containing -// the ogg&vorbis headers (you don't need to do parse them, just provide -// the first N bytes of the file--you're told if it's not enough, see below) -// on success, returns an stb_vorbis *, does not set error, returns the amount of -// data parsed/consumed on this call in *datablock_memory_consumed_in_bytes; -// on failure, returns NULL on error and sets *error, does not change *datablock_memory_consumed -// if returns NULL and *error is VORBIS_need_more_data, then the input block was -// incomplete and you need to pass in a larger block from the start of the file - -extern int stb_vorbis_decode_frame_pushdata( - stb_vorbis *f, - const unsigned char *datablock, int datablock_length_in_bytes, - int *channels, // place to write number of float * buffers - float ***output, // place to write float ** array of float * buffers - int *samples // place to write number of output samples - ); -// decode a frame of audio sample data if possible from the passed-in data block -// -// return value: number of bytes we used from datablock -// -// possible cases: -// 0 bytes used, 0 samples output (need more data) -// N bytes used, 0 samples output (resynching the stream, keep going) -// N bytes used, M samples output (one frame of data) -// note that after opening a file, you will ALWAYS get one N-bytes,0-sample -// frame, because Vorbis always "discards" the first frame. -// -// Note that on resynch, stb_vorbis will rarely consume all of the buffer, -// instead only datablock_length_in_bytes-3 or less. This is because it wants -// to avoid missing parts of a page header if they cross a datablock boundary, -// without writing state-machiney code to record a partial detection. -// -// The number of channels returned are stored in *channels (which can be -// NULL--it is always the same as the number of channels reported by -// get_info). *output will contain an array of float* buffers, one per -// channel. In other words, (*output)[0][0] contains the first sample from -// the first channel, and (*output)[1][0] contains the first sample from -// the second channel. - -extern void stb_vorbis_flush_pushdata(stb_vorbis *f); -// inform stb_vorbis that your next datablock will not be contiguous with -// previous ones (e.g. you've seeked in the data); future attempts to decode -// frames will cause stb_vorbis to resynchronize (as noted above), and -// once it sees a valid Ogg page (typically 4-8KB, as large as 64KB), it -// will begin decoding the _next_ frame. -// -// if you want to seek using pushdata, you need to seek in your file, then -// call stb_vorbis_flush_pushdata(), then start calling decoding, then once -// decoding is returning you data, call stb_vorbis_get_sample_offset, and -// if you don't like the result, seek your file again and repeat. -#endif - - -////////// PULLING INPUT API - -#ifndef STB_VORBIS_NO_PULLDATA_API -// This API assumes stb_vorbis is allowed to pull data from a source-- -// either a block of memory containing the _entire_ vorbis stream, or a -// FILE * that you or it create, or possibly some other reading mechanism -// if you go modify the source to replace the FILE * case with some kind -// of callback to your code. (But if you don't support seeking, you may -// just want to go ahead and use pushdata.) - -#if !defined(STB_VORBIS_NO_STDIO) && !defined(STB_VORBIS_NO_INTEGER_CONVERSION) -extern int stb_vorbis_decode_filename(const char *filename, int *channels, int *sample_rate, short **output); -#endif -#if !defined(STB_VORBIS_NO_INTEGER_CONVERSION) -extern int stb_vorbis_decode_memory(const unsigned char *mem, int len, int *channels, int *sample_rate, short **output); -#endif -// decode an entire file and output the data interleaved into a malloc()ed -// buffer stored in *output. The return value is the number of samples -// decoded, or -1 if the file could not be opened or was not an ogg vorbis file. -// When you're done with it, just free() the pointer returned in *output. - -extern stb_vorbis * stb_vorbis_open_memory(const unsigned char *data, int len, - int *error, const stb_vorbis_alloc *alloc_buffer); -// create an ogg vorbis decoder from an ogg vorbis stream in memory (note -// this must be the entire stream!). on failure, returns NULL and sets *error - -#ifndef STB_VORBIS_NO_STDIO -extern stb_vorbis * stb_vorbis_open_filename(const char *filename, - int *error, const stb_vorbis_alloc *alloc_buffer); -// create an ogg vorbis decoder from a filename via fopen(). on failure, -// returns NULL and sets *error (possibly to VORBIS_file_open_failure). - -extern stb_vorbis * stb_vorbis_open_file(FILE *f, int close_handle_on_close, - int *error, const stb_vorbis_alloc *alloc_buffer); -// create an ogg vorbis decoder from an open FILE *, looking for a stream at -// the _current_ seek point (ftell). on failure, returns NULL and sets *error. -// note that stb_vorbis must "own" this stream; if you seek it in between -// calls to stb_vorbis, it will become confused. Moreover, if you attempt to -// perform stb_vorbis_seek_*() operations on this file, it will assume it -// owns the _entire_ rest of the file after the start point. Use the next -// function, stb_vorbis_open_file_section(), to limit it. - -extern stb_vorbis * stb_vorbis_open_file_section(FILE *f, int close_handle_on_close, - int *error, const stb_vorbis_alloc *alloc_buffer, unsigned int len); -// create an ogg vorbis decoder from an open FILE *, looking for a stream at -// the _current_ seek point (ftell); the stream will be of length 'len' bytes. -// on failure, returns NULL and sets *error. note that stb_vorbis must "own" -// this stream; if you seek it in between calls to stb_vorbis, it will become -// confused. -#endif - -extern int stb_vorbis_seek_frame(stb_vorbis *f, unsigned int sample_number); -extern int stb_vorbis_seek(stb_vorbis *f, unsigned int sample_number); -// these functions seek in the Vorbis file to (approximately) 'sample_number'. -// after calling seek_frame(), the next call to get_frame_*() will include -// the specified sample. after calling stb_vorbis_seek(), the next call to -// stb_vorbis_get_samples_* will start with the specified sample. If you -// do not need to seek to EXACTLY the target sample when using get_samples_*, -// you can also use seek_frame(). - -extern int stb_vorbis_seek_start(stb_vorbis *f); -// this function is equivalent to stb_vorbis_seek(f,0) - -extern unsigned int stb_vorbis_stream_length_in_samples(stb_vorbis *f); -extern float stb_vorbis_stream_length_in_seconds(stb_vorbis *f); -// these functions return the total length of the vorbis stream - -extern int stb_vorbis_get_frame_float(stb_vorbis *f, int *channels, float ***output); -// decode the next frame and return the number of samples. the number of -// channels returned are stored in *channels (which can be NULL--it is always -// the same as the number of channels reported by get_info). *output will -// contain an array of float* buffers, one per channel. These outputs will -// be overwritten on the next call to stb_vorbis_get_frame_*. -// -// You generally should not intermix calls to stb_vorbis_get_frame_*() -// and stb_vorbis_get_samples_*(), since the latter calls the former. - -#ifndef STB_VORBIS_NO_INTEGER_CONVERSION -extern int stb_vorbis_get_frame_short_interleaved(stb_vorbis *f, int num_c, short *buffer, int num_shorts); -extern int stb_vorbis_get_frame_short (stb_vorbis *f, int num_c, short **buffer, int num_samples); -#endif -// decode the next frame and return the number of *samples* per channel. -// Note that for interleaved data, you pass in the number of shorts (the -// size of your array), but the return value is the number of samples per -// channel, not the total number of samples. -// -// The data is coerced to the number of channels you request according to the -// channel coercion rules (see below). You must pass in the size of your -// buffer(s) so that stb_vorbis will not overwrite the end of the buffer. -// The maximum buffer size needed can be gotten from get_info(); however, -// the Vorbis I specification implies an absolute maximum of 4096 samples -// per channel. - -// Channel coercion rules: -// Let M be the number of channels requested, and N the number of channels present, -// and Cn be the nth channel; let stereo L be the sum of all L and center channels, -// and stereo R be the sum of all R and center channels (channel assignment from the -// vorbis spec). -// M N output -// 1 k sum(Ck) for all k -// 2 * stereo L, stereo R -// k l k > l, the first l channels, then 0s -// k l k <= l, the first k channels -// Note that this is not _good_ surround etc. mixing at all! It's just so -// you get something useful. - -extern int stb_vorbis_get_samples_float_interleaved(stb_vorbis *f, int channels, float *buffer, int num_floats); -extern int stb_vorbis_get_samples_float(stb_vorbis *f, int channels, float **buffer, int num_samples); -// gets num_samples samples, not necessarily on a frame boundary--this requires -// buffering so you have to supply the buffers. DOES NOT APPLY THE COERCION RULES. -// Returns the number of samples stored per channel; it may be less than requested -// at the end of the file. If there are no more samples in the file, returns 0. - -#ifndef STB_VORBIS_NO_INTEGER_CONVERSION -extern int stb_vorbis_get_samples_short_interleaved(stb_vorbis *f, int channels, short *buffer, int num_shorts); -extern int stb_vorbis_get_samples_short(stb_vorbis *f, int channels, short **buffer, int num_samples); -#endif -// gets num_samples samples, not necessarily on a frame boundary--this requires -// buffering so you have to supply the buffers. Applies the coercion rules above -// to produce 'channels' channels. Returns the number of samples stored per channel; -// it may be less than requested at the end of the file. If there are no more -// samples in the file, returns 0. - -#endif - -//////// ERROR CODES - -enum STBVorbisError -{ - VORBIS__no_error, - - VORBIS_need_more_data=1, // not a real error - - VORBIS_invalid_api_mixing, // can't mix API modes - VORBIS_outofmem, // not enough memory - VORBIS_feature_not_supported, // uses floor 0 - VORBIS_too_many_channels, // STB_VORBIS_MAX_CHANNELS is too small - VORBIS_file_open_failure, // fopen() failed - VORBIS_seek_without_length, // can't seek in unknown-length file - - VORBIS_unexpected_eof=10, // file is truncated? - VORBIS_seek_invalid, // seek past EOF - - // decoding errors (corrupt/invalid stream) -- you probably - // don't care about the exact details of these - - // vorbis errors: - VORBIS_invalid_setup=20, - VORBIS_invalid_stream, - - // ogg errors: - VORBIS_missing_capture_pattern=30, - VORBIS_invalid_stream_structure_version, - VORBIS_continued_packet_flag_invalid, - VORBIS_incorrect_stream_serial_number, - VORBIS_invalid_first_page, - VORBIS_bad_packet_type, - VORBIS_cant_find_last_page, - VORBIS_seek_failed, - VORBIS_ogg_skeleton_not_supported -}; - - -#ifdef __cplusplus -} -#endif - -#endif // STB_VORBIS_INCLUDE_STB_VORBIS_H -// -// HEADER ENDS HERE -// -////////////////////////////////////////////////////////////////////////////// - -#ifndef STB_VORBIS_HEADER_ONLY - -// global configuration settings (e.g. set these in the project/makefile), -// or just set them in this file at the top (although ideally the first few -// should be visible when the header file is compiled too, although it's not -// crucial) - -// STB_VORBIS_NO_PUSHDATA_API -// does not compile the code for the various stb_vorbis_*_pushdata() -// functions -// #define STB_VORBIS_NO_PUSHDATA_API - -// STB_VORBIS_NO_PULLDATA_API -// does not compile the code for the non-pushdata APIs -// #define STB_VORBIS_NO_PULLDATA_API - -// STB_VORBIS_NO_STDIO -// does not compile the code for the APIs that use FILE *s internally -// or externally (implied by STB_VORBIS_NO_PULLDATA_API) -// #define STB_VORBIS_NO_STDIO - -// STB_VORBIS_NO_INTEGER_CONVERSION -// does not compile the code for converting audio sample data from -// float to integer (implied by STB_VORBIS_NO_PULLDATA_API) -// #define STB_VORBIS_NO_INTEGER_CONVERSION - -// STB_VORBIS_NO_FAST_SCALED_FLOAT -// does not use a fast float-to-int trick to accelerate float-to-int on -// most platforms which requires endianness be defined correctly. -//#define STB_VORBIS_NO_FAST_SCALED_FLOAT - - -// STB_VORBIS_MAX_CHANNELS [number] -// globally define this to the maximum number of channels you need. -// The spec does not put a restriction on channels except that -// the count is stored in a byte, so 255 is the hard limit. -// Reducing this saves about 16 bytes per value, so using 16 saves -// (255-16)*16 or around 4KB. Plus anything other memory usage -// I forgot to account for. Can probably go as low as 8 (7.1 audio), -// 6 (5.1 audio), or 2 (stereo only). -#ifndef STB_VORBIS_MAX_CHANNELS -#define STB_VORBIS_MAX_CHANNELS 16 // enough for anyone? -#endif - -// STB_VORBIS_PUSHDATA_CRC_COUNT [number] -// after a flush_pushdata(), stb_vorbis begins scanning for the -// next valid page, without backtracking. when it finds something -// that looks like a page, it streams through it and verifies its -// CRC32. Should that validation fail, it keeps scanning. But it's -// possible that _while_ streaming through to check the CRC32 of -// one candidate page, it sees another candidate page. This #define -// determines how many "overlapping" candidate pages it can search -// at once. Note that "real" pages are typically ~4KB to ~8KB, whereas -// garbage pages could be as big as 64KB, but probably average ~16KB. -// So don't hose ourselves by scanning an apparent 64KB page and -// missing a ton of real ones in the interim; so minimum of 2 -#ifndef STB_VORBIS_PUSHDATA_CRC_COUNT -#define STB_VORBIS_PUSHDATA_CRC_COUNT 4 -#endif - -// STB_VORBIS_FAST_HUFFMAN_LENGTH [number] -// sets the log size of the huffman-acceleration table. Maximum -// supported value is 24. with larger numbers, more decodings are O(1), -// but the table size is larger so worse cache missing, so you'll have -// to probe (and try multiple ogg vorbis files) to find the sweet spot. -#ifndef STB_VORBIS_FAST_HUFFMAN_LENGTH -#define STB_VORBIS_FAST_HUFFMAN_LENGTH 10 -#endif - -// STB_VORBIS_FAST_BINARY_LENGTH [number] -// sets the log size of the binary-search acceleration table. this -// is used in similar fashion to the fast-huffman size to set initial -// parameters for the binary search - -// STB_VORBIS_FAST_HUFFMAN_INT -// The fast huffman tables are much more efficient if they can be -// stored as 16-bit results instead of 32-bit results. This restricts -// the codebooks to having only 65535 possible outcomes, though. -// (At least, accelerated by the huffman table.) -#ifndef STB_VORBIS_FAST_HUFFMAN_INT -#define STB_VORBIS_FAST_HUFFMAN_SHORT -#endif - -// STB_VORBIS_NO_HUFFMAN_BINARY_SEARCH -// If the 'fast huffman' search doesn't succeed, then stb_vorbis falls -// back on binary searching for the correct one. This requires storing -// extra tables with the huffman codes in sorted order. Defining this -// symbol trades off space for speed by forcing a linear search in the -// non-fast case, except for "sparse" codebooks. -// #define STB_VORBIS_NO_HUFFMAN_BINARY_SEARCH - -// STB_VORBIS_DIVIDES_IN_RESIDUE -// stb_vorbis precomputes the result of the scalar residue decoding -// that would otherwise require a divide per chunk. you can trade off -// space for time by defining this symbol. -// #define STB_VORBIS_DIVIDES_IN_RESIDUE - -// STB_VORBIS_DIVIDES_IN_CODEBOOK -// vorbis VQ codebooks can be encoded two ways: with every case explicitly -// stored, or with all elements being chosen from a small range of values, -// and all values possible in all elements. By default, stb_vorbis expands -// this latter kind out to look like the former kind for ease of decoding, -// because otherwise an integer divide-per-vector-element is required to -// unpack the index. If you define STB_VORBIS_DIVIDES_IN_CODEBOOK, you can -// trade off storage for speed. -//#define STB_VORBIS_DIVIDES_IN_CODEBOOK - -#ifdef STB_VORBIS_CODEBOOK_SHORTS -#error "STB_VORBIS_CODEBOOK_SHORTS is no longer supported as it produced incorrect results for some input formats" -#endif - -// STB_VORBIS_DIVIDE_TABLE -// this replaces small integer divides in the floor decode loop with -// table lookups. made less than 1% difference, so disabled by default. - -// STB_VORBIS_NO_INLINE_DECODE -// disables the inlining of the scalar codebook fast-huffman decode. -// might save a little codespace; useful for debugging -// #define STB_VORBIS_NO_INLINE_DECODE - -// STB_VORBIS_NO_DEFER_FLOOR -// Normally we only decode the floor without synthesizing the actual -// full curve. We can instead synthesize the curve immediately. This -// requires more memory and is very likely slower, so I don't think -// you'd ever want to do it except for debugging. -// #define STB_VORBIS_NO_DEFER_FLOOR - - - - -////////////////////////////////////////////////////////////////////////////// - -#ifdef STB_VORBIS_NO_PULLDATA_API - #define STB_VORBIS_NO_INTEGER_CONVERSION - #define STB_VORBIS_NO_STDIO -#endif - -#if defined(STB_VORBIS_NO_CRT) && !defined(STB_VORBIS_NO_STDIO) - #define STB_VORBIS_NO_STDIO 1 -#endif - -#ifndef STB_VORBIS_NO_INTEGER_CONVERSION -#ifndef STB_VORBIS_NO_FAST_SCALED_FLOAT - - // only need endianness for fast-float-to-int, which we don't - // use for pushdata - - #ifndef STB_VORBIS_BIG_ENDIAN - #define STB_VORBIS_ENDIAN 0 - #else - #define STB_VORBIS_ENDIAN 1 - #endif - -#endif -#endif - - -#ifndef STB_VORBIS_NO_STDIO -#include <stdio.h> -#endif - -#ifndef STB_VORBIS_NO_CRT - #include <stdlib.h> - #include <string.h> - #include <assert.h> - #include <math.h> - - // find definition of alloca if it's not in stdlib.h: - #if defined(_MSC_VER) || defined(__MINGW32__) - #include <malloc.h> - #endif - #if defined(__linux__) || defined(__linux) || defined(__EMSCRIPTEN__) || defined(__NEWLIB__) - #include <alloca.h> - #endif -#else // STB_VORBIS_NO_CRT - #define NULL 0 - #define malloc(s) 0 - #define free(s) ((void) 0) - #define realloc(s) 0 -#endif // STB_VORBIS_NO_CRT - -#include <limits.h> - -#ifdef __MINGW32__ - // eff you mingw: - // "fixed": - // http://sourceforge.net/p/mingw-w64/mailman/message/32882927/ - // "no that broke the build, reverted, who cares about C": - // http://sourceforge.net/p/mingw-w64/mailman/message/32890381/ - #ifdef __forceinline - #undef __forceinline - #endif - #define __forceinline - #ifndef alloca - #define alloca __builtin_alloca - #endif -#elif !defined(_MSC_VER) - #if __GNUC__ - #define __forceinline inline - #else - #define __forceinline - #endif -#endif - -#if STB_VORBIS_MAX_CHANNELS > 256 -#error "Value of STB_VORBIS_MAX_CHANNELS outside of allowed range" -#endif - -#if STB_VORBIS_FAST_HUFFMAN_LENGTH > 24 -#error "Value of STB_VORBIS_FAST_HUFFMAN_LENGTH outside of allowed range" -#endif - - -#if 0 -#include <crtdbg.h> -#define CHECK(f) _CrtIsValidHeapPointer(f->channel_buffers[1]) -#else -#define CHECK(f) ((void) 0) -#endif - -#define MAX_BLOCKSIZE_LOG 13 // from specification -#define MAX_BLOCKSIZE (1 << MAX_BLOCKSIZE_LOG) - - -typedef unsigned char uint8; -typedef signed char int8; -typedef unsigned short uint16; -typedef signed short int16; -typedef unsigned int uint32; -typedef signed int int32; - -#ifndef TRUE -#define TRUE 1 -#define FALSE 0 -#endif - -typedef float codetype; - -// @NOTE -// -// Some arrays below are tagged "//varies", which means it's actually -// a variable-sized piece of data, but rather than malloc I assume it's -// small enough it's better to just allocate it all together with the -// main thing -// -// Most of the variables are specified with the smallest size I could pack -// them into. It might give better performance to make them all full-sized -// integers. It should be safe to freely rearrange the structures or change -// the sizes larger--nothing relies on silently truncating etc., nor the -// order of variables. - -#define FAST_HUFFMAN_TABLE_SIZE (1 << STB_VORBIS_FAST_HUFFMAN_LENGTH) -#define FAST_HUFFMAN_TABLE_MASK (FAST_HUFFMAN_TABLE_SIZE - 1) - -typedef struct -{ - int dimensions, entries; - uint8 *codeword_lengths; - float minimum_value; - float delta_value; - uint8 value_bits; - uint8 lookup_type; - uint8 sequence_p; - uint8 sparse; - uint32 lookup_values; - codetype *multiplicands; - uint32 *codewords; - #ifdef STB_VORBIS_FAST_HUFFMAN_SHORT - int16 fast_huffman[FAST_HUFFMAN_TABLE_SIZE]; - #else - int32 fast_huffman[FAST_HUFFMAN_TABLE_SIZE]; - #endif - uint32 *sorted_codewords; - int *sorted_values; - int sorted_entries; -} Codebook; - -typedef struct -{ - uint8 order; - uint16 rate; - uint16 bark_map_size; - uint8 amplitude_bits; - uint8 amplitude_offset; - uint8 number_of_books; - uint8 book_list[16]; // varies -} Floor0; - -typedef struct -{ - uint8 partitions; - uint8 partition_class_list[32]; // varies - uint8 class_dimensions[16]; // varies - uint8 class_subclasses[16]; // varies - uint8 class_masterbooks[16]; // varies - int16 subclass_books[16][8]; // varies - uint16 Xlist[31*8+2]; // varies - uint8 sorted_order[31*8+2]; - uint8 neighbors[31*8+2][2]; - uint8 floor1_multiplier; - uint8 rangebits; - int values; -} Floor1; - -typedef union -{ - Floor0 floor0; - Floor1 floor1; -} Floor; - -typedef struct -{ - uint32 begin, end; - uint32 part_size; - uint8 classifications; - uint8 classbook; - uint8 **classdata; - int16 (*residue_books)[8]; -} Residue; - -typedef struct -{ - uint8 magnitude; - uint8 angle; - uint8 mux; -} MappingChannel; - -typedef struct -{ - uint16 coupling_steps; - MappingChannel *chan; - uint8 submaps; - uint8 submap_floor[15]; // varies - uint8 submap_residue[15]; // varies -} Mapping; - -typedef struct -{ - uint8 blockflag; - uint8 mapping; - uint16 windowtype; - uint16 transformtype; -} Mode; - -typedef struct -{ - uint32 goal_crc; // expected crc if match - int bytes_left; // bytes left in packet - uint32 crc_so_far; // running crc - int bytes_done; // bytes processed in _current_ chunk - uint32 sample_loc; // granule pos encoded in page -} CRCscan; - -typedef struct -{ - uint32 page_start, page_end; - uint32 last_decoded_sample; -} ProbedPage; - -struct stb_vorbis -{ - // user-accessible info - unsigned int sample_rate; - int channels; - - unsigned int setup_memory_required; - unsigned int temp_memory_required; - unsigned int setup_temp_memory_required; - - char *vendor; - int comment_list_length; - char **comment_list; - - // input config -#ifndef STB_VORBIS_NO_STDIO - FILE *f; - uint32 f_start; - int close_on_free; -#endif - - uint8 *stream; - uint8 *stream_start; - uint8 *stream_end; - - uint32 stream_len; - - uint8 push_mode; - - // the page to seek to when seeking to start, may be zero - uint32 first_audio_page_offset; - - // p_first is the page on which the first audio packet ends - // (but not necessarily the page on which it starts) - ProbedPage p_first, p_last; - - // memory management - stb_vorbis_alloc alloc; - int setup_offset; - int temp_offset; - - // run-time results - int eof; - enum STBVorbisError error; - - // user-useful data - - // header info - int blocksize[2]; - int blocksize_0, blocksize_1; - int codebook_count; - Codebook *codebooks; - int floor_count; - uint16 floor_types[64]; // varies - Floor *floor_config; - int residue_count; - uint16 residue_types[64]; // varies - Residue *residue_config; - int mapping_count; - Mapping *mapping; - int mode_count; - Mode mode_config[64]; // varies - - uint32 total_samples; - - // decode buffer - float *channel_buffers[STB_VORBIS_MAX_CHANNELS]; - float *outputs [STB_VORBIS_MAX_CHANNELS]; - - float *previous_window[STB_VORBIS_MAX_CHANNELS]; - int previous_length; - - #ifndef STB_VORBIS_NO_DEFER_FLOOR - int16 *finalY[STB_VORBIS_MAX_CHANNELS]; - #else - float *floor_buffers[STB_VORBIS_MAX_CHANNELS]; - #endif - - uint32 current_loc; // sample location of next frame to decode - int current_loc_valid; - - // per-blocksize precomputed data - - // twiddle factors - float *A[2],*B[2],*C[2]; - float *window[2]; - uint16 *bit_reverse[2]; - - // current page/packet/segment streaming info - uint32 serial; // stream serial number for verification - int last_page; - int segment_count; - uint8 segments[255]; - uint8 page_flag; - uint8 bytes_in_seg; - uint8 first_decode; - int next_seg; - int last_seg; // flag that we're on the last segment - int last_seg_which; // what was the segment number of the last seg? - uint32 acc; - int valid_bits; - int packet_bytes; - int end_seg_with_known_loc; - uint32 known_loc_for_packet; - int discard_samples_deferred; - uint32 samples_output; - - // push mode scanning - int page_crc_tests; // only in push_mode: number of tests active; -1 if not searching -#ifndef STB_VORBIS_NO_PUSHDATA_API - CRCscan scan[STB_VORBIS_PUSHDATA_CRC_COUNT]; -#endif - - // sample-access - int channel_buffer_start; - int channel_buffer_end; -}; - -#if defined(STB_VORBIS_NO_PUSHDATA_API) - #define IS_PUSH_MODE(f) FALSE -#elif defined(STB_VORBIS_NO_PULLDATA_API) - #define IS_PUSH_MODE(f) TRUE -#else - #define IS_PUSH_MODE(f) ((f)->push_mode) -#endif - -typedef struct stb_vorbis vorb; - -static int error(vorb *f, enum STBVorbisError e) -{ - f->error = e; - if (!f->eof && e != VORBIS_need_more_data) { - f->error=e; // breakpoint for debugging - } - return 0; -} - - -// these functions are used for allocating temporary memory -// while decoding. if you can afford the stack space, use -// alloca(); otherwise, provide a temp buffer and it will -// allocate out of those. - -#define array_size_required(count,size) (count*(sizeof(void *)+(size))) - -#define temp_alloc(f,size) (f->alloc.alloc_buffer ? setup_temp_malloc(f,size) : alloca(size)) -#define temp_free(f,p) (void)0 -#define temp_alloc_save(f) ((f)->temp_offset) -#define temp_alloc_restore(f,p) ((f)->temp_offset = (p)) - -#define temp_block_array(f,count,size) make_block_array(temp_alloc(f,array_size_required(count,size)), count, size) - -// given a sufficiently large block of memory, make an array of pointers to subblocks of it -static void *make_block_array(void *mem, int count, int size) -{ - int i; - void ** p = (void **) mem; - char *q = (char *) (p + count); - for (i=0; i < count; ++i) { - p[i] = q; - q += size; - } - return p; -} - -static void *setup_malloc(vorb *f, int sz) -{ - sz = (sz+7) & ~7; // round up to nearest 8 for alignment of future allocs. - f->setup_memory_required += sz; - if (f->alloc.alloc_buffer) { - void *p = (char *) f->alloc.alloc_buffer + f->setup_offset; - if (f->setup_offset + sz > f->temp_offset) return NULL; - f->setup_offset += sz; - return p; - } - return sz ? malloc(sz) : NULL; -} - -static void setup_free(vorb *f, void *p) -{ - if (f->alloc.alloc_buffer) return; // do nothing; setup mem is a stack - free(p); -} - -static void *setup_temp_malloc(vorb *f, int sz) -{ - sz = (sz+7) & ~7; // round up to nearest 8 for alignment of future allocs. - if (f->alloc.alloc_buffer) { - if (f->temp_offset - sz < f->setup_offset) return NULL; - f->temp_offset -= sz; - return (char *) f->alloc.alloc_buffer + f->temp_offset; - } - return malloc(sz); -} - -static void setup_temp_free(vorb *f, void *p, int sz) -{ - if (f->alloc.alloc_buffer) { - f->temp_offset += (sz+7)&~7; - return; - } - free(p); -} - -#define CRC32_POLY 0x04c11db7 // from spec - -static uint32 crc_table[256]; -static void crc32_init(void) -{ - int i,j; - uint32 s; - for(i=0; i < 256; i++) { - for (s=(uint32) i << 24, j=0; j < 8; ++j) - s = (s << 1) ^ (s >= (1U<<31) ? CRC32_POLY : 0); - crc_table[i] = s; - } -} - -static __forceinline uint32 crc32_update(uint32 crc, uint8 byte) -{ - return (crc << 8) ^ crc_table[byte ^ (crc >> 24)]; -} - - -// used in setup, and for huffman that doesn't go fast path -static unsigned int bit_reverse(unsigned int n) -{ - n = ((n & 0xAAAAAAAA) >> 1) | ((n & 0x55555555) << 1); - n = ((n & 0xCCCCCCCC) >> 2) | ((n & 0x33333333) << 2); - n = ((n & 0xF0F0F0F0) >> 4) | ((n & 0x0F0F0F0F) << 4); - n = ((n & 0xFF00FF00) >> 8) | ((n & 0x00FF00FF) << 8); - return (n >> 16) | (n << 16); -} - -static float square(float x) -{ - return x*x; -} - -// this is a weird definition of log2() for which log2(1) = 1, log2(2) = 2, log2(4) = 3 -// as required by the specification. fast(?) implementation from stb.h -// @OPTIMIZE: called multiple times per-packet with "constants"; move to setup -static int ilog(int32 n) -{ - static signed char log2_4[16] = { 0,1,2,2,3,3,3,3,4,4,4,4,4,4,4,4 }; - - if (n < 0) return 0; // signed n returns 0 - - // 2 compares if n < 16, 3 compares otherwise (4 if signed or n > 1<<29) - if (n < (1 << 14)) - if (n < (1 << 4)) return 0 + log2_4[n ]; - else if (n < (1 << 9)) return 5 + log2_4[n >> 5]; - else return 10 + log2_4[n >> 10]; - else if (n < (1 << 24)) - if (n < (1 << 19)) return 15 + log2_4[n >> 15]; - else return 20 + log2_4[n >> 20]; - else if (n < (1 << 29)) return 25 + log2_4[n >> 25]; - else return 30 + log2_4[n >> 30]; -} - -#ifndef M_PI - #define M_PI 3.14159265358979323846264f // from CRC -#endif - -// code length assigned to a value with no huffman encoding -#define NO_CODE 255 - -/////////////////////// LEAF SETUP FUNCTIONS ////////////////////////// -// -// these functions are only called at setup, and only a few times -// per file - -static float float32_unpack(uint32 x) -{ - // from the specification - uint32 mantissa = x & 0x1fffff; - uint32 sign = x & 0x80000000; - uint32 exp = (x & 0x7fe00000) >> 21; - double res = sign ? -(double)mantissa : (double)mantissa; - return (float) ldexp((float)res, exp-788); -} - - -// zlib & jpeg huffman tables assume that the output symbols -// can either be arbitrarily arranged, or have monotonically -// increasing frequencies--they rely on the lengths being sorted; -// this makes for a very simple generation algorithm. -// vorbis allows a huffman table with non-sorted lengths. This -// requires a more sophisticated construction, since symbols in -// order do not map to huffman codes "in order". -static void add_entry(Codebook *c, uint32 huff_code, int symbol, int count, int len, uint32 *values) -{ - if (!c->sparse) { - c->codewords [symbol] = huff_code; - } else { - c->codewords [count] = huff_code; - c->codeword_lengths[count] = len; - values [count] = symbol; - } -} - -static int compute_codewords(Codebook *c, uint8 *len, int n, uint32 *values) -{ - int i,k,m=0; - uint32 available[32]; - - memset(available, 0, sizeof(available)); - // find the first entry - for (k=0; k < n; ++k) if (len[k] < NO_CODE) break; - if (k == n) { assert(c->sorted_entries == 0); return TRUE; } - // add to the list - add_entry(c, 0, k, m++, len[k], values); - // add all available leaves - for (i=1; i <= len[k]; ++i) - available[i] = 1U << (32-i); - // note that the above code treats the first case specially, - // but it's really the same as the following code, so they - // could probably be combined (except the initial code is 0, - // and I use 0 in available[] to mean 'empty') - for (i=k+1; i < n; ++i) { - uint32 res; - int z = len[i], y; - if (z == NO_CODE) continue; - // find lowest available leaf (should always be earliest, - // which is what the specification calls for) - // note that this property, and the fact we can never have - // more than one free leaf at a given level, isn't totally - // trivial to prove, but it seems true and the assert never - // fires, so! - while (z > 0 && !available[z]) --z; - if (z == 0) { return FALSE; } - res = available[z]; - assert(z >= 0 && z < 32); - available[z] = 0; - add_entry(c, bit_reverse(res), i, m++, len[i], values); - // propagate availability up the tree - if (z != len[i]) { - assert(len[i] >= 0 && len[i] < 32); - for (y=len[i]; y > z; --y) { - assert(available[y] == 0); - available[y] = res + (1 << (32-y)); - } - } - } - return TRUE; -} - -// accelerated huffman table allows fast O(1) match of all symbols -// of length <= STB_VORBIS_FAST_HUFFMAN_LENGTH -static void compute_accelerated_huffman(Codebook *c) -{ - int i, len; - for (i=0; i < FAST_HUFFMAN_TABLE_SIZE; ++i) - c->fast_huffman[i] = -1; - - len = c->sparse ? c->sorted_entries : c->entries; - #ifdef STB_VORBIS_FAST_HUFFMAN_SHORT - if (len > 32767) len = 32767; // largest possible value we can encode! - #endif - for (i=0; i < len; ++i) { - if (c->codeword_lengths[i] <= STB_VORBIS_FAST_HUFFMAN_LENGTH) { - uint32 z = c->sparse ? bit_reverse(c->sorted_codewords[i]) : c->codewords[i]; - // set table entries for all bit combinations in the higher bits - while (z < FAST_HUFFMAN_TABLE_SIZE) { - c->fast_huffman[z] = i; - z += 1 << c->codeword_lengths[i]; - } - } - } -} - -#ifdef _MSC_VER -#define STBV_CDECL __cdecl -#else -#define STBV_CDECL -#endif - -static int STBV_CDECL uint32_compare(const void *p, const void *q) -{ - uint32 x = * (uint32 *) p; - uint32 y = * (uint32 *) q; - return x < y ? -1 : x > y; -} - -static int include_in_sort(Codebook *c, uint8 len) -{ - if (c->sparse) { assert(len != NO_CODE); return TRUE; } - if (len == NO_CODE) return FALSE; - if (len > STB_VORBIS_FAST_HUFFMAN_LENGTH) return TRUE; - return FALSE; -} - -// if the fast table above doesn't work, we want to binary -// search them... need to reverse the bits -static void compute_sorted_huffman(Codebook *c, uint8 *lengths, uint32 *values) -{ - int i, len; - // build a list of all the entries - // OPTIMIZATION: don't include the short ones, since they'll be caught by FAST_HUFFMAN. - // this is kind of a frivolous optimization--I don't see any performance improvement, - // but it's like 4 extra lines of code, so. - if (!c->sparse) { - int k = 0; - for (i=0; i < c->entries; ++i) - if (include_in_sort(c, lengths[i])) - c->sorted_codewords[k++] = bit_reverse(c->codewords[i]); - assert(k == c->sorted_entries); - } else { - for (i=0; i < c->sorted_entries; ++i) - c->sorted_codewords[i] = bit_reverse(c->codewords[i]); - } - - qsort(c->sorted_codewords, c->sorted_entries, sizeof(c->sorted_codewords[0]), uint32_compare); - c->sorted_codewords[c->sorted_entries] = 0xffffffff; - - len = c->sparse ? c->sorted_entries : c->entries; - // now we need to indicate how they correspond; we could either - // #1: sort a different data structure that says who they correspond to - // #2: for each sorted entry, search the original list to find who corresponds - // #3: for each original entry, find the sorted entry - // #1 requires extra storage, #2 is slow, #3 can use binary search! - for (i=0; i < len; ++i) { - int huff_len = c->sparse ? lengths[values[i]] : lengths[i]; - if (include_in_sort(c,huff_len)) { - uint32 code = bit_reverse(c->codewords[i]); - int x=0, n=c->sorted_entries; - while (n > 1) { - // invariant: sc[x] <= code < sc[x+n] - int m = x + (n >> 1); - if (c->sorted_codewords[m] <= code) { - x = m; - n -= (n>>1); - } else { - n >>= 1; - } - } - assert(c->sorted_codewords[x] == code); - if (c->sparse) { - c->sorted_values[x] = values[i]; - c->codeword_lengths[x] = huff_len; - } else { - c->sorted_values[x] = i; - } - } - } -} - -// only run while parsing the header (3 times) -static int vorbis_validate(uint8 *data) -{ - static uint8 vorbis[6] = { 'v', 'o', 'r', 'b', 'i', 's' }; - return memcmp(data, vorbis, 6) == 0; -} - -// called from setup only, once per code book -// (formula implied by specification) -static int lookup1_values(int entries, int dim) -{ - int r = (int) floor(exp((float) log((float) entries) / dim)); - if ((int) floor(pow((float) r+1, dim)) <= entries) // (int) cast for MinGW warning; - ++r; // floor() to avoid _ftol() when non-CRT - if (pow((float) r+1, dim) <= entries) - return -1; - if ((int) floor(pow((float) r, dim)) > entries) - return -1; - return r; -} - -// called twice per file -static void compute_twiddle_factors(int n, float *A, float *B, float *C) -{ - int n4 = n >> 2, n8 = n >> 3; - int k,k2; - - for (k=k2=0; k < n4; ++k,k2+=2) { - A[k2 ] = (float) cos(4*k*M_PI/n); - A[k2+1] = (float) -sin(4*k*M_PI/n); - B[k2 ] = (float) cos((k2+1)*M_PI/n/2) * 0.5f; - B[k2+1] = (float) sin((k2+1)*M_PI/n/2) * 0.5f; - } - for (k=k2=0; k < n8; ++k,k2+=2) { - C[k2 ] = (float) cos(2*(k2+1)*M_PI/n); - C[k2+1] = (float) -sin(2*(k2+1)*M_PI/n); - } -} - -static void compute_window(int n, float *window) -{ - int n2 = n >> 1, i; - for (i=0; i < n2; ++i) - window[i] = (float) sin(0.5 * M_PI * square((float) sin((i - 0 + 0.5) / n2 * 0.5 * M_PI))); -} - -static void compute_bitreverse(int n, uint16 *rev) -{ - int ld = ilog(n) - 1; // ilog is off-by-one from normal definitions - int i, n8 = n >> 3; - for (i=0; i < n8; ++i) - rev[i] = (bit_reverse(i) >> (32-ld+3)) << 2; -} - -static int init_blocksize(vorb *f, int b, int n) -{ - int n2 = n >> 1, n4 = n >> 2, n8 = n >> 3; - f->A[b] = (float *) setup_malloc(f, sizeof(float) * n2); - f->B[b] = (float *) setup_malloc(f, sizeof(float) * n2); - f->C[b] = (float *) setup_malloc(f, sizeof(float) * n4); - if (!f->A[b] || !f->B[b] || !f->C[b]) return error(f, VORBIS_outofmem); - compute_twiddle_factors(n, f->A[b], f->B[b], f->C[b]); - f->window[b] = (float *) setup_malloc(f, sizeof(float) * n2); - if (!f->window[b]) return error(f, VORBIS_outofmem); - compute_window(n, f->window[b]); - f->bit_reverse[b] = (uint16 *) setup_malloc(f, sizeof(uint16) * n8); - if (!f->bit_reverse[b]) return error(f, VORBIS_outofmem); - compute_bitreverse(n, f->bit_reverse[b]); - return TRUE; -} - -static void neighbors(uint16 *x, int n, int *plow, int *phigh) -{ - int low = -1; - int high = 65536; - int i; - for (i=0; i < n; ++i) { - if (x[i] > low && x[i] < x[n]) { *plow = i; low = x[i]; } - if (x[i] < high && x[i] > x[n]) { *phigh = i; high = x[i]; } - } -} - -// this has been repurposed so y is now the original index instead of y -typedef struct -{ - uint16 x,id; -} stbv__floor_ordering; - -static int STBV_CDECL point_compare(const void *p, const void *q) -{ - stbv__floor_ordering *a = (stbv__floor_ordering *) p; - stbv__floor_ordering *b = (stbv__floor_ordering *) q; - return a->x < b->x ? -1 : a->x > b->x; -} - -// -/////////////////////// END LEAF SETUP FUNCTIONS ////////////////////////// - - -#if defined(STB_VORBIS_NO_STDIO) - #define USE_MEMORY(z) TRUE -#else - #define USE_MEMORY(z) ((z)->stream) -#endif - -static uint8 get8(vorb *z) -{ - if (USE_MEMORY(z)) { - if (z->stream >= z->stream_end) { z->eof = TRUE; return 0; } - return *z->stream++; - } - - #ifndef STB_VORBIS_NO_STDIO - { - int c = fgetc(z->f); - if (c == EOF) { z->eof = TRUE; return 0; } - return c; - } - #endif -} - -static uint32 get32(vorb *f) -{ - uint32 x; - x = get8(f); - x += get8(f) << 8; - x += get8(f) << 16; - x += (uint32) get8(f) << 24; - return x; -} - -static int getn(vorb *z, uint8 *data, int n) -{ - if (USE_MEMORY(z)) { - if (z->stream+n > z->stream_end) { z->eof = 1; return 0; } - memcpy(data, z->stream, n); - z->stream += n; - return 1; - } - - #ifndef STB_VORBIS_NO_STDIO - if (fread(data, n, 1, z->f) == 1) - return 1; - else { - z->eof = 1; - return 0; - } - #endif -} - -static void skip(vorb *z, int n) -{ - if (USE_MEMORY(z)) { - z->stream += n; - if (z->stream >= z->stream_end) z->eof = 1; - return; - } - #ifndef STB_VORBIS_NO_STDIO - { - long x = ftell(z->f); - fseek(z->f, x+n, SEEK_SET); - } - #endif -} - -static int set_file_offset(stb_vorbis *f, unsigned int loc) -{ - #ifndef STB_VORBIS_NO_PUSHDATA_API - if (f->push_mode) return 0; - #endif - f->eof = 0; - if (USE_MEMORY(f)) { - if (f->stream_start + loc >= f->stream_end || f->stream_start + loc < f->stream_start) { - f->stream = f->stream_end; - f->eof = 1; - return 0; - } else { - f->stream = f->stream_start + loc; - return 1; - } - } - #ifndef STB_VORBIS_NO_STDIO - if (loc + f->f_start < loc || loc >= 0x80000000) { - loc = 0x7fffffff; - f->eof = 1; - } else { - loc += f->f_start; - } - if (!fseek(f->f, loc, SEEK_SET)) - return 1; - f->eof = 1; - fseek(f->f, f->f_start, SEEK_END); - return 0; - #endif -} - - -static uint8 ogg_page_header[4] = { 0x4f, 0x67, 0x67, 0x53 }; - -static int capture_pattern(vorb *f) -{ - if (0x4f != get8(f)) return FALSE; - if (0x67 != get8(f)) return FALSE; - if (0x67 != get8(f)) return FALSE; - if (0x53 != get8(f)) return FALSE; - return TRUE; -} - -#define PAGEFLAG_continued_packet 1 -#define PAGEFLAG_first_page 2 -#define PAGEFLAG_last_page 4 - -static int start_page_no_capturepattern(vorb *f) -{ - uint32 loc0,loc1,n; - if (f->first_decode && !IS_PUSH_MODE(f)) { - f->p_first.page_start = stb_vorbis_get_file_offset(f) - 4; - } - // stream structure version - if (0 != get8(f)) return error(f, VORBIS_invalid_stream_structure_version); - // header flag - f->page_flag = get8(f); - // absolute granule position - loc0 = get32(f); - loc1 = get32(f); - // @TODO: validate loc0,loc1 as valid positions? - // stream serial number -- vorbis doesn't interleave, so discard - get32(f); - //if (f->serial != get32(f)) return error(f, VORBIS_incorrect_stream_serial_number); - // page sequence number - n = get32(f); - f->last_page = n; - // CRC32 - get32(f); - // page_segments - f->segment_count = get8(f); - if (!getn(f, f->segments, f->segment_count)) - return error(f, VORBIS_unexpected_eof); - // assume we _don't_ know any the sample position of any segments - f->end_seg_with_known_loc = -2; - if (loc0 != ~0U || loc1 != ~0U) { - int i; - // determine which packet is the last one that will complete - for (i=f->segment_count-1; i >= 0; --i) - if (f->segments[i] < 255) - break; - // 'i' is now the index of the _last_ segment of a packet that ends - if (i >= 0) { - f->end_seg_with_known_loc = i; - f->known_loc_for_packet = loc0; - } - } - if (f->first_decode) { - int i,len; - len = 0; - for (i=0; i < f->segment_count; ++i) - len += f->segments[i]; - len += 27 + f->segment_count; - f->p_first.page_end = f->p_first.page_start + len; - f->p_first.last_decoded_sample = loc0; - } - f->next_seg = 0; - return TRUE; -} - -static int start_page(vorb *f) -{ - if (!capture_pattern(f)) return error(f, VORBIS_missing_capture_pattern); - return start_page_no_capturepattern(f); -} - -static int start_packet(vorb *f) -{ - while (f->next_seg == -1) { - if (!start_page(f)) return FALSE; - if (f->page_flag & PAGEFLAG_continued_packet) - return error(f, VORBIS_continued_packet_flag_invalid); - } - f->last_seg = FALSE; - f->valid_bits = 0; - f->packet_bytes = 0; - f->bytes_in_seg = 0; - // f->next_seg is now valid - return TRUE; -} - -static int maybe_start_packet(vorb *f) -{ - if (f->next_seg == -1) { - int x = get8(f); - if (f->eof) return FALSE; // EOF at page boundary is not an error! - if (0x4f != x ) return error(f, VORBIS_missing_capture_pattern); - if (0x67 != get8(f)) return error(f, VORBIS_missing_capture_pattern); - if (0x67 != get8(f)) return error(f, VORBIS_missing_capture_pattern); - if (0x53 != get8(f)) return error(f, VORBIS_missing_capture_pattern); - if (!start_page_no_capturepattern(f)) return FALSE; - if (f->page_flag & PAGEFLAG_continued_packet) { - // set up enough state that we can read this packet if we want, - // e.g. during recovery - f->last_seg = FALSE; - f->bytes_in_seg = 0; - return error(f, VORBIS_continued_packet_flag_invalid); - } - } - return start_packet(f); -} - -static int next_segment(vorb *f) -{ - int len; - if (f->last_seg) return 0; - if (f->next_seg == -1) { - f->last_seg_which = f->segment_count-1; // in case start_page fails - if (!start_page(f)) { f->last_seg = 1; return 0; } - if (!(f->page_flag & PAGEFLAG_continued_packet)) return error(f, VORBIS_continued_packet_flag_invalid); - } - len = f->segments[f->next_seg++]; - if (len < 255) { - f->last_seg = TRUE; - f->last_seg_which = f->next_seg-1; - } - if (f->next_seg >= f->segment_count) - f->next_seg = -1; - assert(f->bytes_in_seg == 0); - f->bytes_in_seg = len; - return len; -} - -#define EOP (-1) -#define INVALID_BITS (-1) - -static int get8_packet_raw(vorb *f) -{ - if (!f->bytes_in_seg) { // CLANG! - if (f->last_seg) return EOP; - else if (!next_segment(f)) return EOP; - } - assert(f->bytes_in_seg > 0); - --f->bytes_in_seg; - ++f->packet_bytes; - return get8(f); -} - -static int get8_packet(vorb *f) -{ - int x = get8_packet_raw(f); - f->valid_bits = 0; - return x; -} - -static int get32_packet(vorb *f) -{ - uint32 x; - x = get8_packet(f); - x += get8_packet(f) << 8; - x += get8_packet(f) << 16; - x += (uint32) get8_packet(f) << 24; - return x; -} - -static void flush_packet(vorb *f) -{ - while (get8_packet_raw(f) != EOP); -} - -// @OPTIMIZE: this is the secondary bit decoder, so it's probably not as important -// as the huffman decoder? -static uint32 get_bits(vorb *f, int n) -{ - uint32 z; - - if (f->valid_bits < 0) return 0; - if (f->valid_bits < n) { - if (n > 24) { - // the accumulator technique below would not work correctly in this case - z = get_bits(f, 24); - z += get_bits(f, n-24) << 24; - return z; - } - if (f->valid_bits == 0) f->acc = 0; - while (f->valid_bits < n) { - int z = get8_packet_raw(f); - if (z == EOP) { - f->valid_bits = INVALID_BITS; - return 0; - } - f->acc += z << f->valid_bits; - f->valid_bits += 8; - } - } - - assert(f->valid_bits >= n); - z = f->acc & ((1 << n)-1); - f->acc >>= n; - f->valid_bits -= n; - return z; -} - -// @OPTIMIZE: primary accumulator for huffman -// expand the buffer to as many bits as possible without reading off end of packet -// it might be nice to allow f->valid_bits and f->acc to be stored in registers, -// e.g. cache them locally and decode locally -static __forceinline void prep_huffman(vorb *f) -{ - if (f->valid_bits <= 24) { - if (f->valid_bits == 0) f->acc = 0; - do { - int z; - if (f->last_seg && !f->bytes_in_seg) return; - z = get8_packet_raw(f); - if (z == EOP) return; - f->acc += (unsigned) z << f->valid_bits; - f->valid_bits += 8; - } while (f->valid_bits <= 24); - } -} - -enum -{ - VORBIS_packet_id = 1, - VORBIS_packet_comment = 3, - VORBIS_packet_setup = 5 -}; - -static int codebook_decode_scalar_raw(vorb *f, Codebook *c) -{ - int i; - prep_huffman(f); - - if (c->codewords == NULL && c->sorted_codewords == NULL) - return -1; - - // cases to use binary search: sorted_codewords && !c->codewords - // sorted_codewords && c->entries > 8 - if (c->entries > 8 ? c->sorted_codewords!=NULL : !c->codewords) { - // binary search - uint32 code = bit_reverse(f->acc); - int x=0, n=c->sorted_entries, len; - - while (n > 1) { - // invariant: sc[x] <= code < sc[x+n] - int m = x + (n >> 1); - if (c->sorted_codewords[m] <= code) { - x = m; - n -= (n>>1); - } else { - n >>= 1; - } - } - // x is now the sorted index - if (!c->sparse) x = c->sorted_values[x]; - // x is now sorted index if sparse, or symbol otherwise - len = c->codeword_lengths[x]; - if (f->valid_bits >= len) { - f->acc >>= len; - f->valid_bits -= len; - return x; - } - - f->valid_bits = 0; - return -1; - } - - // if small, linear search - assert(!c->sparse); - for (i=0; i < c->entries; ++i) { - if (c->codeword_lengths[i] == NO_CODE) continue; - if (c->codewords[i] == (f->acc & ((1 << c->codeword_lengths[i])-1))) { - if (f->valid_bits >= c->codeword_lengths[i]) { - f->acc >>= c->codeword_lengths[i]; - f->valid_bits -= c->codeword_lengths[i]; - return i; - } - f->valid_bits = 0; - return -1; - } - } - - error(f, VORBIS_invalid_stream); - f->valid_bits = 0; - return -1; -} - -#ifndef STB_VORBIS_NO_INLINE_DECODE - -#define DECODE_RAW(var, f,c) \ - if (f->valid_bits < STB_VORBIS_FAST_HUFFMAN_LENGTH) \ - prep_huffman(f); \ - var = f->acc & FAST_HUFFMAN_TABLE_MASK; \ - var = c->fast_huffman[var]; \ - if (var >= 0) { \ - int n = c->codeword_lengths[var]; \ - f->acc >>= n; \ - f->valid_bits -= n; \ - if (f->valid_bits < 0) { f->valid_bits = 0; var = -1; } \ - } else { \ - var = codebook_decode_scalar_raw(f,c); \ - } - -#else - -static int codebook_decode_scalar(vorb *f, Codebook *c) -{ - int i; - if (f->valid_bits < STB_VORBIS_FAST_HUFFMAN_LENGTH) - prep_huffman(f); - // fast huffman table lookup - i = f->acc & FAST_HUFFMAN_TABLE_MASK; - i = c->fast_huffman[i]; - if (i >= 0) { - f->acc >>= c->codeword_lengths[i]; - f->valid_bits -= c->codeword_lengths[i]; - if (f->valid_bits < 0) { f->valid_bits = 0; return -1; } - return i; - } - return codebook_decode_scalar_raw(f,c); -} - -#define DECODE_RAW(var,f,c) var = codebook_decode_scalar(f,c); - -#endif - -#define DECODE(var,f,c) \ - DECODE_RAW(var,f,c) \ - if (c->sparse) var = c->sorted_values[var]; - -#ifndef STB_VORBIS_DIVIDES_IN_CODEBOOK - #define DECODE_VQ(var,f,c) DECODE_RAW(var,f,c) -#else - #define DECODE_VQ(var,f,c) DECODE(var,f,c) -#endif - - - - - - -// CODEBOOK_ELEMENT_FAST is an optimization for the CODEBOOK_FLOATS case -// where we avoid one addition -#define CODEBOOK_ELEMENT(c,off) (c->multiplicands[off]) -#define CODEBOOK_ELEMENT_FAST(c,off) (c->multiplicands[off]) -#define CODEBOOK_ELEMENT_BASE(c) (0) - -static int codebook_decode_start(vorb *f, Codebook *c) -{ - int z = -1; - - // type 0 is only legal in a scalar context - if (c->lookup_type == 0) - error(f, VORBIS_invalid_stream); - else { - DECODE_VQ(z,f,c); - if (c->sparse) assert(z < c->sorted_entries); - if (z < 0) { // check for EOP - if (!f->bytes_in_seg) - if (f->last_seg) - return z; - error(f, VORBIS_invalid_stream); - } - } - return z; -} - -static int codebook_decode(vorb *f, Codebook *c, float *output, int len) -{ - int i,z = codebook_decode_start(f,c); - if (z < 0) return FALSE; - if (len > c->dimensions) len = c->dimensions; - -#ifdef STB_VORBIS_DIVIDES_IN_CODEBOOK - if (c->lookup_type == 1) { - float last = CODEBOOK_ELEMENT_BASE(c); - int div = 1; - for (i=0; i < len; ++i) { - int off = (z / div) % c->lookup_values; - float val = CODEBOOK_ELEMENT_FAST(c,off) + last; - output[i] += val; - if (c->sequence_p) last = val + c->minimum_value; - div *= c->lookup_values; - } - return TRUE; - } -#endif - - z *= c->dimensions; - if (c->sequence_p) { - float last = CODEBOOK_ELEMENT_BASE(c); - for (i=0; i < len; ++i) { - float val = CODEBOOK_ELEMENT_FAST(c,z+i) + last; - output[i] += val; - last = val + c->minimum_value; - } - } else { - float last = CODEBOOK_ELEMENT_BASE(c); - for (i=0; i < len; ++i) { - output[i] += CODEBOOK_ELEMENT_FAST(c,z+i) + last; - } - } - - return TRUE; -} - -static int codebook_decode_step(vorb *f, Codebook *c, float *output, int len, int step) -{ - int i,z = codebook_decode_start(f,c); - float last = CODEBOOK_ELEMENT_BASE(c); - if (z < 0) return FALSE; - if (len > c->dimensions) len = c->dimensions; - -#ifdef STB_VORBIS_DIVIDES_IN_CODEBOOK - if (c->lookup_type == 1) { - int div = 1; - for (i=0; i < len; ++i) { - int off = (z / div) % c->lookup_values; - float val = CODEBOOK_ELEMENT_FAST(c,off) + last; - output[i*step] += val; - if (c->sequence_p) last = val; - div *= c->lookup_values; - } - return TRUE; - } -#endif - - z *= c->dimensions; - for (i=0; i < len; ++i) { - float val = CODEBOOK_ELEMENT_FAST(c,z+i) + last; - output[i*step] += val; - if (c->sequence_p) last = val; - } - - return TRUE; -} - -static int codebook_decode_deinterleave_repeat(vorb *f, Codebook *c, float **outputs, int ch, int *c_inter_p, int *p_inter_p, int len, int total_decode) -{ - int c_inter = *c_inter_p; - int p_inter = *p_inter_p; - int i,z, effective = c->dimensions; - - // type 0 is only legal in a scalar context - if (c->lookup_type == 0) return error(f, VORBIS_invalid_stream); - - while (total_decode > 0) { - float last = CODEBOOK_ELEMENT_BASE(c); - DECODE_VQ(z,f,c); - #ifndef STB_VORBIS_DIVIDES_IN_CODEBOOK - assert(!c->sparse || z < c->sorted_entries); - #endif - if (z < 0) { - if (!f->bytes_in_seg) - if (f->last_seg) return FALSE; - return error(f, VORBIS_invalid_stream); - } - - // if this will take us off the end of the buffers, stop short! - // we check by computing the length of the virtual interleaved - // buffer (len*ch), our current offset within it (p_inter*ch)+(c_inter), - // and the length we'll be using (effective) - if (c_inter + p_inter*ch + effective > len * ch) { - effective = len*ch - (p_inter*ch - c_inter); - } - - #ifdef STB_VORBIS_DIVIDES_IN_CODEBOOK - if (c->lookup_type == 1) { - int div = 1; - for (i=0; i < effective; ++i) { - int off = (z / div) % c->lookup_values; - float val = CODEBOOK_ELEMENT_FAST(c,off) + last; - if (outputs[c_inter]) - outputs[c_inter][p_inter] += val; - if (++c_inter == ch) { c_inter = 0; ++p_inter; } - if (c->sequence_p) last = val; - div *= c->lookup_values; - } - } else - #endif - { - z *= c->dimensions; - if (c->sequence_p) { - for (i=0; i < effective; ++i) { - float val = CODEBOOK_ELEMENT_FAST(c,z+i) + last; - if (outputs[c_inter]) - outputs[c_inter][p_inter] += val; - if (++c_inter == ch) { c_inter = 0; ++p_inter; } - last = val; - } - } else { - for (i=0; i < effective; ++i) { - float val = CODEBOOK_ELEMENT_FAST(c,z+i) + last; - if (outputs[c_inter]) - outputs[c_inter][p_inter] += val; - if (++c_inter == ch) { c_inter = 0; ++p_inter; } - } - } - } - - total_decode -= effective; - } - *c_inter_p = c_inter; - *p_inter_p = p_inter; - return TRUE; -} - -static int predict_point(int x, int x0, int x1, int y0, int y1) -{ - int dy = y1 - y0; - int adx = x1 - x0; - // @OPTIMIZE: force int division to round in the right direction... is this necessary on x86? - int err = abs(dy) * (x - x0); - int off = err / adx; - return dy < 0 ? y0 - off : y0 + off; -} - -// the following table is block-copied from the specification -static float inverse_db_table[256] = -{ - 1.0649863e-07f, 1.1341951e-07f, 1.2079015e-07f, 1.2863978e-07f, - 1.3699951e-07f, 1.4590251e-07f, 1.5538408e-07f, 1.6548181e-07f, - 1.7623575e-07f, 1.8768855e-07f, 1.9988561e-07f, 2.1287530e-07f, - 2.2670913e-07f, 2.4144197e-07f, 2.5713223e-07f, 2.7384213e-07f, - 2.9163793e-07f, 3.1059021e-07f, 3.3077411e-07f, 3.5226968e-07f, - 3.7516214e-07f, 3.9954229e-07f, 4.2550680e-07f, 4.5315863e-07f, - 4.8260743e-07f, 5.1396998e-07f, 5.4737065e-07f, 5.8294187e-07f, - 6.2082472e-07f, 6.6116941e-07f, 7.0413592e-07f, 7.4989464e-07f, - 7.9862701e-07f, 8.5052630e-07f, 9.0579828e-07f, 9.6466216e-07f, - 1.0273513e-06f, 1.0941144e-06f, 1.1652161e-06f, 1.2409384e-06f, - 1.3215816e-06f, 1.4074654e-06f, 1.4989305e-06f, 1.5963394e-06f, - 1.7000785e-06f, 1.8105592e-06f, 1.9282195e-06f, 2.0535261e-06f, - 2.1869758e-06f, 2.3290978e-06f, 2.4804557e-06f, 2.6416497e-06f, - 2.8133190e-06f, 2.9961443e-06f, 3.1908506e-06f, 3.3982101e-06f, - 3.6190449e-06f, 3.8542308e-06f, 4.1047004e-06f, 4.3714470e-06f, - 4.6555282e-06f, 4.9580707e-06f, 5.2802740e-06f, 5.6234160e-06f, - 5.9888572e-06f, 6.3780469e-06f, 6.7925283e-06f, 7.2339451e-06f, - 7.7040476e-06f, 8.2047000e-06f, 8.7378876e-06f, 9.3057248e-06f, - 9.9104632e-06f, 1.0554501e-05f, 1.1240392e-05f, 1.1970856e-05f, - 1.2748789e-05f, 1.3577278e-05f, 1.4459606e-05f, 1.5399272e-05f, - 1.6400004e-05f, 1.7465768e-05f, 1.8600792e-05f, 1.9809576e-05f, - 2.1096914e-05f, 2.2467911e-05f, 2.3928002e-05f, 2.5482978e-05f, - 2.7139006e-05f, 2.8902651e-05f, 3.0780908e-05f, 3.2781225e-05f, - 3.4911534e-05f, 3.7180282e-05f, 3.9596466e-05f, 4.2169667e-05f, - 4.4910090e-05f, 4.7828601e-05f, 5.0936773e-05f, 5.4246931e-05f, - 5.7772202e-05f, 6.1526565e-05f, 6.5524908e-05f, 6.9783085e-05f, - 7.4317983e-05f, 7.9147585e-05f, 8.4291040e-05f, 8.9768747e-05f, - 9.5602426e-05f, 0.00010181521f, 0.00010843174f, 0.00011547824f, - 0.00012298267f, 0.00013097477f, 0.00013948625f, 0.00014855085f, - 0.00015820453f, 0.00016848555f, 0.00017943469f, 0.00019109536f, - 0.00020351382f, 0.00021673929f, 0.00023082423f, 0.00024582449f, - 0.00026179955f, 0.00027881276f, 0.00029693158f, 0.00031622787f, - 0.00033677814f, 0.00035866388f, 0.00038197188f, 0.00040679456f, - 0.00043323036f, 0.00046138411f, 0.00049136745f, 0.00052329927f, - 0.00055730621f, 0.00059352311f, 0.00063209358f, 0.00067317058f, - 0.00071691700f, 0.00076350630f, 0.00081312324f, 0.00086596457f, - 0.00092223983f, 0.00098217216f, 0.0010459992f, 0.0011139742f, - 0.0011863665f, 0.0012634633f, 0.0013455702f, 0.0014330129f, - 0.0015261382f, 0.0016253153f, 0.0017309374f, 0.0018434235f, - 0.0019632195f, 0.0020908006f, 0.0022266726f, 0.0023713743f, - 0.0025254795f, 0.0026895994f, 0.0028643847f, 0.0030505286f, - 0.0032487691f, 0.0034598925f, 0.0036847358f, 0.0039241906f, - 0.0041792066f, 0.0044507950f, 0.0047400328f, 0.0050480668f, - 0.0053761186f, 0.0057254891f, 0.0060975636f, 0.0064938176f, - 0.0069158225f, 0.0073652516f, 0.0078438871f, 0.0083536271f, - 0.0088964928f, 0.009474637f, 0.010090352f, 0.010746080f, - 0.011444421f, 0.012188144f, 0.012980198f, 0.013823725f, - 0.014722068f, 0.015678791f, 0.016697687f, 0.017782797f, - 0.018938423f, 0.020169149f, 0.021479854f, 0.022875735f, - 0.024362330f, 0.025945531f, 0.027631618f, 0.029427276f, - 0.031339626f, 0.033376252f, 0.035545228f, 0.037855157f, - 0.040315199f, 0.042935108f, 0.045725273f, 0.048696758f, - 0.051861348f, 0.055231591f, 0.058820850f, 0.062643361f, - 0.066714279f, 0.071049749f, 0.075666962f, 0.080584227f, - 0.085821044f, 0.091398179f, 0.097337747f, 0.10366330f, - 0.11039993f, 0.11757434f, 0.12521498f, 0.13335215f, - 0.14201813f, 0.15124727f, 0.16107617f, 0.17154380f, - 0.18269168f, 0.19456402f, 0.20720788f, 0.22067342f, - 0.23501402f, 0.25028656f, 0.26655159f, 0.28387361f, - 0.30232132f, 0.32196786f, 0.34289114f, 0.36517414f, - 0.38890521f, 0.41417847f, 0.44109412f, 0.46975890f, - 0.50028648f, 0.53279791f, 0.56742212f, 0.60429640f, - 0.64356699f, 0.68538959f, 0.72993007f, 0.77736504f, - 0.82788260f, 0.88168307f, 0.9389798f, 1.0f -}; - - -// @OPTIMIZE: if you want to replace this bresenham line-drawing routine, -// note that you must produce bit-identical output to decode correctly; -// this specific sequence of operations is specified in the spec (it's -// drawing integer-quantized frequency-space lines that the encoder -// expects to be exactly the same) -// ... also, isn't the whole point of Bresenham's algorithm to NOT -// have to divide in the setup? sigh. -#ifndef STB_VORBIS_NO_DEFER_FLOOR -#define LINE_OP(a,b) a *= b -#else -#define LINE_OP(a,b) a = b -#endif - -#ifdef STB_VORBIS_DIVIDE_TABLE -#define DIVTAB_NUMER 32 -#define DIVTAB_DENOM 64 -int8 integer_divide_table[DIVTAB_NUMER][DIVTAB_DENOM]; // 2KB -#endif - -static __forceinline void draw_line(float *output, int x0, int y0, int x1, int y1, int n) -{ - int dy = y1 - y0; - int adx = x1 - x0; - int ady = abs(dy); - int base; - int x=x0,y=y0; - int err = 0; - int sy; - -#ifdef STB_VORBIS_DIVIDE_TABLE - if (adx < DIVTAB_DENOM && ady < DIVTAB_NUMER) { - if (dy < 0) { - base = -integer_divide_table[ady][adx]; - sy = base-1; - } else { - base = integer_divide_table[ady][adx]; - sy = base+1; - } - } else { - base = dy / adx; - if (dy < 0) - sy = base - 1; - else - sy = base+1; - } -#else - base = dy / adx; - if (dy < 0) - sy = base - 1; - else - sy = base+1; -#endif - ady -= abs(base) * adx; - if (x1 > n) x1 = n; - if (x < x1) { - LINE_OP(output[x], inverse_db_table[y&255]); - for (++x; x < x1; ++x) { - err += ady; - if (err >= adx) { - err -= adx; - y += sy; - } else - y += base; - LINE_OP(output[x], inverse_db_table[y&255]); - } - } -} - -static int residue_decode(vorb *f, Codebook *book, float *target, int offset, int n, int rtype) -{ - int k; - if (rtype == 0) { - int step = n / book->dimensions; - for (k=0; k < step; ++k) - if (!codebook_decode_step(f, book, target+offset+k, n-offset-k, step)) - return FALSE; - } else { - for (k=0; k < n; ) { - if (!codebook_decode(f, book, target+offset, n-k)) - return FALSE; - k += book->dimensions; - offset += book->dimensions; - } - } - return TRUE; -} - -// n is 1/2 of the blocksize -- -// specification: "Correct per-vector decode length is [n]/2" -static void decode_residue(vorb *f, float *residue_buffers[], int ch, int n, int rn, uint8 *do_not_decode) -{ - int i,j,pass; - Residue *r = f->residue_config + rn; - int rtype = f->residue_types[rn]; - int c = r->classbook; - int classwords = f->codebooks[c].dimensions; - unsigned int actual_size = rtype == 2 ? n*2 : n; - unsigned int limit_r_begin = (r->begin < actual_size ? r->begin : actual_size); - unsigned int limit_r_end = (r->end < actual_size ? r->end : actual_size); - int n_read = limit_r_end - limit_r_begin; - int part_read = n_read / r->part_size; - int temp_alloc_point = temp_alloc_save(f); - #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE - uint8 ***part_classdata = (uint8 ***) temp_block_array(f,f->channels, part_read * sizeof(**part_classdata)); - #else - int **classifications = (int **) temp_block_array(f,f->channels, part_read * sizeof(**classifications)); - #endif - - CHECK(f); - - for (i=0; i < ch; ++i) - if (!do_not_decode[i]) - memset(residue_buffers[i], 0, sizeof(float) * n); - - if (rtype == 2 && ch != 1) { - for (j=0; j < ch; ++j) - if (!do_not_decode[j]) - break; - if (j == ch) - goto done; - - for (pass=0; pass < 8; ++pass) { - int pcount = 0, class_set = 0; - if (ch == 2) { - while (pcount < part_read) { - int z = r->begin + pcount*r->part_size; - int c_inter = (z & 1), p_inter = z>>1; - if (pass == 0) { - Codebook *c = f->codebooks+r->classbook; - int q; - DECODE(q,f,c); - if (q == EOP) goto done; - #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE - part_classdata[0][class_set] = r->classdata[q]; - #else - for (i=classwords-1; i >= 0; --i) { - classifications[0][i+pcount] = q % r->classifications; - q /= r->classifications; - } - #endif - } - for (i=0; i < classwords && pcount < part_read; ++i, ++pcount) { - int z = r->begin + pcount*r->part_size; - #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE - int c = part_classdata[0][class_set][i]; - #else - int c = classifications[0][pcount]; - #endif - int b = r->residue_books[c][pass]; - if (b >= 0) { - Codebook *book = f->codebooks + b; - #ifdef STB_VORBIS_DIVIDES_IN_CODEBOOK - if (!codebook_decode_deinterleave_repeat(f, book, residue_buffers, ch, &c_inter, &p_inter, n, r->part_size)) - goto done; - #else - // saves 1% - if (!codebook_decode_deinterleave_repeat(f, book, residue_buffers, ch, &c_inter, &p_inter, n, r->part_size)) - goto done; - #endif - } else { - z += r->part_size; - c_inter = z & 1; - p_inter = z >> 1; - } - } - #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE - ++class_set; - #endif - } - } else if (ch > 2) { - while (pcount < part_read) { - int z = r->begin + pcount*r->part_size; - int c_inter = z % ch, p_inter = z/ch; - if (pass == 0) { - Codebook *c = f->codebooks+r->classbook; - int q; - DECODE(q,f,c); - if (q == EOP) goto done; - #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE - part_classdata[0][class_set] = r->classdata[q]; - #else - for (i=classwords-1; i >= 0; --i) { - classifications[0][i+pcount] = q % r->classifications; - q /= r->classifications; - } - #endif - } - for (i=0; i < classwords && pcount < part_read; ++i, ++pcount) { - int z = r->begin + pcount*r->part_size; - #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE - int c = part_classdata[0][class_set][i]; - #else - int c = classifications[0][pcount]; - #endif - int b = r->residue_books[c][pass]; - if (b >= 0) { - Codebook *book = f->codebooks + b; - if (!codebook_decode_deinterleave_repeat(f, book, residue_buffers, ch, &c_inter, &p_inter, n, r->part_size)) - goto done; - } else { - z += r->part_size; - c_inter = z % ch; - p_inter = z / ch; - } - } - #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE - ++class_set; - #endif - } - } - } - goto done; - } - CHECK(f); - - for (pass=0; pass < 8; ++pass) { - int pcount = 0, class_set=0; - while (pcount < part_read) { - if (pass == 0) { - for (j=0; j < ch; ++j) { - if (!do_not_decode[j]) { - Codebook *c = f->codebooks+r->classbook; - int temp; - DECODE(temp,f,c); - if (temp == EOP) goto done; - #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE - part_classdata[j][class_set] = r->classdata[temp]; - #else - for (i=classwords-1; i >= 0; --i) { - classifications[j][i+pcount] = temp % r->classifications; - temp /= r->classifications; - } - #endif - } - } - } - for (i=0; i < classwords && pcount < part_read; ++i, ++pcount) { - for (j=0; j < ch; ++j) { - if (!do_not_decode[j]) { - #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE - int c = part_classdata[j][class_set][i]; - #else - int c = classifications[j][pcount]; - #endif - int b = r->residue_books[c][pass]; - if (b >= 0) { - float *target = residue_buffers[j]; - int offset = r->begin + pcount * r->part_size; - int n = r->part_size; - Codebook *book = f->codebooks + b; - if (!residue_decode(f, book, target, offset, n, rtype)) - goto done; - } - } - } - } - #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE - ++class_set; - #endif - } - } - done: - CHECK(f); - #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE - temp_free(f,part_classdata); - #else - temp_free(f,classifications); - #endif - temp_alloc_restore(f,temp_alloc_point); -} - - -#if 0 -// slow way for debugging -void inverse_mdct_slow(float *buffer, int n) -{ - int i,j; - int n2 = n >> 1; - float *x = (float *) malloc(sizeof(*x) * n2); - memcpy(x, buffer, sizeof(*x) * n2); - for (i=0; i < n; ++i) { - float acc = 0; - for (j=0; j < n2; ++j) - // formula from paper: - //acc += n/4.0f * x[j] * (float) cos(M_PI / 2 / n * (2 * i + 1 + n/2.0)*(2*j+1)); - // formula from wikipedia - //acc += 2.0f / n2 * x[j] * (float) cos(M_PI/n2 * (i + 0.5 + n2/2)*(j + 0.5)); - // these are equivalent, except the formula from the paper inverts the multiplier! - // however, what actually works is NO MULTIPLIER!?! - //acc += 64 * 2.0f / n2 * x[j] * (float) cos(M_PI/n2 * (i + 0.5 + n2/2)*(j + 0.5)); - acc += x[j] * (float) cos(M_PI / 2 / n * (2 * i + 1 + n/2.0)*(2*j+1)); - buffer[i] = acc; - } - free(x); -} -#elif 0 -// same as above, but just barely able to run in real time on modern machines -void inverse_mdct_slow(float *buffer, int n, vorb *f, int blocktype) -{ - float mcos[16384]; - int i,j; - int n2 = n >> 1, nmask = (n << 2) -1; - float *x = (float *) malloc(sizeof(*x) * n2); - memcpy(x, buffer, sizeof(*x) * n2); - for (i=0; i < 4*n; ++i) - mcos[i] = (float) cos(M_PI / 2 * i / n); - - for (i=0; i < n; ++i) { - float acc = 0; - for (j=0; j < n2; ++j) - acc += x[j] * mcos[(2 * i + 1 + n2)*(2*j+1) & nmask]; - buffer[i] = acc; - } - free(x); -} -#elif 0 -// transform to use a slow dct-iv; this is STILL basically trivial, -// but only requires half as many ops -void dct_iv_slow(float *buffer, int n) -{ - float mcos[16384]; - float x[2048]; - int i,j; - int n2 = n >> 1, nmask = (n << 3) - 1; - memcpy(x, buffer, sizeof(*x) * n); - for (i=0; i < 8*n; ++i) - mcos[i] = (float) cos(M_PI / 4 * i / n); - for (i=0; i < n; ++i) { - float acc = 0; - for (j=0; j < n; ++j) - acc += x[j] * mcos[((2 * i + 1)*(2*j+1)) & nmask]; - buffer[i] = acc; - } -} - -void inverse_mdct_slow(float *buffer, int n, vorb *f, int blocktype) -{ - int i, n4 = n >> 2, n2 = n >> 1, n3_4 = n - n4; - float temp[4096]; - - memcpy(temp, buffer, n2 * sizeof(float)); - dct_iv_slow(temp, n2); // returns -c'-d, a-b' - - for (i=0; i < n4 ; ++i) buffer[i] = temp[i+n4]; // a-b' - for ( ; i < n3_4; ++i) buffer[i] = -temp[n3_4 - i - 1]; // b-a', c+d' - for ( ; i < n ; ++i) buffer[i] = -temp[i - n3_4]; // c'+d -} -#endif - -#ifndef LIBVORBIS_MDCT -#define LIBVORBIS_MDCT 0 -#endif - -#if LIBVORBIS_MDCT -// directly call the vorbis MDCT using an interface documented -// by Jeff Roberts... useful for performance comparison -typedef struct -{ - int n; - int log2n; - - float *trig; - int *bitrev; - - float scale; -} mdct_lookup; - -extern void mdct_init(mdct_lookup *lookup, int n); -extern void mdct_clear(mdct_lookup *l); -extern void mdct_backward(mdct_lookup *init, float *in, float *out); - -mdct_lookup M1,M2; - -void inverse_mdct(float *buffer, int n, vorb *f, int blocktype) -{ - mdct_lookup *M; - if (M1.n == n) M = &M1; - else if (M2.n == n) M = &M2; - else if (M1.n == 0) { mdct_init(&M1, n); M = &M1; } - else { - if (M2.n) __asm int 3; - mdct_init(&M2, n); - M = &M2; - } - - mdct_backward(M, buffer, buffer); -} -#endif - - -// the following were split out into separate functions while optimizing; -// they could be pushed back up but eh. __forceinline showed no change; -// they're probably already being inlined. -static void imdct_step3_iter0_loop(int n, float *e, int i_off, int k_off, float *A) -{ - float *ee0 = e + i_off; - float *ee2 = ee0 + k_off; - int i; - - assert((n & 3) == 0); - for (i=(n>>2); i > 0; --i) { - float k00_20, k01_21; - k00_20 = ee0[ 0] - ee2[ 0]; - k01_21 = ee0[-1] - ee2[-1]; - ee0[ 0] += ee2[ 0];//ee0[ 0] = ee0[ 0] + ee2[ 0]; - ee0[-1] += ee2[-1];//ee0[-1] = ee0[-1] + ee2[-1]; - ee2[ 0] = k00_20 * A[0] - k01_21 * A[1]; - ee2[-1] = k01_21 * A[0] + k00_20 * A[1]; - A += 8; - - k00_20 = ee0[-2] - ee2[-2]; - k01_21 = ee0[-3] - ee2[-3]; - ee0[-2] += ee2[-2];//ee0[-2] = ee0[-2] + ee2[-2]; - ee0[-3] += ee2[-3];//ee0[-3] = ee0[-3] + ee2[-3]; - ee2[-2] = k00_20 * A[0] - k01_21 * A[1]; - ee2[-3] = k01_21 * A[0] + k00_20 * A[1]; - A += 8; - - k00_20 = ee0[-4] - ee2[-4]; - k01_21 = ee0[-5] - ee2[-5]; - ee0[-4] += ee2[-4];//ee0[-4] = ee0[-4] + ee2[-4]; - ee0[-5] += ee2[-5];//ee0[-5] = ee0[-5] + ee2[-5]; - ee2[-4] = k00_20 * A[0] - k01_21 * A[1]; - ee2[-5] = k01_21 * A[0] + k00_20 * A[1]; - A += 8; - - k00_20 = ee0[-6] - ee2[-6]; - k01_21 = ee0[-7] - ee2[-7]; - ee0[-6] += ee2[-6];//ee0[-6] = ee0[-6] + ee2[-6]; - ee0[-7] += ee2[-7];//ee0[-7] = ee0[-7] + ee2[-7]; - ee2[-6] = k00_20 * A[0] - k01_21 * A[1]; - ee2[-7] = k01_21 * A[0] + k00_20 * A[1]; - A += 8; - ee0 -= 8; - ee2 -= 8; - } -} - -static void imdct_step3_inner_r_loop(int lim, float *e, int d0, int k_off, float *A, int k1) -{ - int i; - float k00_20, k01_21; - - float *e0 = e + d0; - float *e2 = e0 + k_off; - - for (i=lim >> 2; i > 0; --i) { - k00_20 = e0[-0] - e2[-0]; - k01_21 = e0[-1] - e2[-1]; - e0[-0] += e2[-0];//e0[-0] = e0[-0] + e2[-0]; - e0[-1] += e2[-1];//e0[-1] = e0[-1] + e2[-1]; - e2[-0] = (k00_20)*A[0] - (k01_21) * A[1]; - e2[-1] = (k01_21)*A[0] + (k00_20) * A[1]; - - A += k1; - - k00_20 = e0[-2] - e2[-2]; - k01_21 = e0[-3] - e2[-3]; - e0[-2] += e2[-2];//e0[-2] = e0[-2] + e2[-2]; - e0[-3] += e2[-3];//e0[-3] = e0[-3] + e2[-3]; - e2[-2] = (k00_20)*A[0] - (k01_21) * A[1]; - e2[-3] = (k01_21)*A[0] + (k00_20) * A[1]; - - A += k1; - - k00_20 = e0[-4] - e2[-4]; - k01_21 = e0[-5] - e2[-5]; - e0[-4] += e2[-4];//e0[-4] = e0[-4] + e2[-4]; - e0[-5] += e2[-5];//e0[-5] = e0[-5] + e2[-5]; - e2[-4] = (k00_20)*A[0] - (k01_21) * A[1]; - e2[-5] = (k01_21)*A[0] + (k00_20) * A[1]; - - A += k1; - - k00_20 = e0[-6] - e2[-6]; - k01_21 = e0[-7] - e2[-7]; - e0[-6] += e2[-6];//e0[-6] = e0[-6] + e2[-6]; - e0[-7] += e2[-7];//e0[-7] = e0[-7] + e2[-7]; - e2[-6] = (k00_20)*A[0] - (k01_21) * A[1]; - e2[-7] = (k01_21)*A[0] + (k00_20) * A[1]; - - e0 -= 8; - e2 -= 8; - - A += k1; - } -} - -static void imdct_step3_inner_s_loop(int n, float *e, int i_off, int k_off, float *A, int a_off, int k0) -{ - int i; - float A0 = A[0]; - float A1 = A[0+1]; - float A2 = A[0+a_off]; - float A3 = A[0+a_off+1]; - float A4 = A[0+a_off*2+0]; - float A5 = A[0+a_off*2+1]; - float A6 = A[0+a_off*3+0]; - float A7 = A[0+a_off*3+1]; - - float k00,k11; - - float *ee0 = e +i_off; - float *ee2 = ee0+k_off; - - for (i=n; i > 0; --i) { - k00 = ee0[ 0] - ee2[ 0]; - k11 = ee0[-1] - ee2[-1]; - ee0[ 0] = ee0[ 0] + ee2[ 0]; - ee0[-1] = ee0[-1] + ee2[-1]; - ee2[ 0] = (k00) * A0 - (k11) * A1; - ee2[-1] = (k11) * A0 + (k00) * A1; - - k00 = ee0[-2] - ee2[-2]; - k11 = ee0[-3] - ee2[-3]; - ee0[-2] = ee0[-2] + ee2[-2]; - ee0[-3] = ee0[-3] + ee2[-3]; - ee2[-2] = (k00) * A2 - (k11) * A3; - ee2[-3] = (k11) * A2 + (k00) * A3; - - k00 = ee0[-4] - ee2[-4]; - k11 = ee0[-5] - ee2[-5]; - ee0[-4] = ee0[-4] + ee2[-4]; - ee0[-5] = ee0[-5] + ee2[-5]; - ee2[-4] = (k00) * A4 - (k11) * A5; - ee2[-5] = (k11) * A4 + (k00) * A5; - - k00 = ee0[-6] - ee2[-6]; - k11 = ee0[-7] - ee2[-7]; - ee0[-6] = ee0[-6] + ee2[-6]; - ee0[-7] = ee0[-7] + ee2[-7]; - ee2[-6] = (k00) * A6 - (k11) * A7; - ee2[-7] = (k11) * A6 + (k00) * A7; - - ee0 -= k0; - ee2 -= k0; - } -} - -static __forceinline void iter_54(float *z) -{ - float k00,k11,k22,k33; - float y0,y1,y2,y3; - - k00 = z[ 0] - z[-4]; - y0 = z[ 0] + z[-4]; - y2 = z[-2] + z[-6]; - k22 = z[-2] - z[-6]; - - z[-0] = y0 + y2; // z0 + z4 + z2 + z6 - z[-2] = y0 - y2; // z0 + z4 - z2 - z6 - - // done with y0,y2 - - k33 = z[-3] - z[-7]; - - z[-4] = k00 + k33; // z0 - z4 + z3 - z7 - z[-6] = k00 - k33; // z0 - z4 - z3 + z7 - - // done with k33 - - k11 = z[-1] - z[-5]; - y1 = z[-1] + z[-5]; - y3 = z[-3] + z[-7]; - - z[-1] = y1 + y3; // z1 + z5 + z3 + z7 - z[-3] = y1 - y3; // z1 + z5 - z3 - z7 - z[-5] = k11 - k22; // z1 - z5 + z2 - z6 - z[-7] = k11 + k22; // z1 - z5 - z2 + z6 -} - -static void imdct_step3_inner_s_loop_ld654(int n, float *e, int i_off, float *A, int base_n) -{ - int a_off = base_n >> 3; - float A2 = A[0+a_off]; - float *z = e + i_off; - float *base = z - 16 * n; - - while (z > base) { - float k00,k11; - - k00 = z[-0] - z[-8]; - k11 = z[-1] - z[-9]; - z[-0] = z[-0] + z[-8]; - z[-1] = z[-1] + z[-9]; - z[-8] = k00; - z[-9] = k11 ; - - k00 = z[ -2] - z[-10]; - k11 = z[ -3] - z[-11]; - z[ -2] = z[ -2] + z[-10]; - z[ -3] = z[ -3] + z[-11]; - z[-10] = (k00+k11) * A2; - z[-11] = (k11-k00) * A2; - - k00 = z[-12] - z[ -4]; // reverse to avoid a unary negation - k11 = z[ -5] - z[-13]; - z[ -4] = z[ -4] + z[-12]; - z[ -5] = z[ -5] + z[-13]; - z[-12] = k11; - z[-13] = k00; - - k00 = z[-14] - z[ -6]; // reverse to avoid a unary negation - k11 = z[ -7] - z[-15]; - z[ -6] = z[ -6] + z[-14]; - z[ -7] = z[ -7] + z[-15]; - z[-14] = (k00+k11) * A2; - z[-15] = (k00-k11) * A2; - - iter_54(z); - iter_54(z-8); - z -= 16; - } -} - -static void inverse_mdct(float *buffer, int n, vorb *f, int blocktype) -{ - int n2 = n >> 1, n4 = n >> 2, n8 = n >> 3, l; - int ld; - // @OPTIMIZE: reduce register pressure by using fewer variables? - int save_point = temp_alloc_save(f); - float *buf2 = (float *) temp_alloc(f, n2 * sizeof(*buf2)); - float *u=NULL,*v=NULL; - // twiddle factors - float *A = f->A[blocktype]; - - // IMDCT algorithm from "The use of multirate filter banks for coding of high quality digital audio" - // See notes about bugs in that paper in less-optimal implementation 'inverse_mdct_old' after this function. - - // kernel from paper - - - // merged: - // copy and reflect spectral data - // step 0 - - // note that it turns out that the items added together during - // this step are, in fact, being added to themselves (as reflected - // by step 0). inexplicable inefficiency! this became obvious - // once I combined the passes. - - // so there's a missing 'times 2' here (for adding X to itself). - // this propagates through linearly to the end, where the numbers - // are 1/2 too small, and need to be compensated for. - - { - float *d,*e, *AA, *e_stop; - d = &buf2[n2-2]; - AA = A; - e = &buffer[0]; - e_stop = &buffer[n2]; - while (e != e_stop) { - d[1] = (e[0] * AA[0] - e[2]*AA[1]); - d[0] = (e[0] * AA[1] + e[2]*AA[0]); - d -= 2; - AA += 2; - e += 4; - } - - e = &buffer[n2-3]; - while (d >= buf2) { - d[1] = (-e[2] * AA[0] - -e[0]*AA[1]); - d[0] = (-e[2] * AA[1] + -e[0]*AA[0]); - d -= 2; - AA += 2; - e -= 4; - } - } - - // now we use symbolic names for these, so that we can - // possibly swap their meaning as we change which operations - // are in place - - u = buffer; - v = buf2; - - // step 2 (paper output is w, now u) - // this could be in place, but the data ends up in the wrong - // place... _somebody_'s got to swap it, so this is nominated - { - float *AA = &A[n2-8]; - float *d0,*d1, *e0, *e1; - - e0 = &v[n4]; - e1 = &v[0]; - - d0 = &u[n4]; - d1 = &u[0]; - - while (AA >= A) { - float v40_20, v41_21; - - v41_21 = e0[1] - e1[1]; - v40_20 = e0[0] - e1[0]; - d0[1] = e0[1] + e1[1]; - d0[0] = e0[0] + e1[0]; - d1[1] = v41_21*AA[4] - v40_20*AA[5]; - d1[0] = v40_20*AA[4] + v41_21*AA[5]; - - v41_21 = e0[3] - e1[3]; - v40_20 = e0[2] - e1[2]; - d0[3] = e0[3] + e1[3]; - d0[2] = e0[2] + e1[2]; - d1[3] = v41_21*AA[0] - v40_20*AA[1]; - d1[2] = v40_20*AA[0] + v41_21*AA[1]; - - AA -= 8; - - d0 += 4; - d1 += 4; - e0 += 4; - e1 += 4; - } - } - - // step 3 - ld = ilog(n) - 1; // ilog is off-by-one from normal definitions - - // optimized step 3: - - // the original step3 loop can be nested r inside s or s inside r; - // it's written originally as s inside r, but this is dumb when r - // iterates many times, and s few. So I have two copies of it and - // switch between them halfway. - - // this is iteration 0 of step 3 - imdct_step3_iter0_loop(n >> 4, u, n2-1-n4*0, -(n >> 3), A); - imdct_step3_iter0_loop(n >> 4, u, n2-1-n4*1, -(n >> 3), A); - - // this is iteration 1 of step 3 - imdct_step3_inner_r_loop(n >> 5, u, n2-1 - n8*0, -(n >> 4), A, 16); - imdct_step3_inner_r_loop(n >> 5, u, n2-1 - n8*1, -(n >> 4), A, 16); - imdct_step3_inner_r_loop(n >> 5, u, n2-1 - n8*2, -(n >> 4), A, 16); - imdct_step3_inner_r_loop(n >> 5, u, n2-1 - n8*3, -(n >> 4), A, 16); - - l=2; - for (; l < (ld-3)>>1; ++l) { - int k0 = n >> (l+2), k0_2 = k0>>1; - int lim = 1 << (l+1); - int i; - for (i=0; i < lim; ++i) - imdct_step3_inner_r_loop(n >> (l+4), u, n2-1 - k0*i, -k0_2, A, 1 << (l+3)); - } - - for (; l < ld-6; ++l) { - int k0 = n >> (l+2), k1 = 1 << (l+3), k0_2 = k0>>1; - int rlim = n >> (l+6), r; - int lim = 1 << (l+1); - int i_off; - float *A0 = A; - i_off = n2-1; - for (r=rlim; r > 0; --r) { - imdct_step3_inner_s_loop(lim, u, i_off, -k0_2, A0, k1, k0); - A0 += k1*4; - i_off -= 8; - } - } - - // iterations with count: - // ld-6,-5,-4 all interleaved together - // the big win comes from getting rid of needless flops - // due to the constants on pass 5 & 4 being all 1 and 0; - // combining them to be simultaneous to improve cache made little difference - imdct_step3_inner_s_loop_ld654(n >> 5, u, n2-1, A, n); - - // output is u - - // step 4, 5, and 6 - // cannot be in-place because of step 5 - { - uint16 *bitrev = f->bit_reverse[blocktype]; - // weirdly, I'd have thought reading sequentially and writing - // erratically would have been better than vice-versa, but in - // fact that's not what my testing showed. (That is, with - // j = bitreverse(i), do you read i and write j, or read j and write i.) - - float *d0 = &v[n4-4]; - float *d1 = &v[n2-4]; - while (d0 >= v) { - int k4; - - k4 = bitrev[0]; - d1[3] = u[k4+0]; - d1[2] = u[k4+1]; - d0[3] = u[k4+2]; - d0[2] = u[k4+3]; - - k4 = bitrev[1]; - d1[1] = u[k4+0]; - d1[0] = u[k4+1]; - d0[1] = u[k4+2]; - d0[0] = u[k4+3]; - - d0 -= 4; - d1 -= 4; - bitrev += 2; - } - } - // (paper output is u, now v) - - - // data must be in buf2 - assert(v == buf2); - - // step 7 (paper output is v, now v) - // this is now in place - { - float *C = f->C[blocktype]; - float *d, *e; - - d = v; - e = v + n2 - 4; - - while (d < e) { - float a02,a11,b0,b1,b2,b3; - - a02 = d[0] - e[2]; - a11 = d[1] + e[3]; - - b0 = C[1]*a02 + C[0]*a11; - b1 = C[1]*a11 - C[0]*a02; - - b2 = d[0] + e[ 2]; - b3 = d[1] - e[ 3]; - - d[0] = b2 + b0; - d[1] = b3 + b1; - e[2] = b2 - b0; - e[3] = b1 - b3; - - a02 = d[2] - e[0]; - a11 = d[3] + e[1]; - - b0 = C[3]*a02 + C[2]*a11; - b1 = C[3]*a11 - C[2]*a02; - - b2 = d[2] + e[ 0]; - b3 = d[3] - e[ 1]; - - d[2] = b2 + b0; - d[3] = b3 + b1; - e[0] = b2 - b0; - e[1] = b1 - b3; - - C += 4; - d += 4; - e -= 4; - } - } - - // data must be in buf2 - - - // step 8+decode (paper output is X, now buffer) - // this generates pairs of data a la 8 and pushes them directly through - // the decode kernel (pushing rather than pulling) to avoid having - // to make another pass later - - // this cannot POSSIBLY be in place, so we refer to the buffers directly - - { - float *d0,*d1,*d2,*d3; - - float *B = f->B[blocktype] + n2 - 8; - float *e = buf2 + n2 - 8; - d0 = &buffer[0]; - d1 = &buffer[n2-4]; - d2 = &buffer[n2]; - d3 = &buffer[n-4]; - while (e >= v) { - float p0,p1,p2,p3; - - p3 = e[6]*B[7] - e[7]*B[6]; - p2 = -e[6]*B[6] - e[7]*B[7]; - - d0[0] = p3; - d1[3] = - p3; - d2[0] = p2; - d3[3] = p2; - - p1 = e[4]*B[5] - e[5]*B[4]; - p0 = -e[4]*B[4] - e[5]*B[5]; - - d0[1] = p1; - d1[2] = - p1; - d2[1] = p0; - d3[2] = p0; - - p3 = e[2]*B[3] - e[3]*B[2]; - p2 = -e[2]*B[2] - e[3]*B[3]; - - d0[2] = p3; - d1[1] = - p3; - d2[2] = p2; - d3[1] = p2; - - p1 = e[0]*B[1] - e[1]*B[0]; - p0 = -e[0]*B[0] - e[1]*B[1]; - - d0[3] = p1; - d1[0] = - p1; - d2[3] = p0; - d3[0] = p0; - - B -= 8; - e -= 8; - d0 += 4; - d2 += 4; - d1 -= 4; - d3 -= 4; - } - } - - temp_free(f,buf2); - temp_alloc_restore(f,save_point); -} - -#if 0 -// this is the original version of the above code, if you want to optimize it from scratch -void inverse_mdct_naive(float *buffer, int n) -{ - float s; - float A[1 << 12], B[1 << 12], C[1 << 11]; - int i,k,k2,k4, n2 = n >> 1, n4 = n >> 2, n8 = n >> 3, l; - int n3_4 = n - n4, ld; - // how can they claim this only uses N words?! - // oh, because they're only used sparsely, whoops - float u[1 << 13], X[1 << 13], v[1 << 13], w[1 << 13]; - // set up twiddle factors - - for (k=k2=0; k < n4; ++k,k2+=2) { - A[k2 ] = (float) cos(4*k*M_PI/n); - A[k2+1] = (float) -sin(4*k*M_PI/n); - B[k2 ] = (float) cos((k2+1)*M_PI/n/2); - B[k2+1] = (float) sin((k2+1)*M_PI/n/2); - } - for (k=k2=0; k < n8; ++k,k2+=2) { - C[k2 ] = (float) cos(2*(k2+1)*M_PI/n); - C[k2+1] = (float) -sin(2*(k2+1)*M_PI/n); - } - - // IMDCT algorithm from "The use of multirate filter banks for coding of high quality digital audio" - // Note there are bugs in that pseudocode, presumably due to them attempting - // to rename the arrays nicely rather than representing the way their actual - // implementation bounces buffers back and forth. As a result, even in the - // "some formulars corrected" version, a direct implementation fails. These - // are noted below as "paper bug". - - // copy and reflect spectral data - for (k=0; k < n2; ++k) u[k] = buffer[k]; - for ( ; k < n ; ++k) u[k] = -buffer[n - k - 1]; - // kernel from paper - // step 1 - for (k=k2=k4=0; k < n4; k+=1, k2+=2, k4+=4) { - v[n-k4-1] = (u[k4] - u[n-k4-1]) * A[k2] - (u[k4+2] - u[n-k4-3])*A[k2+1]; - v[n-k4-3] = (u[k4] - u[n-k4-1]) * A[k2+1] + (u[k4+2] - u[n-k4-3])*A[k2]; - } - // step 2 - for (k=k4=0; k < n8; k+=1, k4+=4) { - w[n2+3+k4] = v[n2+3+k4] + v[k4+3]; - w[n2+1+k4] = v[n2+1+k4] + v[k4+1]; - w[k4+3] = (v[n2+3+k4] - v[k4+3])*A[n2-4-k4] - (v[n2+1+k4]-v[k4+1])*A[n2-3-k4]; - w[k4+1] = (v[n2+1+k4] - v[k4+1])*A[n2-4-k4] + (v[n2+3+k4]-v[k4+3])*A[n2-3-k4]; - } - // step 3 - ld = ilog(n) - 1; // ilog is off-by-one from normal definitions - for (l=0; l < ld-3; ++l) { - int k0 = n >> (l+2), k1 = 1 << (l+3); - int rlim = n >> (l+4), r4, r; - int s2lim = 1 << (l+2), s2; - for (r=r4=0; r < rlim; r4+=4,++r) { - for (s2=0; s2 < s2lim; s2+=2) { - u[n-1-k0*s2-r4] = w[n-1-k0*s2-r4] + w[n-1-k0*(s2+1)-r4]; - u[n-3-k0*s2-r4] = w[n-3-k0*s2-r4] + w[n-3-k0*(s2+1)-r4]; - u[n-1-k0*(s2+1)-r4] = (w[n-1-k0*s2-r4] - w[n-1-k0*(s2+1)-r4]) * A[r*k1] - - (w[n-3-k0*s2-r4] - w[n-3-k0*(s2+1)-r4]) * A[r*k1+1]; - u[n-3-k0*(s2+1)-r4] = (w[n-3-k0*s2-r4] - w[n-3-k0*(s2+1)-r4]) * A[r*k1] - + (w[n-1-k0*s2-r4] - w[n-1-k0*(s2+1)-r4]) * A[r*k1+1]; - } - } - if (l+1 < ld-3) { - // paper bug: ping-ponging of u&w here is omitted - memcpy(w, u, sizeof(u)); - } - } - - // step 4 - for (i=0; i < n8; ++i) { - int j = bit_reverse(i) >> (32-ld+3); - assert(j < n8); - if (i == j) { - // paper bug: original code probably swapped in place; if copying, - // need to directly copy in this case - int i8 = i << 3; - v[i8+1] = u[i8+1]; - v[i8+3] = u[i8+3]; - v[i8+5] = u[i8+5]; - v[i8+7] = u[i8+7]; - } else if (i < j) { - int i8 = i << 3, j8 = j << 3; - v[j8+1] = u[i8+1], v[i8+1] = u[j8 + 1]; - v[j8+3] = u[i8+3], v[i8+3] = u[j8 + 3]; - v[j8+5] = u[i8+5], v[i8+5] = u[j8 + 5]; - v[j8+7] = u[i8+7], v[i8+7] = u[j8 + 7]; - } - } - // step 5 - for (k=0; k < n2; ++k) { - w[k] = v[k*2+1]; - } - // step 6 - for (k=k2=k4=0; k < n8; ++k, k2 += 2, k4 += 4) { - u[n-1-k2] = w[k4]; - u[n-2-k2] = w[k4+1]; - u[n3_4 - 1 - k2] = w[k4+2]; - u[n3_4 - 2 - k2] = w[k4+3]; - } - // step 7 - for (k=k2=0; k < n8; ++k, k2 += 2) { - v[n2 + k2 ] = ( u[n2 + k2] + u[n-2-k2] + C[k2+1]*(u[n2+k2]-u[n-2-k2]) + C[k2]*(u[n2+k2+1]+u[n-2-k2+1]))/2; - v[n-2 - k2] = ( u[n2 + k2] + u[n-2-k2] - C[k2+1]*(u[n2+k2]-u[n-2-k2]) - C[k2]*(u[n2+k2+1]+u[n-2-k2+1]))/2; - v[n2+1+ k2] = ( u[n2+1+k2] - u[n-1-k2] + C[k2+1]*(u[n2+1+k2]+u[n-1-k2]) - C[k2]*(u[n2+k2]-u[n-2-k2]))/2; - v[n-1 - k2] = (-u[n2+1+k2] + u[n-1-k2] + C[k2+1]*(u[n2+1+k2]+u[n-1-k2]) - C[k2]*(u[n2+k2]-u[n-2-k2]))/2; - } - // step 8 - for (k=k2=0; k < n4; ++k,k2 += 2) { - X[k] = v[k2+n2]*B[k2 ] + v[k2+1+n2]*B[k2+1]; - X[n2-1-k] = v[k2+n2]*B[k2+1] - v[k2+1+n2]*B[k2 ]; - } - - // decode kernel to output - // determined the following value experimentally - // (by first figuring out what made inverse_mdct_slow work); then matching that here - // (probably vorbis encoder premultiplies by n or n/2, to save it on the decoder?) - s = 0.5; // theoretically would be n4 - - // [[[ note! the s value of 0.5 is compensated for by the B[] in the current code, - // so it needs to use the "old" B values to behave correctly, or else - // set s to 1.0 ]]] - for (i=0; i < n4 ; ++i) buffer[i] = s * X[i+n4]; - for ( ; i < n3_4; ++i) buffer[i] = -s * X[n3_4 - i - 1]; - for ( ; i < n ; ++i) buffer[i] = -s * X[i - n3_4]; -} -#endif - -static float *get_window(vorb *f, int len) -{ - len <<= 1; - if (len == f->blocksize_0) return f->window[0]; - if (len == f->blocksize_1) return f->window[1]; - return NULL; -} - -#ifndef STB_VORBIS_NO_DEFER_FLOOR -typedef int16 YTYPE; -#else -typedef int YTYPE; -#endif -static int do_floor(vorb *f, Mapping *map, int i, int n, float *target, YTYPE *finalY, uint8 *step2_flag) -{ - int n2 = n >> 1; - int s = map->chan[i].mux, floor; - floor = map->submap_floor[s]; - if (f->floor_types[floor] == 0) { - return error(f, VORBIS_invalid_stream); - } else { - Floor1 *g = &f->floor_config[floor].floor1; - int j,q; - int lx = 0, ly = finalY[0] * g->floor1_multiplier; - for (q=1; q < g->values; ++q) { - j = g->sorted_order[q]; - #ifndef STB_VORBIS_NO_DEFER_FLOOR - if (finalY[j] >= 0) - #else - if (step2_flag[j]) - #endif - { - int hy = finalY[j] * g->floor1_multiplier; - int hx = g->Xlist[j]; - if (lx != hx) - draw_line(target, lx,ly, hx,hy, n2); - CHECK(f); - lx = hx, ly = hy; - } - } - if (lx < n2) { - // optimization of: draw_line(target, lx,ly, n,ly, n2); - for (j=lx; j < n2; ++j) - LINE_OP(target[j], inverse_db_table[ly]); - CHECK(f); - } - } - return TRUE; -} - -// The meaning of "left" and "right" -// -// For a given frame: -// we compute samples from 0..n -// window_center is n/2 -// we'll window and mix the samples from left_start to left_end with data from the previous frame -// all of the samples from left_end to right_start can be output without mixing; however, -// this interval is 0-length except when transitioning between short and long frames -// all of the samples from right_start to right_end need to be mixed with the next frame, -// which we don't have, so those get saved in a buffer -// frame N's right_end-right_start, the number of samples to mix with the next frame, -// has to be the same as frame N+1's left_end-left_start (which they are by -// construction) - -static int vorbis_decode_initial(vorb *f, int *p_left_start, int *p_left_end, int *p_right_start, int *p_right_end, int *mode) -{ - Mode *m; - int i, n, prev, next, window_center; - f->channel_buffer_start = f->channel_buffer_end = 0; - - retry: - if (f->eof) return FALSE; - if (!maybe_start_packet(f)) - return FALSE; - // check packet type - if (get_bits(f,1) != 0) { - if (IS_PUSH_MODE(f)) - return error(f,VORBIS_bad_packet_type); - while (EOP != get8_packet(f)); - goto retry; - } - - if (f->alloc.alloc_buffer) - assert(f->alloc.alloc_buffer_length_in_bytes == f->temp_offset); - - i = get_bits(f, ilog(f->mode_count-1)); - if (i == EOP) return FALSE; - if (i >= f->mode_count) return FALSE; - *mode = i; - m = f->mode_config + i; - if (m->blockflag) { - n = f->blocksize_1; - prev = get_bits(f,1); - next = get_bits(f,1); - } else { - prev = next = 0; - n = f->blocksize_0; - } - -// WINDOWING - - window_center = n >> 1; - if (m->blockflag && !prev) { - *p_left_start = (n - f->blocksize_0) >> 2; - *p_left_end = (n + f->blocksize_0) >> 2; - } else { - *p_left_start = 0; - *p_left_end = window_center; - } - if (m->blockflag && !next) { - *p_right_start = (n*3 - f->blocksize_0) >> 2; - *p_right_end = (n*3 + f->blocksize_0) >> 2; - } else { - *p_right_start = window_center; - *p_right_end = n; - } - - return TRUE; -} - -static int vorbis_decode_packet_rest(vorb *f, int *len, Mode *m, int left_start, int left_end, int right_start, int right_end, int *p_left) -{ - Mapping *map; - int i,j,k,n,n2; - int zero_channel[256]; - int really_zero_channel[256]; - -// WINDOWING - - n = f->blocksize[m->blockflag]; - map = &f->mapping[m->mapping]; - -// FLOORS - n2 = n >> 1; - - CHECK(f); - - for (i=0; i < f->channels; ++i) { - int s = map->chan[i].mux, floor; - zero_channel[i] = FALSE; - floor = map->submap_floor[s]; - if (f->floor_types[floor] == 0) { - return error(f, VORBIS_invalid_stream); - } else { - Floor1 *g = &f->floor_config[floor].floor1; - if (get_bits(f, 1)) { - short *finalY; - uint8 step2_flag[256]; - static int range_list[4] = { 256, 128, 86, 64 }; - int range = range_list[g->floor1_multiplier-1]; - int offset = 2; - finalY = f->finalY[i]; - finalY[0] = get_bits(f, ilog(range)-1); - finalY[1] = get_bits(f, ilog(range)-1); - for (j=0; j < g->partitions; ++j) { - int pclass = g->partition_class_list[j]; - int cdim = g->class_dimensions[pclass]; - int cbits = g->class_subclasses[pclass]; - int csub = (1 << cbits)-1; - int cval = 0; - if (cbits) { - Codebook *c = f->codebooks + g->class_masterbooks[pclass]; - DECODE(cval,f,c); - } - for (k=0; k < cdim; ++k) { - int book = g->subclass_books[pclass][cval & csub]; - cval = cval >> cbits; - if (book >= 0) { - int temp; - Codebook *c = f->codebooks + book; - DECODE(temp,f,c); - finalY[offset++] = temp; - } else - finalY[offset++] = 0; - } - } - if (f->valid_bits == INVALID_BITS) goto error; // behavior according to spec - step2_flag[0] = step2_flag[1] = 1; - for (j=2; j < g->values; ++j) { - int low, high, pred, highroom, lowroom, room, val; - low = g->neighbors[j][0]; - high = g->neighbors[j][1]; - //neighbors(g->Xlist, j, &low, &high); - pred = predict_point(g->Xlist[j], g->Xlist[low], g->Xlist[high], finalY[low], finalY[high]); - val = finalY[j]; - highroom = range - pred; - lowroom = pred; - if (highroom < lowroom) - room = highroom * 2; - else - room = lowroom * 2; - if (val) { - step2_flag[low] = step2_flag[high] = 1; - step2_flag[j] = 1; - if (val >= room) - if (highroom > lowroom) - finalY[j] = val - lowroom + pred; - else - finalY[j] = pred - val + highroom - 1; - else - if (val & 1) - finalY[j] = pred - ((val+1)>>1); - else - finalY[j] = pred + (val>>1); - } else { - step2_flag[j] = 0; - finalY[j] = pred; - } - } - -#ifdef STB_VORBIS_NO_DEFER_FLOOR - do_floor(f, map, i, n, f->floor_buffers[i], finalY, step2_flag); -#else - // defer final floor computation until _after_ residue - for (j=0; j < g->values; ++j) { - if (!step2_flag[j]) - finalY[j] = -1; - } -#endif - } else { - error: - zero_channel[i] = TRUE; - } - // So we just defer everything else to later - - // at this point we've decoded the floor into buffer - } - } - CHECK(f); - // at this point we've decoded all floors - - if (f->alloc.alloc_buffer) - assert(f->alloc.alloc_buffer_length_in_bytes == f->temp_offset); - - // re-enable coupled channels if necessary - memcpy(really_zero_channel, zero_channel, sizeof(really_zero_channel[0]) * f->channels); - for (i=0; i < map->coupling_steps; ++i) - if (!zero_channel[map->chan[i].magnitude] || !zero_channel[map->chan[i].angle]) { - zero_channel[map->chan[i].magnitude] = zero_channel[map->chan[i].angle] = FALSE; - } - - CHECK(f); -// RESIDUE DECODE - for (i=0; i < map->submaps; ++i) { - float *residue_buffers[STB_VORBIS_MAX_CHANNELS]; - int r; - uint8 do_not_decode[256]; - int ch = 0; - for (j=0; j < f->channels; ++j) { - if (map->chan[j].mux == i) { - if (zero_channel[j]) { - do_not_decode[ch] = TRUE; - residue_buffers[ch] = NULL; - } else { - do_not_decode[ch] = FALSE; - residue_buffers[ch] = f->channel_buffers[j]; - } - ++ch; - } - } - r = map->submap_residue[i]; - decode_residue(f, residue_buffers, ch, n2, r, do_not_decode); - } - - if (f->alloc.alloc_buffer) - assert(f->alloc.alloc_buffer_length_in_bytes == f->temp_offset); - CHECK(f); - -// INVERSE COUPLING - for (i = map->coupling_steps-1; i >= 0; --i) { - int n2 = n >> 1; - float *m = f->channel_buffers[map->chan[i].magnitude]; - float *a = f->channel_buffers[map->chan[i].angle ]; - for (j=0; j < n2; ++j) { - float a2,m2; - if (m[j] > 0) - if (a[j] > 0) - m2 = m[j], a2 = m[j] - a[j]; - else - a2 = m[j], m2 = m[j] + a[j]; - else - if (a[j] > 0) - m2 = m[j], a2 = m[j] + a[j]; - else - a2 = m[j], m2 = m[j] - a[j]; - m[j] = m2; - a[j] = a2; - } - } - CHECK(f); - - // finish decoding the floors -#ifndef STB_VORBIS_NO_DEFER_FLOOR - for (i=0; i < f->channels; ++i) { - if (really_zero_channel[i]) { - memset(f->channel_buffers[i], 0, sizeof(*f->channel_buffers[i]) * n2); - } else { - do_floor(f, map, i, n, f->channel_buffers[i], f->finalY[i], NULL); - } - } -#else - for (i=0; i < f->channels; ++i) { - if (really_zero_channel[i]) { - memset(f->channel_buffers[i], 0, sizeof(*f->channel_buffers[i]) * n2); - } else { - for (j=0; j < n2; ++j) - f->channel_buffers[i][j] *= f->floor_buffers[i][j]; - } - } -#endif - -// INVERSE MDCT - CHECK(f); - for (i=0; i < f->channels; ++i) - inverse_mdct(f->channel_buffers[i], n, f, m->blockflag); - CHECK(f); - - // this shouldn't be necessary, unless we exited on an error - // and want to flush to get to the next packet - flush_packet(f); - - if (f->first_decode) { - // assume we start so first non-discarded sample is sample 0 - // this isn't to spec, but spec would require us to read ahead - // and decode the size of all current frames--could be done, - // but presumably it's not a commonly used feature - f->current_loc = -n2; // start of first frame is positioned for discard - // we might have to discard samples "from" the next frame too, - // if we're lapping a large block then a small at the start? - f->discard_samples_deferred = n - right_end; - f->current_loc_valid = TRUE; - f->first_decode = FALSE; - } else if (f->discard_samples_deferred) { - if (f->discard_samples_deferred >= right_start - left_start) { - f->discard_samples_deferred -= (right_start - left_start); - left_start = right_start; - *p_left = left_start; - } else { - left_start += f->discard_samples_deferred; - *p_left = left_start; - f->discard_samples_deferred = 0; - } - } else if (f->previous_length == 0 && f->current_loc_valid) { - // we're recovering from a seek... that means we're going to discard - // the samples from this packet even though we know our position from - // the last page header, so we need to update the position based on - // the discarded samples here - // but wait, the code below is going to add this in itself even - // on a discard, so we don't need to do it here... - } - - // check if we have ogg information about the sample # for this packet - if (f->last_seg_which == f->end_seg_with_known_loc) { - // if we have a valid current loc, and this is final: - if (f->current_loc_valid && (f->page_flag & PAGEFLAG_last_page)) { - uint32 current_end = f->known_loc_for_packet; - // then let's infer the size of the (probably) short final frame - if (current_end < f->current_loc + (right_end-left_start)) { - if (current_end < f->current_loc) { - // negative truncation, that's impossible! - *len = 0; - } else { - *len = current_end - f->current_loc; - } - *len += left_start; // this doesn't seem right, but has no ill effect on my test files - if (*len > right_end) *len = right_end; // this should never happen - f->current_loc += *len; - return TRUE; - } - } - // otherwise, just set our sample loc - // guess that the ogg granule pos refers to the _middle_ of the - // last frame? - // set f->current_loc to the position of left_start - f->current_loc = f->known_loc_for_packet - (n2-left_start); - f->current_loc_valid = TRUE; - } - if (f->current_loc_valid) - f->current_loc += (right_start - left_start); - - if (f->alloc.alloc_buffer) - assert(f->alloc.alloc_buffer_length_in_bytes == f->temp_offset); - *len = right_end; // ignore samples after the window goes to 0 - CHECK(f); - - return TRUE; -} - -static int vorbis_decode_packet(vorb *f, int *len, int *p_left, int *p_right) -{ - int mode, left_end, right_end; - if (!vorbis_decode_initial(f, p_left, &left_end, p_right, &right_end, &mode)) return 0; - return vorbis_decode_packet_rest(f, len, f->mode_config + mode, *p_left, left_end, *p_right, right_end, p_left); -} - -static int vorbis_finish_frame(stb_vorbis *f, int len, int left, int right) -{ - int prev,i,j; - // we use right&left (the start of the right- and left-window sin()-regions) - // to determine how much to return, rather than inferring from the rules - // (same result, clearer code); 'left' indicates where our sin() window - // starts, therefore where the previous window's right edge starts, and - // therefore where to start mixing from the previous buffer. 'right' - // indicates where our sin() ending-window starts, therefore that's where - // we start saving, and where our returned-data ends. - - // mixin from previous window - if (f->previous_length) { - int i,j, n = f->previous_length; - float *w = get_window(f, n); - if (w == NULL) return 0; - for (i=0; i < f->channels; ++i) { - for (j=0; j < n; ++j) - f->channel_buffers[i][left+j] = - f->channel_buffers[i][left+j]*w[ j] + - f->previous_window[i][ j]*w[n-1-j]; - } - } - - prev = f->previous_length; - - // last half of this data becomes previous window - f->previous_length = len - right; - - // @OPTIMIZE: could avoid this copy by double-buffering the - // output (flipping previous_window with channel_buffers), but - // then previous_window would have to be 2x as large, and - // channel_buffers couldn't be temp mem (although they're NOT - // currently temp mem, they could be (unless we want to level - // performance by spreading out the computation)) - for (i=0; i < f->channels; ++i) - for (j=0; right+j < len; ++j) - f->previous_window[i][j] = f->channel_buffers[i][right+j]; - - if (!prev) - // there was no previous packet, so this data isn't valid... - // this isn't entirely true, only the would-have-overlapped data - // isn't valid, but this seems to be what the spec requires - return 0; - - // truncate a short frame - if (len < right) right = len; - - f->samples_output += right-left; - - return right - left; -} - -static int vorbis_pump_first_frame(stb_vorbis *f) -{ - int len, right, left, res; - res = vorbis_decode_packet(f, &len, &left, &right); - if (res) - vorbis_finish_frame(f, len, left, right); - return res; -} - -#ifndef STB_VORBIS_NO_PUSHDATA_API -static int is_whole_packet_present(stb_vorbis *f) -{ - // make sure that we have the packet available before continuing... - // this requires a full ogg parse, but we know we can fetch from f->stream - - // instead of coding this out explicitly, we could save the current read state, - // read the next packet with get8() until end-of-packet, check f->eof, then - // reset the state? but that would be slower, esp. since we'd have over 256 bytes - // of state to restore (primarily the page segment table) - - int s = f->next_seg, first = TRUE; - uint8 *p = f->stream; - - if (s != -1) { // if we're not starting the packet with a 'continue on next page' flag - for (; s < f->segment_count; ++s) { - p += f->segments[s]; - if (f->segments[s] < 255) // stop at first short segment - break; - } - // either this continues, or it ends it... - if (s == f->segment_count) - s = -1; // set 'crosses page' flag - if (p > f->stream_end) return error(f, VORBIS_need_more_data); - first = FALSE; - } - for (; s == -1;) { - uint8 *q; - int n; - - // check that we have the page header ready - if (p + 26 >= f->stream_end) return error(f, VORBIS_need_more_data); - // validate the page - if (memcmp(p, ogg_page_header, 4)) return error(f, VORBIS_invalid_stream); - if (p[4] != 0) return error(f, VORBIS_invalid_stream); - if (first) { // the first segment must NOT have 'continued_packet', later ones MUST - if (f->previous_length) - if ((p[5] & PAGEFLAG_continued_packet)) return error(f, VORBIS_invalid_stream); - // if no previous length, we're resynching, so we can come in on a continued-packet, - // which we'll just drop - } else { - if (!(p[5] & PAGEFLAG_continued_packet)) return error(f, VORBIS_invalid_stream); - } - n = p[26]; // segment counts - q = p+27; // q points to segment table - p = q + n; // advance past header - // make sure we've read the segment table - if (p > f->stream_end) return error(f, VORBIS_need_more_data); - for (s=0; s < n; ++s) { - p += q[s]; - if (q[s] < 255) - break; - } - if (s == n) - s = -1; // set 'crosses page' flag - if (p > f->stream_end) return error(f, VORBIS_need_more_data); - first = FALSE; - } - return TRUE; -} -#endif // !STB_VORBIS_NO_PUSHDATA_API - -static int start_decoder(vorb *f) -{ - uint8 header[6], x,y; - int len,i,j,k, max_submaps = 0; - int longest_floorlist=0; - - // first page, first packet - f->first_decode = TRUE; - - if (!start_page(f)) return FALSE; - // validate page flag - if (!(f->page_flag & PAGEFLAG_first_page)) return error(f, VORBIS_invalid_first_page); - if (f->page_flag & PAGEFLAG_last_page) return error(f, VORBIS_invalid_first_page); - if (f->page_flag & PAGEFLAG_continued_packet) return error(f, VORBIS_invalid_first_page); - // check for expected packet length - if (f->segment_count != 1) return error(f, VORBIS_invalid_first_page); - if (f->segments[0] != 30) { - // check for the Ogg skeleton fishead identifying header to refine our error - if (f->segments[0] == 64 && - getn(f, header, 6) && - header[0] == 'f' && - header[1] == 'i' && - header[2] == 's' && - header[3] == 'h' && - header[4] == 'e' && - header[5] == 'a' && - get8(f) == 'd' && - get8(f) == '\0') return error(f, VORBIS_ogg_skeleton_not_supported); - else - return error(f, VORBIS_invalid_first_page); - } - - // read packet - // check packet header - if (get8(f) != VORBIS_packet_id) return error(f, VORBIS_invalid_first_page); - if (!getn(f, header, 6)) return error(f, VORBIS_unexpected_eof); - if (!vorbis_validate(header)) return error(f, VORBIS_invalid_first_page); - // vorbis_version - if (get32(f) != 0) return error(f, VORBIS_invalid_first_page); - f->channels = get8(f); if (!f->channels) return error(f, VORBIS_invalid_first_page); - if (f->channels > STB_VORBIS_MAX_CHANNELS) return error(f, VORBIS_too_many_channels); - f->sample_rate = get32(f); if (!f->sample_rate) return error(f, VORBIS_invalid_first_page); - get32(f); // bitrate_maximum - get32(f); // bitrate_nominal - get32(f); // bitrate_minimum - x = get8(f); - { - int log0,log1; - log0 = x & 15; - log1 = x >> 4; - f->blocksize_0 = 1 << log0; - f->blocksize_1 = 1 << log1; - if (log0 < 6 || log0 > 13) return error(f, VORBIS_invalid_setup); - if (log1 < 6 || log1 > 13) return error(f, VORBIS_invalid_setup); - if (log0 > log1) return error(f, VORBIS_invalid_setup); - } - - // framing_flag - x = get8(f); - if (!(x & 1)) return error(f, VORBIS_invalid_first_page); - - // second packet! - if (!start_page(f)) return FALSE; - - if (!start_packet(f)) return FALSE; - - if (!next_segment(f)) return FALSE; - - if (get8_packet(f) != VORBIS_packet_comment) return error(f, VORBIS_invalid_setup); - for (i=0; i < 6; ++i) header[i] = get8_packet(f); - if (!vorbis_validate(header)) return error(f, VORBIS_invalid_setup); - //file vendor - len = get32_packet(f); - f->vendor = (char*)setup_malloc(f, sizeof(char) * (len+1)); - if (f->vendor == NULL) return error(f, VORBIS_outofmem); - for(i=0; i < len; ++i) { - f->vendor[i] = get8_packet(f); - } - f->vendor[len] = (char)'\0'; - //user comments - f->comment_list_length = get32_packet(f); - f->comment_list = (char**)setup_malloc(f, sizeof(char*) * (f->comment_list_length)); - if (f->comment_list == NULL) return error(f, VORBIS_outofmem); - - for(i=0; i < f->comment_list_length; ++i) { - len = get32_packet(f); - f->comment_list[i] = (char*)setup_malloc(f, sizeof(char) * (len+1)); - if (f->comment_list[i] == NULL) return error(f, VORBIS_outofmem); - - for(j=0; j < len; ++j) { - f->comment_list[i][j] = get8_packet(f); - } - f->comment_list[i][len] = (char)'\0'; - } - - // framing_flag - x = get8_packet(f); - if (!(x & 1)) return error(f, VORBIS_invalid_setup); - - - skip(f, f->bytes_in_seg); - f->bytes_in_seg = 0; - - do { - len = next_segment(f); - skip(f, len); - f->bytes_in_seg = 0; - } while (len); - - // third packet! - if (!start_packet(f)) return FALSE; - - #ifndef STB_VORBIS_NO_PUSHDATA_API - if (IS_PUSH_MODE(f)) { - if (!is_whole_packet_present(f)) { - // convert error in ogg header to write type - if (f->error == VORBIS_invalid_stream) - f->error = VORBIS_invalid_setup; - return FALSE; - } - } - #endif - - crc32_init(); // always init it, to avoid multithread race conditions - - if (get8_packet(f) != VORBIS_packet_setup) return error(f, VORBIS_invalid_setup); - for (i=0; i < 6; ++i) header[i] = get8_packet(f); - if (!vorbis_validate(header)) return error(f, VORBIS_invalid_setup); - - // codebooks - - f->codebook_count = get_bits(f,8) + 1; - f->codebooks = (Codebook *) setup_malloc(f, sizeof(*f->codebooks) * f->codebook_count); - if (f->codebooks == NULL) return error(f, VORBIS_outofmem); - memset(f->codebooks, 0, sizeof(*f->codebooks) * f->codebook_count); - for (i=0; i < f->codebook_count; ++i) { - uint32 *values; - int ordered, sorted_count; - int total=0; - uint8 *lengths; - Codebook *c = f->codebooks+i; - CHECK(f); - x = get_bits(f, 8); if (x != 0x42) return error(f, VORBIS_invalid_setup); - x = get_bits(f, 8); if (x != 0x43) return error(f, VORBIS_invalid_setup); - x = get_bits(f, 8); if (x != 0x56) return error(f, VORBIS_invalid_setup); - x = get_bits(f, 8); - c->dimensions = (get_bits(f, 8)<<8) + x; - x = get_bits(f, 8); - y = get_bits(f, 8); - c->entries = (get_bits(f, 8)<<16) + (y<<8) + x; - ordered = get_bits(f,1); - c->sparse = ordered ? 0 : get_bits(f,1); - - if (c->dimensions == 0 && c->entries != 0) return error(f, VORBIS_invalid_setup); - - if (c->sparse) - lengths = (uint8 *) setup_temp_malloc(f, c->entries); - else - lengths = c->codeword_lengths = (uint8 *) setup_malloc(f, c->entries); - - if (!lengths) return error(f, VORBIS_outofmem); - - if (ordered) { - int current_entry = 0; - int current_length = get_bits(f,5) + 1; - while (current_entry < c->entries) { - int limit = c->entries - current_entry; - int n = get_bits(f, ilog(limit)); - if (current_length >= 32) return error(f, VORBIS_invalid_setup); - if (current_entry + n > (int) c->entries) { return error(f, VORBIS_invalid_setup); } - memset(lengths + current_entry, current_length, n); - current_entry += n; - ++current_length; - } - } else { - for (j=0; j < c->entries; ++j) { - int present = c->sparse ? get_bits(f,1) : 1; - if (present) { - lengths[j] = get_bits(f, 5) + 1; - ++total; - if (lengths[j] == 32) - return error(f, VORBIS_invalid_setup); - } else { - lengths[j] = NO_CODE; - } - } - } - - if (c->sparse && total >= c->entries >> 2) { - // convert sparse items to non-sparse! - if (c->entries > (int) f->setup_temp_memory_required) - f->setup_temp_memory_required = c->entries; - - c->codeword_lengths = (uint8 *) setup_malloc(f, c->entries); - if (c->codeword_lengths == NULL) return error(f, VORBIS_outofmem); - memcpy(c->codeword_lengths, lengths, c->entries); - setup_temp_free(f, lengths, c->entries); // note this is only safe if there have been no intervening temp mallocs! - lengths = c->codeword_lengths; - c->sparse = 0; - } - - // compute the size of the sorted tables - if (c->sparse) { - sorted_count = total; - } else { - sorted_count = 0; - #ifndef STB_VORBIS_NO_HUFFMAN_BINARY_SEARCH - for (j=0; j < c->entries; ++j) - if (lengths[j] > STB_VORBIS_FAST_HUFFMAN_LENGTH && lengths[j] != NO_CODE) - ++sorted_count; - #endif - } - - c->sorted_entries = sorted_count; - values = NULL; - - CHECK(f); - if (!c->sparse) { - c->codewords = (uint32 *) setup_malloc(f, sizeof(c->codewords[0]) * c->entries); - if (!c->codewords) return error(f, VORBIS_outofmem); - } else { - unsigned int size; - if (c->sorted_entries) { - c->codeword_lengths = (uint8 *) setup_malloc(f, c->sorted_entries); - if (!c->codeword_lengths) return error(f, VORBIS_outofmem); - c->codewords = (uint32 *) setup_temp_malloc(f, sizeof(*c->codewords) * c->sorted_entries); - if (!c->codewords) return error(f, VORBIS_outofmem); - values = (uint32 *) setup_temp_malloc(f, sizeof(*values) * c->sorted_entries); - if (!values) return error(f, VORBIS_outofmem); - } - size = c->entries + (sizeof(*c->codewords) + sizeof(*values)) * c->sorted_entries; - if (size > f->setup_temp_memory_required) - f->setup_temp_memory_required = size; - } - - if (!compute_codewords(c, lengths, c->entries, values)) { - if (c->sparse) setup_temp_free(f, values, 0); - return error(f, VORBIS_invalid_setup); - } - - if (c->sorted_entries) { - // allocate an extra slot for sentinels - c->sorted_codewords = (uint32 *) setup_malloc(f, sizeof(*c->sorted_codewords) * (c->sorted_entries+1)); - if (c->sorted_codewords == NULL) return error(f, VORBIS_outofmem); - // allocate an extra slot at the front so that c->sorted_values[-1] is defined - // so that we can catch that case without an extra if - c->sorted_values = ( int *) setup_malloc(f, sizeof(*c->sorted_values ) * (c->sorted_entries+1)); - if (c->sorted_values == NULL) return error(f, VORBIS_outofmem); - ++c->sorted_values; - c->sorted_values[-1] = -1; - compute_sorted_huffman(c, lengths, values); - } - - if (c->sparse) { - setup_temp_free(f, values, sizeof(*values)*c->sorted_entries); - setup_temp_free(f, c->codewords, sizeof(*c->codewords)*c->sorted_entries); - setup_temp_free(f, lengths, c->entries); - c->codewords = NULL; - } - - compute_accelerated_huffman(c); - - CHECK(f); - c->lookup_type = get_bits(f, 4); - if (c->lookup_type > 2) return error(f, VORBIS_invalid_setup); - if (c->lookup_type > 0) { - uint16 *mults; - c->minimum_value = float32_unpack(get_bits(f, 32)); - c->delta_value = float32_unpack(get_bits(f, 32)); - c->value_bits = get_bits(f, 4)+1; - c->sequence_p = get_bits(f,1); - if (c->lookup_type == 1) { - int values = lookup1_values(c->entries, c->dimensions); - if (values < 0) return error(f, VORBIS_invalid_setup); - c->lookup_values = (uint32) values; - } else { - c->lookup_values = c->entries * c->dimensions; - } - if (c->lookup_values == 0) return error(f, VORBIS_invalid_setup); - mults = (uint16 *) setup_temp_malloc(f, sizeof(mults[0]) * c->lookup_values); - if (mults == NULL) return error(f, VORBIS_outofmem); - for (j=0; j < (int) c->lookup_values; ++j) { - int q = get_bits(f, c->value_bits); - if (q == EOP) { setup_temp_free(f,mults,sizeof(mults[0])*c->lookup_values); return error(f, VORBIS_invalid_setup); } - mults[j] = q; - } - -#ifndef STB_VORBIS_DIVIDES_IN_CODEBOOK - if (c->lookup_type == 1) { - int len, sparse = c->sparse; - float last=0; - // pre-expand the lookup1-style multiplicands, to avoid a divide in the inner loop - if (sparse) { - if (c->sorted_entries == 0) goto skip; - c->multiplicands = (codetype *) setup_malloc(f, sizeof(c->multiplicands[0]) * c->sorted_entries * c->dimensions); - } else - c->multiplicands = (codetype *) setup_malloc(f, sizeof(c->multiplicands[0]) * c->entries * c->dimensions); - if (c->multiplicands == NULL) { setup_temp_free(f,mults,sizeof(mults[0])*c->lookup_values); return error(f, VORBIS_outofmem); } - len = sparse ? c->sorted_entries : c->entries; - for (j=0; j < len; ++j) { - unsigned int z = sparse ? c->sorted_values[j] : j; - unsigned int div=1; - for (k=0; k < c->dimensions; ++k) { - int off = (z / div) % c->lookup_values; - float val = mults[off]; - val = mults[off]*c->delta_value + c->minimum_value + last; - c->multiplicands[j*c->dimensions + k] = val; - if (c->sequence_p) - last = val; - if (k+1 < c->dimensions) { - if (div > UINT_MAX / (unsigned int) c->lookup_values) { - setup_temp_free(f, mults,sizeof(mults[0])*c->lookup_values); - return error(f, VORBIS_invalid_setup); - } - div *= c->lookup_values; - } - } - } - c->lookup_type = 2; - } - else -#endif - { - float last=0; - CHECK(f); - c->multiplicands = (codetype *) setup_malloc(f, sizeof(c->multiplicands[0]) * c->lookup_values); - if (c->multiplicands == NULL) { setup_temp_free(f, mults,sizeof(mults[0])*c->lookup_values); return error(f, VORBIS_outofmem); } - for (j=0; j < (int) c->lookup_values; ++j) { - float val = mults[j] * c->delta_value + c->minimum_value + last; - c->multiplicands[j] = val; - if (c->sequence_p) - last = val; - } - } -#ifndef STB_VORBIS_DIVIDES_IN_CODEBOOK - skip:; -#endif - setup_temp_free(f, mults, sizeof(mults[0])*c->lookup_values); - - CHECK(f); - } - CHECK(f); - } - - // time domain transfers (notused) - - x = get_bits(f, 6) + 1; - for (i=0; i < x; ++i) { - uint32 z = get_bits(f, 16); - if (z != 0) return error(f, VORBIS_invalid_setup); - } - - // Floors - f->floor_count = get_bits(f, 6)+1; - f->floor_config = (Floor *) setup_malloc(f, f->floor_count * sizeof(*f->floor_config)); - if (f->floor_config == NULL) return error(f, VORBIS_outofmem); - for (i=0; i < f->floor_count; ++i) { - f->floor_types[i] = get_bits(f, 16); - if (f->floor_types[i] > 1) return error(f, VORBIS_invalid_setup); - if (f->floor_types[i] == 0) { - Floor0 *g = &f->floor_config[i].floor0; - g->order = get_bits(f,8); - g->rate = get_bits(f,16); - g->bark_map_size = get_bits(f,16); - g->amplitude_bits = get_bits(f,6); - g->amplitude_offset = get_bits(f,8); - g->number_of_books = get_bits(f,4) + 1; - for (j=0; j < g->number_of_books; ++j) - g->book_list[j] = get_bits(f,8); - return error(f, VORBIS_feature_not_supported); - } else { - stbv__floor_ordering p[31*8+2]; - Floor1 *g = &f->floor_config[i].floor1; - int max_class = -1; - g->partitions = get_bits(f, 5); - for (j=0; j < g->partitions; ++j) { - g->partition_class_list[j] = get_bits(f, 4); - if (g->partition_class_list[j] > max_class) - max_class = g->partition_class_list[j]; - } - for (j=0; j <= max_class; ++j) { - g->class_dimensions[j] = get_bits(f, 3)+1; - g->class_subclasses[j] = get_bits(f, 2); - if (g->class_subclasses[j]) { - g->class_masterbooks[j] = get_bits(f, 8); - if (g->class_masterbooks[j] >= f->codebook_count) return error(f, VORBIS_invalid_setup); - } - for (k=0; k < 1 << g->class_subclasses[j]; ++k) { - g->subclass_books[j][k] = get_bits(f,8)-1; - if (g->subclass_books[j][k] >= f->codebook_count) return error(f, VORBIS_invalid_setup); - } - } - g->floor1_multiplier = get_bits(f,2)+1; - g->rangebits = get_bits(f,4); - g->Xlist[0] = 0; - g->Xlist[1] = 1 << g->rangebits; - g->values = 2; - for (j=0; j < g->partitions; ++j) { - int c = g->partition_class_list[j]; - for (k=0; k < g->class_dimensions[c]; ++k) { - g->Xlist[g->values] = get_bits(f, g->rangebits); - ++g->values; - } - } - // precompute the sorting - for (j=0; j < g->values; ++j) { - p[j].x = g->Xlist[j]; - p[j].id = j; - } - qsort(p, g->values, sizeof(p[0]), point_compare); - for (j=0; j < g->values-1; ++j) - if (p[j].x == p[j+1].x) - return error(f, VORBIS_invalid_setup); - for (j=0; j < g->values; ++j) - g->sorted_order[j] = (uint8) p[j].id; - // precompute the neighbors - for (j=2; j < g->values; ++j) { - int low = 0,hi = 0; - neighbors(g->Xlist, j, &low,&hi); - g->neighbors[j][0] = low; - g->neighbors[j][1] = hi; - } - - if (g->values > longest_floorlist) - longest_floorlist = g->values; - } - } - - // Residue - f->residue_count = get_bits(f, 6)+1; - f->residue_config = (Residue *) setup_malloc(f, f->residue_count * sizeof(f->residue_config[0])); - if (f->residue_config == NULL) return error(f, VORBIS_outofmem); - memset(f->residue_config, 0, f->residue_count * sizeof(f->residue_config[0])); - for (i=0; i < f->residue_count; ++i) { - uint8 residue_cascade[64]; - Residue *r = f->residue_config+i; - f->residue_types[i] = get_bits(f, 16); - if (f->residue_types[i] > 2) return error(f, VORBIS_invalid_setup); - r->begin = get_bits(f, 24); - r->end = get_bits(f, 24); - if (r->end < r->begin) return error(f, VORBIS_invalid_setup); - r->part_size = get_bits(f,24)+1; - r->classifications = get_bits(f,6)+1; - r->classbook = get_bits(f,8); - if (r->classbook >= f->codebook_count) return error(f, VORBIS_invalid_setup); - for (j=0; j < r->classifications; ++j) { - uint8 high_bits=0; - uint8 low_bits=get_bits(f,3); - if (get_bits(f,1)) - high_bits = get_bits(f,5); - residue_cascade[j] = high_bits*8 + low_bits; - } - r->residue_books = (short (*)[8]) setup_malloc(f, sizeof(r->residue_books[0]) * r->classifications); - if (r->residue_books == NULL) return error(f, VORBIS_outofmem); - for (j=0; j < r->classifications; ++j) { - for (k=0; k < 8; ++k) { - if (residue_cascade[j] & (1 << k)) { - r->residue_books[j][k] = get_bits(f, 8); - if (r->residue_books[j][k] >= f->codebook_count) return error(f, VORBIS_invalid_setup); - } else { - r->residue_books[j][k] = -1; - } - } - } - // precompute the classifications[] array to avoid inner-loop mod/divide - // call it 'classdata' since we already have r->classifications - r->classdata = (uint8 **) setup_malloc(f, sizeof(*r->classdata) * f->codebooks[r->classbook].entries); - if (!r->classdata) return error(f, VORBIS_outofmem); - memset(r->classdata, 0, sizeof(*r->classdata) * f->codebooks[r->classbook].entries); - for (j=0; j < f->codebooks[r->classbook].entries; ++j) { - int classwords = f->codebooks[r->classbook].dimensions; - int temp = j; - r->classdata[j] = (uint8 *) setup_malloc(f, sizeof(r->classdata[j][0]) * classwords); - if (r->classdata[j] == NULL) return error(f, VORBIS_outofmem); - for (k=classwords-1; k >= 0; --k) { - r->classdata[j][k] = temp % r->classifications; - temp /= r->classifications; - } - } - } - - f->mapping_count = get_bits(f,6)+1; - f->mapping = (Mapping *) setup_malloc(f, f->mapping_count * sizeof(*f->mapping)); - if (f->mapping == NULL) return error(f, VORBIS_outofmem); - memset(f->mapping, 0, f->mapping_count * sizeof(*f->mapping)); - for (i=0; i < f->mapping_count; ++i) { - Mapping *m = f->mapping + i; - int mapping_type = get_bits(f,16); - if (mapping_type != 0) return error(f, VORBIS_invalid_setup); - m->chan = (MappingChannel *) setup_malloc(f, f->channels * sizeof(*m->chan)); - if (m->chan == NULL) return error(f, VORBIS_outofmem); - if (get_bits(f,1)) - m->submaps = get_bits(f,4)+1; - else - m->submaps = 1; - if (m->submaps > max_submaps) - max_submaps = m->submaps; - if (get_bits(f,1)) { - m->coupling_steps = get_bits(f,8)+1; - if (m->coupling_steps > f->channels) return error(f, VORBIS_invalid_setup); - for (k=0; k < m->coupling_steps; ++k) { - m->chan[k].magnitude = get_bits(f, ilog(f->channels-1)); - m->chan[k].angle = get_bits(f, ilog(f->channels-1)); - if (m->chan[k].magnitude >= f->channels) return error(f, VORBIS_invalid_setup); - if (m->chan[k].angle >= f->channels) return error(f, VORBIS_invalid_setup); - if (m->chan[k].magnitude == m->chan[k].angle) return error(f, VORBIS_invalid_setup); - } - } else - m->coupling_steps = 0; - - // reserved field - if (get_bits(f,2)) return error(f, VORBIS_invalid_setup); - if (m->submaps > 1) { - for (j=0; j < f->channels; ++j) { - m->chan[j].mux = get_bits(f, 4); - if (m->chan[j].mux >= m->submaps) return error(f, VORBIS_invalid_setup); - } - } else - // @SPECIFICATION: this case is missing from the spec - for (j=0; j < f->channels; ++j) - m->chan[j].mux = 0; - - for (j=0; j < m->submaps; ++j) { - get_bits(f,8); // discard - m->submap_floor[j] = get_bits(f,8); - m->submap_residue[j] = get_bits(f,8); - if (m->submap_floor[j] >= f->floor_count) return error(f, VORBIS_invalid_setup); - if (m->submap_residue[j] >= f->residue_count) return error(f, VORBIS_invalid_setup); - } - } - - // Modes - f->mode_count = get_bits(f, 6)+1; - for (i=0; i < f->mode_count; ++i) { - Mode *m = f->mode_config+i; - m->blockflag = get_bits(f,1); - m->windowtype = get_bits(f,16); - m->transformtype = get_bits(f,16); - m->mapping = get_bits(f,8); - if (m->windowtype != 0) return error(f, VORBIS_invalid_setup); - if (m->transformtype != 0) return error(f, VORBIS_invalid_setup); - if (m->mapping >= f->mapping_count) return error(f, VORBIS_invalid_setup); - } - - flush_packet(f); - - f->previous_length = 0; - - for (i=0; i < f->channels; ++i) { - f->channel_buffers[i] = (float *) setup_malloc(f, sizeof(float) * f->blocksize_1); - f->previous_window[i] = (float *) setup_malloc(f, sizeof(float) * f->blocksize_1/2); - f->finalY[i] = (int16 *) setup_malloc(f, sizeof(int16) * longest_floorlist); - if (f->channel_buffers[i] == NULL || f->previous_window[i] == NULL || f->finalY[i] == NULL) return error(f, VORBIS_outofmem); - memset(f->channel_buffers[i], 0, sizeof(float) * f->blocksize_1); - #ifdef STB_VORBIS_NO_DEFER_FLOOR - f->floor_buffers[i] = (float *) setup_malloc(f, sizeof(float) * f->blocksize_1/2); - if (f->floor_buffers[i] == NULL) return error(f, VORBIS_outofmem); - #endif - } - - if (!init_blocksize(f, 0, f->blocksize_0)) return FALSE; - if (!init_blocksize(f, 1, f->blocksize_1)) return FALSE; - f->blocksize[0] = f->blocksize_0; - f->blocksize[1] = f->blocksize_1; - -#ifdef STB_VORBIS_DIVIDE_TABLE - if (integer_divide_table[1][1]==0) - for (i=0; i < DIVTAB_NUMER; ++i) - for (j=1; j < DIVTAB_DENOM; ++j) - integer_divide_table[i][j] = i / j; -#endif - - // compute how much temporary memory is needed - - // 1. - { - uint32 imdct_mem = (f->blocksize_1 * sizeof(float) >> 1); - uint32 classify_mem; - int i,max_part_read=0; - for (i=0; i < f->residue_count; ++i) { - Residue *r = f->residue_config + i; - unsigned int actual_size = f->blocksize_1 / 2; - unsigned int limit_r_begin = r->begin < actual_size ? r->begin : actual_size; - unsigned int limit_r_end = r->end < actual_size ? r->end : actual_size; - int n_read = limit_r_end - limit_r_begin; - int part_read = n_read / r->part_size; - if (part_read > max_part_read) - max_part_read = part_read; - } - #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE - classify_mem = f->channels * (sizeof(void*) + max_part_read * sizeof(uint8 *)); - #else - classify_mem = f->channels * (sizeof(void*) + max_part_read * sizeof(int *)); - #endif - - // maximum reasonable partition size is f->blocksize_1 - - f->temp_memory_required = classify_mem; - if (imdct_mem > f->temp_memory_required) - f->temp_memory_required = imdct_mem; - } - - - if (f->alloc.alloc_buffer) { - assert(f->temp_offset == f->alloc.alloc_buffer_length_in_bytes); - // check if there's enough temp memory so we don't error later - if (f->setup_offset + sizeof(*f) + f->temp_memory_required > (unsigned) f->temp_offset) - return error(f, VORBIS_outofmem); - } - - // @TODO: stb_vorbis_seek_start expects first_audio_page_offset to point to a page - // without PAGEFLAG_continued_packet, so this either points to the first page, or - // the page after the end of the headers. It might be cleaner to point to a page - // in the middle of the headers, when that's the page where the first audio packet - // starts, but we'd have to also correctly skip the end of any continued packet in - // stb_vorbis_seek_start. - if (f->next_seg == -1) { - f->first_audio_page_offset = stb_vorbis_get_file_offset(f); - } else { - f->first_audio_page_offset = 0; - } - - return TRUE; -} - -static void vorbis_deinit(stb_vorbis *p) -{ - int i,j; - - setup_free(p, p->vendor); - for (i=0; i < p->comment_list_length; ++i) { - setup_free(p, p->comment_list[i]); - } - setup_free(p, p->comment_list); - - if (p->residue_config) { - for (i=0; i < p->residue_count; ++i) { - Residue *r = p->residue_config+i; - if (r->classdata) { - for (j=0; j < p->codebooks[r->classbook].entries; ++j) - setup_free(p, r->classdata[j]); - setup_free(p, r->classdata); - } - setup_free(p, r->residue_books); - } - } - - if (p->codebooks) { - CHECK(p); - for (i=0; i < p->codebook_count; ++i) { - Codebook *c = p->codebooks + i; - setup_free(p, c->codeword_lengths); - setup_free(p, c->multiplicands); - setup_free(p, c->codewords); - setup_free(p, c->sorted_codewords); - // c->sorted_values[-1] is the first entry in the array - setup_free(p, c->sorted_values ? c->sorted_values-1 : NULL); - } - setup_free(p, p->codebooks); - } - setup_free(p, p->floor_config); - setup_free(p, p->residue_config); - if (p->mapping) { - for (i=0; i < p->mapping_count; ++i) - setup_free(p, p->mapping[i].chan); - setup_free(p, p->mapping); - } - CHECK(p); - for (i=0; i < p->channels && i < STB_VORBIS_MAX_CHANNELS; ++i) { - setup_free(p, p->channel_buffers[i]); - setup_free(p, p->previous_window[i]); - #ifdef STB_VORBIS_NO_DEFER_FLOOR - setup_free(p, p->floor_buffers[i]); - #endif - setup_free(p, p->finalY[i]); - } - for (i=0; i < 2; ++i) { - setup_free(p, p->A[i]); - setup_free(p, p->B[i]); - setup_free(p, p->C[i]); - setup_free(p, p->window[i]); - setup_free(p, p->bit_reverse[i]); - } - #ifndef STB_VORBIS_NO_STDIO - if (p->close_on_free) fclose(p->f); - #endif -} - -void stb_vorbis_close(stb_vorbis *p) -{ - if (p == NULL) return; - vorbis_deinit(p); - setup_free(p,p); -} - -static void vorbis_init(stb_vorbis *p, const stb_vorbis_alloc *z) -{ - memset(p, 0, sizeof(*p)); // NULL out all malloc'd pointers to start - if (z) { - p->alloc = *z; - p->alloc.alloc_buffer_length_in_bytes &= ~7; - p->temp_offset = p->alloc.alloc_buffer_length_in_bytes; - } - p->eof = 0; - p->error = VORBIS__no_error; - p->stream = NULL; - p->codebooks = NULL; - p->page_crc_tests = -1; - #ifndef STB_VORBIS_NO_STDIO - p->close_on_free = FALSE; - p->f = NULL; - #endif -} - -int stb_vorbis_get_sample_offset(stb_vorbis *f) -{ - if (f->current_loc_valid) - return f->current_loc; - else - return -1; -} - -stb_vorbis_info stb_vorbis_get_info(stb_vorbis *f) -{ - stb_vorbis_info d; - d.channels = f->channels; - d.sample_rate = f->sample_rate; - d.setup_memory_required = f->setup_memory_required; - d.setup_temp_memory_required = f->setup_temp_memory_required; - d.temp_memory_required = f->temp_memory_required; - d.max_frame_size = f->blocksize_1 >> 1; - return d; -} - -stb_vorbis_comment stb_vorbis_get_comment(stb_vorbis *f) -{ - stb_vorbis_comment d; - d.vendor = f->vendor; - d.comment_list_length = f->comment_list_length; - d.comment_list = f->comment_list; - return d; -} - -int stb_vorbis_get_error(stb_vorbis *f) -{ - int e = f->error; - f->error = VORBIS__no_error; - return e; -} - -static stb_vorbis * vorbis_alloc(stb_vorbis *f) -{ - stb_vorbis *p = (stb_vorbis *) setup_malloc(f, sizeof(*p)); - return p; -} - -#ifndef STB_VORBIS_NO_PUSHDATA_API - -void stb_vorbis_flush_pushdata(stb_vorbis *f) -{ - f->previous_length = 0; - f->page_crc_tests = 0; - f->discard_samples_deferred = 0; - f->current_loc_valid = FALSE; - f->first_decode = FALSE; - f->samples_output = 0; - f->channel_buffer_start = 0; - f->channel_buffer_end = 0; -} - -static int vorbis_search_for_page_pushdata(vorb *f, uint8 *data, int data_len) -{ - int i,n; - for (i=0; i < f->page_crc_tests; ++i) - f->scan[i].bytes_done = 0; - - // if we have room for more scans, search for them first, because - // they may cause us to stop early if their header is incomplete - if (f->page_crc_tests < STB_VORBIS_PUSHDATA_CRC_COUNT) { - if (data_len < 4) return 0; - data_len -= 3; // need to look for 4-byte sequence, so don't miss - // one that straddles a boundary - for (i=0; i < data_len; ++i) { - if (data[i] == 0x4f) { - if (0==memcmp(data+i, ogg_page_header, 4)) { - int j,len; - uint32 crc; - // make sure we have the whole page header - if (i+26 >= data_len || i+27+data[i+26] >= data_len) { - // only read up to this page start, so hopefully we'll - // have the whole page header start next time - data_len = i; - break; - } - // ok, we have it all; compute the length of the page - len = 27 + data[i+26]; - for (j=0; j < data[i+26]; ++j) - len += data[i+27+j]; - // scan everything up to the embedded crc (which we must 0) - crc = 0; - for (j=0; j < 22; ++j) - crc = crc32_update(crc, data[i+j]); - // now process 4 0-bytes - for ( ; j < 26; ++j) - crc = crc32_update(crc, 0); - // len is the total number of bytes we need to scan - n = f->page_crc_tests++; - f->scan[n].bytes_left = len-j; - f->scan[n].crc_so_far = crc; - f->scan[n].goal_crc = data[i+22] + (data[i+23] << 8) + (data[i+24]<<16) + (data[i+25]<<24); - // if the last frame on a page is continued to the next, then - // we can't recover the sample_loc immediately - if (data[i+27+data[i+26]-1] == 255) - f->scan[n].sample_loc = ~0; - else - f->scan[n].sample_loc = data[i+6] + (data[i+7] << 8) + (data[i+ 8]<<16) + (data[i+ 9]<<24); - f->scan[n].bytes_done = i+j; - if (f->page_crc_tests == STB_VORBIS_PUSHDATA_CRC_COUNT) - break; - // keep going if we still have room for more - } - } - } - } - - for (i=0; i < f->page_crc_tests;) { - uint32 crc; - int j; - int n = f->scan[i].bytes_done; - int m = f->scan[i].bytes_left; - if (m > data_len - n) m = data_len - n; - // m is the bytes to scan in the current chunk - crc = f->scan[i].crc_so_far; - for (j=0; j < m; ++j) - crc = crc32_update(crc, data[n+j]); - f->scan[i].bytes_left -= m; - f->scan[i].crc_so_far = crc; - if (f->scan[i].bytes_left == 0) { - // does it match? - if (f->scan[i].crc_so_far == f->scan[i].goal_crc) { - // Houston, we have page - data_len = n+m; // consumption amount is wherever that scan ended - f->page_crc_tests = -1; // drop out of page scan mode - f->previous_length = 0; // decode-but-don't-output one frame - f->next_seg = -1; // start a new page - f->current_loc = f->scan[i].sample_loc; // set the current sample location - // to the amount we'd have decoded had we decoded this page - f->current_loc_valid = f->current_loc != ~0U; - return data_len; - } - // delete entry - f->scan[i] = f->scan[--f->page_crc_tests]; - } else { - ++i; - } - } - - return data_len; -} - -// return value: number of bytes we used -int stb_vorbis_decode_frame_pushdata( - stb_vorbis *f, // the file we're decoding - const uint8 *data, int data_len, // the memory available for decoding - int *channels, // place to write number of float * buffers - float ***output, // place to write float ** array of float * buffers - int *samples // place to write number of output samples - ) -{ - int i; - int len,right,left; - - if (!IS_PUSH_MODE(f)) return error(f, VORBIS_invalid_api_mixing); - - if (f->page_crc_tests >= 0) { - *samples = 0; - return vorbis_search_for_page_pushdata(f, (uint8 *) data, data_len); - } - - f->stream = (uint8 *) data; - f->stream_end = (uint8 *) data + data_len; - f->error = VORBIS__no_error; - - // check that we have the entire packet in memory - if (!is_whole_packet_present(f)) { - *samples = 0; - return 0; - } - - if (!vorbis_decode_packet(f, &len, &left, &right)) { - // save the actual error we encountered - enum STBVorbisError error = f->error; - if (error == VORBIS_bad_packet_type) { - // flush and resynch - f->error = VORBIS__no_error; - while (get8_packet(f) != EOP) - if (f->eof) break; - *samples = 0; - return (int) (f->stream - data); - } - if (error == VORBIS_continued_packet_flag_invalid) { - if (f->previous_length == 0) { - // we may be resynching, in which case it's ok to hit one - // of these; just discard the packet - f->error = VORBIS__no_error; - while (get8_packet(f) != EOP) - if (f->eof) break; - *samples = 0; - return (int) (f->stream - data); - } - } - // if we get an error while parsing, what to do? - // well, it DEFINITELY won't work to continue from where we are! - stb_vorbis_flush_pushdata(f); - // restore the error that actually made us bail - f->error = error; - *samples = 0; - return 1; - } - - // success! - len = vorbis_finish_frame(f, len, left, right); - for (i=0; i < f->channels; ++i) - f->outputs[i] = f->channel_buffers[i] + left; - - if (channels) *channels = f->channels; - *samples = len; - *output = f->outputs; - return (int) (f->stream - data); -} - -stb_vorbis *stb_vorbis_open_pushdata( - const unsigned char *data, int data_len, // the memory available for decoding - int *data_used, // only defined if result is not NULL - int *error, const stb_vorbis_alloc *alloc) -{ - stb_vorbis *f, p; - vorbis_init(&p, alloc); - p.stream = (uint8 *) data; - p.stream_end = (uint8 *) data + data_len; - p.push_mode = TRUE; - if (!start_decoder(&p)) { - if (p.eof) - *error = VORBIS_need_more_data; - else - *error = p.error; - return NULL; - } - f = vorbis_alloc(&p); - if (f) { - *f = p; - *data_used = (int) (f->stream - data); - *error = 0; - return f; - } else { - vorbis_deinit(&p); - return NULL; - } -} -#endif // STB_VORBIS_NO_PUSHDATA_API - -unsigned int stb_vorbis_get_file_offset(stb_vorbis *f) -{ - #ifndef STB_VORBIS_NO_PUSHDATA_API - if (f->push_mode) return 0; - #endif - if (USE_MEMORY(f)) return (unsigned int) (f->stream - f->stream_start); - #ifndef STB_VORBIS_NO_STDIO - return (unsigned int) (ftell(f->f) - f->f_start); - #endif -} - -#ifndef STB_VORBIS_NO_PULLDATA_API -// -// DATA-PULLING API -// - -static uint32 vorbis_find_page(stb_vorbis *f, uint32 *end, uint32 *last) -{ - for(;;) { - int n; - if (f->eof) return 0; - n = get8(f); - if (n == 0x4f) { // page header candidate - unsigned int retry_loc = stb_vorbis_get_file_offset(f); - int i; - // check if we're off the end of a file_section stream - if (retry_loc - 25 > f->stream_len) - return 0; - // check the rest of the header - for (i=1; i < 4; ++i) - if (get8(f) != ogg_page_header[i]) - break; - if (f->eof) return 0; - if (i == 4) { - uint8 header[27]; - uint32 i, crc, goal, len; - for (i=0; i < 4; ++i) - header[i] = ogg_page_header[i]; - for (; i < 27; ++i) - header[i] = get8(f); - if (f->eof) return 0; - if (header[4] != 0) goto invalid; - goal = header[22] + (header[23] << 8) + (header[24]<<16) + (header[25]<<24); - for (i=22; i < 26; ++i) - header[i] = 0; - crc = 0; - for (i=0; i < 27; ++i) - crc = crc32_update(crc, header[i]); - len = 0; - for (i=0; i < header[26]; ++i) { - int s = get8(f); - crc = crc32_update(crc, s); - len += s; - } - if (len && f->eof) return 0; - for (i=0; i < len; ++i) - crc = crc32_update(crc, get8(f)); - // finished parsing probable page - if (crc == goal) { - // we could now check that it's either got the last - // page flag set, OR it's followed by the capture - // pattern, but I guess TECHNICALLY you could have - // a file with garbage between each ogg page and recover - // from it automatically? So even though that paranoia - // might decrease the chance of an invalid decode by - // another 2^32, not worth it since it would hose those - // invalid-but-useful files? - if (end) - *end = stb_vorbis_get_file_offset(f); - if (last) { - if (header[5] & 0x04) - *last = 1; - else - *last = 0; - } - set_file_offset(f, retry_loc-1); - return 1; - } - } - invalid: - // not a valid page, so rewind and look for next one - set_file_offset(f, retry_loc); - } - } -} - - -#define SAMPLE_unknown 0xffffffff - -// seeking is implemented with a binary search, which narrows down the range to -// 64K, before using a linear search (because finding the synchronization -// pattern can be expensive, and the chance we'd find the end page again is -// relatively high for small ranges) -// -// two initial interpolation-style probes are used at the start of the search -// to try to bound either side of the binary search sensibly, while still -// working in O(log n) time if they fail. - -static int get_seek_page_info(stb_vorbis *f, ProbedPage *z) -{ - uint8 header[27], lacing[255]; - int i,len; - - // record where the page starts - z->page_start = stb_vorbis_get_file_offset(f); - - // parse the header - getn(f, header, 27); - if (header[0] != 'O' || header[1] != 'g' || header[2] != 'g' || header[3] != 'S') - return 0; - getn(f, lacing, header[26]); - - // determine the length of the payload - len = 0; - for (i=0; i < header[26]; ++i) - len += lacing[i]; - - // this implies where the page ends - z->page_end = z->page_start + 27 + header[26] + len; - - // read the last-decoded sample out of the data - z->last_decoded_sample = header[6] + (header[7] << 8) + (header[8] << 16) + (header[9] << 24); - - // restore file state to where we were - set_file_offset(f, z->page_start); - return 1; -} - -// rarely used function to seek back to the preceding page while finding the -// start of a packet -static int go_to_page_before(stb_vorbis *f, unsigned int limit_offset) -{ - unsigned int previous_safe, end; - - // now we want to seek back 64K from the limit - if (limit_offset >= 65536 && limit_offset-65536 >= f->first_audio_page_offset) - previous_safe = limit_offset - 65536; - else - previous_safe = f->first_audio_page_offset; - - set_file_offset(f, previous_safe); - - while (vorbis_find_page(f, &end, NULL)) { - if (end >= limit_offset && stb_vorbis_get_file_offset(f) < limit_offset) - return 1; - set_file_offset(f, end); - } - - return 0; -} - -// implements the search logic for finding a page and starting decoding. if -// the function succeeds, current_loc_valid will be true and current_loc will -// be less than or equal to the provided sample number (the closer the -// better). -static int seek_to_sample_coarse(stb_vorbis *f, uint32 sample_number) -{ - ProbedPage left, right, mid; - int i, start_seg_with_known_loc, end_pos, page_start; - uint32 delta, stream_length, padding, last_sample_limit; - double offset = 0.0, bytes_per_sample = 0.0; - int probe = 0; - - // find the last page and validate the target sample - stream_length = stb_vorbis_stream_length_in_samples(f); - if (stream_length == 0) return error(f, VORBIS_seek_without_length); - if (sample_number > stream_length) return error(f, VORBIS_seek_invalid); - - // this is the maximum difference between the window-center (which is the - // actual granule position value), and the right-start (which the spec - // indicates should be the granule position (give or take one)). - padding = ((f->blocksize_1 - f->blocksize_0) >> 2); - if (sample_number < padding) - last_sample_limit = 0; - else - last_sample_limit = sample_number - padding; - - left = f->p_first; - while (left.last_decoded_sample == ~0U) { - // (untested) the first page does not have a 'last_decoded_sample' - set_file_offset(f, left.page_end); - if (!get_seek_page_info(f, &left)) goto error; - } - - right = f->p_last; - assert(right.last_decoded_sample != ~0U); - - // starting from the start is handled differently - if (last_sample_limit <= left.last_decoded_sample) { - if (stb_vorbis_seek_start(f)) { - if (f->current_loc > sample_number) - return error(f, VORBIS_seek_failed); - return 1; - } - return 0; - } - - while (left.page_end != right.page_start) { - assert(left.page_end < right.page_start); - // search range in bytes - delta = right.page_start - left.page_end; - if (delta <= 65536) { - // there's only 64K left to search - handle it linearly - set_file_offset(f, left.page_end); - } else { - if (probe < 2) { - if (probe == 0) { - // first probe (interpolate) - double data_bytes = right.page_end - left.page_start; - bytes_per_sample = data_bytes / right.last_decoded_sample; - offset = left.page_start + bytes_per_sample * (last_sample_limit - left.last_decoded_sample); - } else { - // second probe (try to bound the other side) - double error = ((double) last_sample_limit - mid.last_decoded_sample) * bytes_per_sample; - if (error >= 0 && error < 8000) error = 8000; - if (error < 0 && error > -8000) error = -8000; - offset += error * 2; - } - - // ensure the offset is valid - if (offset < left.page_end) - offset = left.page_end; - if (offset > right.page_start - 65536) - offset = right.page_start - 65536; - - set_file_offset(f, (unsigned int) offset); - } else { - // binary search for large ranges (offset by 32K to ensure - // we don't hit the right page) - set_file_offset(f, left.page_end + (delta / 2) - 32768); - } - - if (!vorbis_find_page(f, NULL, NULL)) goto error; - } - - for (;;) { - if (!get_seek_page_info(f, &mid)) goto error; - if (mid.last_decoded_sample != ~0U) break; - // (untested) no frames end on this page - set_file_offset(f, mid.page_end); - assert(mid.page_start < right.page_start); - } - - // if we've just found the last page again then we're in a tricky file, - // and we're close enough (if it wasn't an interpolation probe). - if (mid.page_start == right.page_start) { - if (probe >= 2 || delta <= 65536) - break; - } else { - if (last_sample_limit < mid.last_decoded_sample) - right = mid; - else - left = mid; - } - - ++probe; - } - - // seek back to start of the last packet - page_start = left.page_start; - set_file_offset(f, page_start); - if (!start_page(f)) return error(f, VORBIS_seek_failed); - end_pos = f->end_seg_with_known_loc; - assert(end_pos >= 0); - - for (;;) { - for (i = end_pos; i > 0; --i) - if (f->segments[i-1] != 255) - break; - - start_seg_with_known_loc = i; - - if (start_seg_with_known_loc > 0 || !(f->page_flag & PAGEFLAG_continued_packet)) - break; - - // (untested) the final packet begins on an earlier page - if (!go_to_page_before(f, page_start)) - goto error; - - page_start = stb_vorbis_get_file_offset(f); - if (!start_page(f)) goto error; - end_pos = f->segment_count - 1; - } - - // prepare to start decoding - f->current_loc_valid = FALSE; - f->last_seg = FALSE; - f->valid_bits = 0; - f->packet_bytes = 0; - f->bytes_in_seg = 0; - f->previous_length = 0; - f->next_seg = start_seg_with_known_loc; - - for (i = 0; i < start_seg_with_known_loc; i++) - skip(f, f->segments[i]); - - // start decoding (optimizable - this frame is generally discarded) - if (!vorbis_pump_first_frame(f)) - return 0; - if (f->current_loc > sample_number) - return error(f, VORBIS_seek_failed); - return 1; - -error: - // try to restore the file to a valid state - stb_vorbis_seek_start(f); - return error(f, VORBIS_seek_failed); -} - -// the same as vorbis_decode_initial, but without advancing -static int peek_decode_initial(vorb *f, int *p_left_start, int *p_left_end, int *p_right_start, int *p_right_end, int *mode) -{ - int bits_read, bytes_read; - - if (!vorbis_decode_initial(f, p_left_start, p_left_end, p_right_start, p_right_end, mode)) - return 0; - - // either 1 or 2 bytes were read, figure out which so we can rewind - bits_read = 1 + ilog(f->mode_count-1); - if (f->mode_config[*mode].blockflag) - bits_read += 2; - bytes_read = (bits_read + 7) / 8; - - f->bytes_in_seg += bytes_read; - f->packet_bytes -= bytes_read; - skip(f, -bytes_read); - if (f->next_seg == -1) - f->next_seg = f->segment_count - 1; - else - f->next_seg--; - f->valid_bits = 0; - - return 1; -} - -int stb_vorbis_seek_frame(stb_vorbis *f, unsigned int sample_number) -{ - uint32 max_frame_samples; - - if (IS_PUSH_MODE(f)) return error(f, VORBIS_invalid_api_mixing); - - // fast page-level search - if (!seek_to_sample_coarse(f, sample_number)) - return 0; - - assert(f->current_loc_valid); - assert(f->current_loc <= sample_number); - - // linear search for the relevant packet - max_frame_samples = (f->blocksize_1*3 - f->blocksize_0) >> 2; - while (f->current_loc < sample_number) { - int left_start, left_end, right_start, right_end, mode, frame_samples; - if (!peek_decode_initial(f, &left_start, &left_end, &right_start, &right_end, &mode)) - return error(f, VORBIS_seek_failed); - // calculate the number of samples returned by the next frame - frame_samples = right_start - left_start; - if (f->current_loc + frame_samples > sample_number) { - return 1; // the next frame will contain the sample - } else if (f->current_loc + frame_samples + max_frame_samples > sample_number) { - // there's a chance the frame after this could contain the sample - vorbis_pump_first_frame(f); - } else { - // this frame is too early to be relevant - f->current_loc += frame_samples; - f->previous_length = 0; - maybe_start_packet(f); - flush_packet(f); - } - } - // the next frame should start with the sample - if (f->current_loc != sample_number) return error(f, VORBIS_seek_failed); - return 1; -} - -int stb_vorbis_seek(stb_vorbis *f, unsigned int sample_number) -{ - if (!stb_vorbis_seek_frame(f, sample_number)) - return 0; - - if (sample_number != f->current_loc) { - int n; - uint32 frame_start = f->current_loc; - stb_vorbis_get_frame_float(f, &n, NULL); - assert(sample_number > frame_start); - assert(f->channel_buffer_start + (int) (sample_number-frame_start) <= f->channel_buffer_end); - f->channel_buffer_start += (sample_number - frame_start); - } - - return 1; -} - -int stb_vorbis_seek_start(stb_vorbis *f) -{ - if (IS_PUSH_MODE(f)) { return error(f, VORBIS_invalid_api_mixing); } - set_file_offset(f, f->first_audio_page_offset); - f->previous_length = 0; - f->first_decode = TRUE; - f->next_seg = -1; - return vorbis_pump_first_frame(f); -} - -unsigned int stb_vorbis_stream_length_in_samples(stb_vorbis *f) -{ - unsigned int restore_offset, previous_safe; - unsigned int end, last_page_loc; - - if (IS_PUSH_MODE(f)) return error(f, VORBIS_invalid_api_mixing); - if (!f->total_samples) { - unsigned int last; - uint32 lo,hi; - char header[6]; - - // first, store the current decode position so we can restore it - restore_offset = stb_vorbis_get_file_offset(f); - - // now we want to seek back 64K from the end (the last page must - // be at most a little less than 64K, but let's allow a little slop) - if (f->stream_len >= 65536 && f->stream_len-65536 >= f->first_audio_page_offset) - previous_safe = f->stream_len - 65536; - else - previous_safe = f->first_audio_page_offset; - - set_file_offset(f, previous_safe); - // previous_safe is now our candidate 'earliest known place that seeking - // to will lead to the final page' - - if (!vorbis_find_page(f, &end, &last)) { - // if we can't find a page, we're hosed! - f->error = VORBIS_cant_find_last_page; - f->total_samples = 0xffffffff; - goto done; - } - - // check if there are more pages - last_page_loc = stb_vorbis_get_file_offset(f); - - // stop when the last_page flag is set, not when we reach eof; - // this allows us to stop short of a 'file_section' end without - // explicitly checking the length of the section - while (!last) { - set_file_offset(f, end); - if (!vorbis_find_page(f, &end, &last)) { - // the last page we found didn't have the 'last page' flag - // set. whoops! - break; - } - previous_safe = last_page_loc+1; - last_page_loc = stb_vorbis_get_file_offset(f); - } - - set_file_offset(f, last_page_loc); - - // parse the header - getn(f, (unsigned char *)header, 6); - // extract the absolute granule position - lo = get32(f); - hi = get32(f); - if (lo == 0xffffffff && hi == 0xffffffff) { - f->error = VORBIS_cant_find_last_page; - f->total_samples = SAMPLE_unknown; - goto done; - } - if (hi) - lo = 0xfffffffe; // saturate - f->total_samples = lo; - - f->p_last.page_start = last_page_loc; - f->p_last.page_end = end; - f->p_last.last_decoded_sample = lo; - - done: - set_file_offset(f, restore_offset); - } - return f->total_samples == SAMPLE_unknown ? 0 : f->total_samples; -} - -float stb_vorbis_stream_length_in_seconds(stb_vorbis *f) -{ - return stb_vorbis_stream_length_in_samples(f) / (float) f->sample_rate; -} - - - -int stb_vorbis_get_frame_float(stb_vorbis *f, int *channels, float ***output) -{ - int len, right,left,i; - if (IS_PUSH_MODE(f)) return error(f, VORBIS_invalid_api_mixing); - - if (!vorbis_decode_packet(f, &len, &left, &right)) { - f->channel_buffer_start = f->channel_buffer_end = 0; - return 0; - } - - len = vorbis_finish_frame(f, len, left, right); - for (i=0; i < f->channels; ++i) - f->outputs[i] = f->channel_buffers[i] + left; - - f->channel_buffer_start = left; - f->channel_buffer_end = left+len; - - if (channels) *channels = f->channels; - if (output) *output = f->outputs; - return len; -} - -#ifndef STB_VORBIS_NO_STDIO - -stb_vorbis * stb_vorbis_open_file_section(FILE *file, int close_on_free, int *error, const stb_vorbis_alloc *alloc, unsigned int length) -{ - stb_vorbis *f, p; - vorbis_init(&p, alloc); - p.f = file; - p.f_start = (uint32) ftell(file); - p.stream_len = length; - p.close_on_free = close_on_free; - if (start_decoder(&p)) { - f = vorbis_alloc(&p); - if (f) { - *f = p; - vorbis_pump_first_frame(f); - return f; - } - } - if (error) *error = p.error; - vorbis_deinit(&p); - return NULL; -} - -stb_vorbis * stb_vorbis_open_file(FILE *file, int close_on_free, int *error, const stb_vorbis_alloc *alloc) -{ - unsigned int len, start; - start = (unsigned int) ftell(file); - fseek(file, 0, SEEK_END); - len = (unsigned int) (ftell(file) - start); - fseek(file, start, SEEK_SET); - return stb_vorbis_open_file_section(file, close_on_free, error, alloc, len); -} - -stb_vorbis * stb_vorbis_open_filename(const char *filename, int *error, const stb_vorbis_alloc *alloc) -{ - FILE *f; -#if defined(_WIN32) && defined(__STDC_WANT_SECURE_LIB__) - if (0 != fopen_s(&f, filename, "rb")) - f = NULL; -#else - f = fopen(filename, "rb"); -#endif - if (f) - return stb_vorbis_open_file(f, TRUE, error, alloc); - if (error) *error = VORBIS_file_open_failure; - return NULL; -} -#endif // STB_VORBIS_NO_STDIO - -stb_vorbis * stb_vorbis_open_memory(const unsigned char *data, int len, int *error, const stb_vorbis_alloc *alloc) -{ - stb_vorbis *f, p; - if (data == NULL) return NULL; - vorbis_init(&p, alloc); - p.stream = (uint8 *) data; - p.stream_end = (uint8 *) data + len; - p.stream_start = (uint8 *) p.stream; - p.stream_len = len; - p.push_mode = FALSE; - if (start_decoder(&p)) { - f = vorbis_alloc(&p); - if (f) { - *f = p; - vorbis_pump_first_frame(f); - if (error) *error = VORBIS__no_error; - return f; - } - } - if (error) *error = p.error; - vorbis_deinit(&p); - return NULL; -} - -#ifndef STB_VORBIS_NO_INTEGER_CONVERSION -#define PLAYBACK_MONO 1 -#define PLAYBACK_LEFT 2 -#define PLAYBACK_RIGHT 4 - -#define L (PLAYBACK_LEFT | PLAYBACK_MONO) -#define C (PLAYBACK_LEFT | PLAYBACK_RIGHT | PLAYBACK_MONO) -#define R (PLAYBACK_RIGHT | PLAYBACK_MONO) - -static int8 channel_position[7][6] = -{ - { 0 }, - { C }, - { L, R }, - { L, C, R }, - { L, R, L, R }, - { L, C, R, L, R }, - { L, C, R, L, R, C }, -}; - - -#ifndef STB_VORBIS_NO_FAST_SCALED_FLOAT - typedef union { - float f; - int i; - } float_conv; - typedef char stb_vorbis_float_size_test[sizeof(float)==4 && sizeof(int) == 4]; - #define FASTDEF(x) float_conv x - // add (1<<23) to convert to int, then divide by 2^SHIFT, then add 0.5/2^SHIFT to round - #define MAGIC(SHIFT) (1.5f * (1 << (23-SHIFT)) + 0.5f/(1 << SHIFT)) - #define ADDEND(SHIFT) (((150-SHIFT) << 23) + (1 << 22)) - #define FAST_SCALED_FLOAT_TO_INT(temp,x,s) (temp.f = (x) + MAGIC(s), temp.i - ADDEND(s)) - #define check_endianness() -#else - #define FAST_SCALED_FLOAT_TO_INT(temp,x,s) ((int) ((x) * (1 << (s)))) - #define check_endianness() - #define FASTDEF(x) -#endif - -static void copy_samples(short *dest, float *src, int len) -{ - int i; - check_endianness(); - for (i=0; i < len; ++i) { - FASTDEF(temp); - int v = FAST_SCALED_FLOAT_TO_INT(temp, src[i],15); - if ((unsigned int) (v + 32768) > 65535) - v = v < 0 ? -32768 : 32767; - dest[i] = v; - } -} - -static void compute_samples(int mask, short *output, int num_c, float **data, int d_offset, int len) -{ - #define BUFFER_SIZE 32 - float buffer[BUFFER_SIZE]; - int i,j,o,n = BUFFER_SIZE; - check_endianness(); - for (o = 0; o < len; o += BUFFER_SIZE) { - memset(buffer, 0, sizeof(buffer)); - if (o + n > len) n = len - o; - for (j=0; j < num_c; ++j) { - if (channel_position[num_c][j] & mask) { - for (i=0; i < n; ++i) - buffer[i] += data[j][d_offset+o+i]; - } - } - for (i=0; i < n; ++i) { - FASTDEF(temp); - int v = FAST_SCALED_FLOAT_TO_INT(temp,buffer[i],15); - if ((unsigned int) (v + 32768) > 65535) - v = v < 0 ? -32768 : 32767; - output[o+i] = v; - } - } -} - -static void compute_stereo_samples(short *output, int num_c, float **data, int d_offset, int len) -{ - #define BUFFER_SIZE 32 - float buffer[BUFFER_SIZE]; - int i,j,o,n = BUFFER_SIZE >> 1; - // o is the offset in the source data - check_endianness(); - for (o = 0; o < len; o += BUFFER_SIZE >> 1) { - // o2 is the offset in the output data - int o2 = o << 1; - memset(buffer, 0, sizeof(buffer)); - if (o + n > len) n = len - o; - for (j=0; j < num_c; ++j) { - int m = channel_position[num_c][j] & (PLAYBACK_LEFT | PLAYBACK_RIGHT); - if (m == (PLAYBACK_LEFT | PLAYBACK_RIGHT)) { - for (i=0; i < n; ++i) { - buffer[i*2+0] += data[j][d_offset+o+i]; - buffer[i*2+1] += data[j][d_offset+o+i]; - } - } else if (m == PLAYBACK_LEFT) { - for (i=0; i < n; ++i) { - buffer[i*2+0] += data[j][d_offset+o+i]; - } - } else if (m == PLAYBACK_RIGHT) { - for (i=0; i < n; ++i) { - buffer[i*2+1] += data[j][d_offset+o+i]; - } - } - } - for (i=0; i < (n<<1); ++i) { - FASTDEF(temp); - int v = FAST_SCALED_FLOAT_TO_INT(temp,buffer[i],15); - if ((unsigned int) (v + 32768) > 65535) - v = v < 0 ? -32768 : 32767; - output[o2+i] = v; - } - } -} - -static void convert_samples_short(int buf_c, short **buffer, int b_offset, int data_c, float **data, int d_offset, int samples) -{ - int i; - if (buf_c != data_c && buf_c <= 2 && data_c <= 6) { - static int channel_selector[3][2] = { {0}, {PLAYBACK_MONO}, {PLAYBACK_LEFT, PLAYBACK_RIGHT} }; - for (i=0; i < buf_c; ++i) - compute_samples(channel_selector[buf_c][i], buffer[i]+b_offset, data_c, data, d_offset, samples); - } else { - int limit = buf_c < data_c ? buf_c : data_c; - for (i=0; i < limit; ++i) - copy_samples(buffer[i]+b_offset, data[i]+d_offset, samples); - for ( ; i < buf_c; ++i) - memset(buffer[i]+b_offset, 0, sizeof(short) * samples); - } -} - -int stb_vorbis_get_frame_short(stb_vorbis *f, int num_c, short **buffer, int num_samples) -{ - float **output = NULL; - int len = stb_vorbis_get_frame_float(f, NULL, &output); - if (len > num_samples) len = num_samples; - if (len) - convert_samples_short(num_c, buffer, 0, f->channels, output, 0, len); - return len; -} - -static void convert_channels_short_interleaved(int buf_c, short *buffer, int data_c, float **data, int d_offset, int len) -{ - int i; - check_endianness(); - if (buf_c != data_c && buf_c <= 2 && data_c <= 6) { - assert(buf_c == 2); - for (i=0; i < buf_c; ++i) - compute_stereo_samples(buffer, data_c, data, d_offset, len); - } else { - int limit = buf_c < data_c ? buf_c : data_c; - int j; - for (j=0; j < len; ++j) { - for (i=0; i < limit; ++i) { - FASTDEF(temp); - float f = data[i][d_offset+j]; - int v = FAST_SCALED_FLOAT_TO_INT(temp, f,15);//data[i][d_offset+j],15); - if ((unsigned int) (v + 32768) > 65535) - v = v < 0 ? -32768 : 32767; - *buffer++ = v; - } - for ( ; i < buf_c; ++i) - *buffer++ = 0; - } - } -} - -int stb_vorbis_get_frame_short_interleaved(stb_vorbis *f, int num_c, short *buffer, int num_shorts) -{ - float **output; - int len; - if (num_c == 1) return stb_vorbis_get_frame_short(f,num_c,&buffer, num_shorts); - len = stb_vorbis_get_frame_float(f, NULL, &output); - if (len) { - if (len*num_c > num_shorts) len = num_shorts / num_c; - convert_channels_short_interleaved(num_c, buffer, f->channels, output, 0, len); - } - return len; -} - -int stb_vorbis_get_samples_short_interleaved(stb_vorbis *f, int channels, short *buffer, int num_shorts) -{ - float **outputs; - int len = num_shorts / channels; - int n=0; - int z = f->channels; - if (z > channels) z = channels; - while (n < len) { - int k = f->channel_buffer_end - f->channel_buffer_start; - if (n+k >= len) k = len - n; - if (k) - convert_channels_short_interleaved(channels, buffer, f->channels, f->channel_buffers, f->channel_buffer_start, k); - buffer += k*channels; - n += k; - f->channel_buffer_start += k; - if (n == len) break; - if (!stb_vorbis_get_frame_float(f, NULL, &outputs)) break; - } - return n; -} - -int stb_vorbis_get_samples_short(stb_vorbis *f, int channels, short **buffer, int len) -{ - float **outputs; - int n=0; - int z = f->channels; - if (z > channels) z = channels; - while (n < len) { - int k = f->channel_buffer_end - f->channel_buffer_start; - if (n+k >= len) k = len - n; - if (k) - convert_samples_short(channels, buffer, n, f->channels, f->channel_buffers, f->channel_buffer_start, k); - n += k; - f->channel_buffer_start += k; - if (n == len) break; - if (!stb_vorbis_get_frame_float(f, NULL, &outputs)) break; - } - return n; -} - -#ifndef STB_VORBIS_NO_STDIO -int stb_vorbis_decode_filename(const char *filename, int *channels, int *sample_rate, short **output) -{ - int data_len, offset, total, limit, error; - short *data; - stb_vorbis *v = stb_vorbis_open_filename(filename, &error, NULL); - if (v == NULL) return -1; - limit = v->channels * 4096; - *channels = v->channels; - if (sample_rate) - *sample_rate = v->sample_rate; - offset = data_len = 0; - total = limit; - data = (short *) malloc(total * sizeof(*data)); - if (data == NULL) { - stb_vorbis_close(v); - return -2; - } - for (;;) { - int n = stb_vorbis_get_frame_short_interleaved(v, v->channels, data+offset, total-offset); - if (n == 0) break; - data_len += n; - offset += n * v->channels; - if (offset + limit > total) { - short *data2; - total *= 2; - data2 = (short *) realloc(data, total * sizeof(*data)); - if (data2 == NULL) { - free(data); - stb_vorbis_close(v); - return -2; - } - data = data2; - } - } - *output = data; - stb_vorbis_close(v); - return data_len; -} -#endif // NO_STDIO - -int stb_vorbis_decode_memory(const uint8 *mem, int len, int *channels, int *sample_rate, short **output) -{ - int data_len, offset, total, limit, error; - short *data; - stb_vorbis *v = stb_vorbis_open_memory(mem, len, &error, NULL); - if (v == NULL) return -1; - limit = v->channels * 4096; - *channels = v->channels; - if (sample_rate) - *sample_rate = v->sample_rate; - offset = data_len = 0; - total = limit; - data = (short *) malloc(total * sizeof(*data)); - if (data == NULL) { - stb_vorbis_close(v); - return -2; - } - for (;;) { - int n = stb_vorbis_get_frame_short_interleaved(v, v->channels, data+offset, total-offset); - if (n == 0) break; - data_len += n; - offset += n * v->channels; - if (offset + limit > total) { - short *data2; - total *= 2; - data2 = (short *) realloc(data, total * sizeof(*data)); - if (data2 == NULL) { - free(data); - stb_vorbis_close(v); - return -2; - } - data = data2; - } - } - *output = data; - stb_vorbis_close(v); - return data_len; -} -#endif // STB_VORBIS_NO_INTEGER_CONVERSION - -int stb_vorbis_get_samples_float_interleaved(stb_vorbis *f, int channels, float *buffer, int num_floats) -{ - float **outputs; - int len = num_floats / channels; - int n=0; - int z = f->channels; - if (z > channels) z = channels; - while (n < len) { - int i,j; - int k = f->channel_buffer_end - f->channel_buffer_start; - if (n+k >= len) k = len - n; - for (j=0; j < k; ++j) { - for (i=0; i < z; ++i) - *buffer++ = f->channel_buffers[i][f->channel_buffer_start+j]; - for ( ; i < channels; ++i) - *buffer++ = 0; - } - n += k; - f->channel_buffer_start += k; - if (n == len) - break; - if (!stb_vorbis_get_frame_float(f, NULL, &outputs)) - break; - } - return n; -} - -int stb_vorbis_get_samples_float(stb_vorbis *f, int channels, float **buffer, int num_samples) -{ - float **outputs; - int n=0; - int z = f->channels; - if (z > channels) z = channels; - while (n < num_samples) { - int i; - int k = f->channel_buffer_end - f->channel_buffer_start; - if (n+k >= num_samples) k = num_samples - n; - if (k) { - for (i=0; i < z; ++i) - memcpy(buffer[i]+n, f->channel_buffers[i]+f->channel_buffer_start, sizeof(float)*k); - for ( ; i < channels; ++i) - memset(buffer[i]+n, 0, sizeof(float) * k); - } - n += k; - f->channel_buffer_start += k; - if (n == num_samples) - break; - if (!stb_vorbis_get_frame_float(f, NULL, &outputs)) - break; - } - return n; -} -#endif // STB_VORBIS_NO_PULLDATA_API - -/* Version history - 1.17 - 2019-07-08 - fix CVE-2019-13217, -13218, -13219, -13220, -13221, -13222, -13223 - found with Mayhem by ForAllSecure - 1.16 - 2019-03-04 - fix warnings - 1.15 - 2019-02-07 - explicit failure if Ogg Skeleton data is found - 1.14 - 2018-02-11 - delete bogus dealloca usage - 1.13 - 2018-01-29 - fix truncation of last frame (hopefully) - 1.12 - 2017-11-21 - limit residue begin/end to blocksize/2 to avoid large temp allocs in bad/corrupt files - 1.11 - 2017-07-23 - fix MinGW compilation - 1.10 - 2017-03-03 - more robust seeking; fix negative ilog(); clear error in open_memory - 1.09 - 2016-04-04 - back out 'avoid discarding last frame' fix from previous version - 1.08 - 2016-04-02 - fixed multiple warnings; fix setup memory leaks; - avoid discarding last frame of audio data - 1.07 - 2015-01-16 - fixed some warnings, fix mingw, const-correct API - some more crash fixes when out of memory or with corrupt files - 1.06 - 2015-08-31 - full, correct support for seeking API (Dougall Johnson) - some crash fixes when out of memory or with corrupt files - 1.05 - 2015-04-19 - don't define __forceinline if it's redundant - 1.04 - 2014-08-27 - fix missing const-correct case in API - 1.03 - 2014-08-07 - Warning fixes - 1.02 - 2014-07-09 - Declare qsort compare function _cdecl on windows - 1.01 - 2014-06-18 - fix stb_vorbis_get_samples_float - 1.0 - 2014-05-26 - fix memory leaks; fix warnings; fix bugs in multichannel - (API change) report sample rate for decode-full-file funcs - 0.99996 - bracket #include <malloc.h> for macintosh compilation by Laurent Gomila - 0.99995 - use union instead of pointer-cast for fast-float-to-int to avoid alias-optimization problem - 0.99994 - change fast-float-to-int to work in single-precision FPU mode, remove endian-dependence - 0.99993 - remove assert that fired on legal files with empty tables - 0.99992 - rewind-to-start - 0.99991 - bugfix to stb_vorbis_get_samples_short by Bernhard Wodo - 0.9999 - (should have been 0.99990) fix no-CRT support, compiling as C++ - 0.9998 - add a full-decode function with a memory source - 0.9997 - fix a bug in the read-from-FILE case in 0.9996 addition - 0.9996 - query length of vorbis stream in samples/seconds - 0.9995 - bugfix to another optimization that only happened in certain files - 0.9994 - bugfix to one of the optimizations that caused significant (but inaudible?) errors - 0.9993 - performance improvements; runs in 99% to 104% of time of reference implementation - 0.9992 - performance improvement of IMDCT; now performs close to reference implementation - 0.9991 - performance improvement of IMDCT - 0.999 - (should have been 0.9990) performance improvement of IMDCT - 0.998 - no-CRT support from Casey Muratori - 0.997 - bugfixes for bugs found by Terje Mathisen - 0.996 - bugfix: fast-huffman decode initialized incorrectly for sparse codebooks; fixing gives 10% speedup - found by Terje Mathisen - 0.995 - bugfix: fix to 'effective' overrun detection - found by Terje Mathisen - 0.994 - bugfix: garbage decode on final VQ symbol of a non-multiple - found by Terje Mathisen - 0.993 - bugfix: pushdata API required 1 extra byte for empty page (failed to consume final page if empty) - found by Terje Mathisen - 0.992 - fixes for MinGW warning - 0.991 - turn fast-float-conversion on by default - 0.990 - fix push-mode seek recovery if you seek into the headers - 0.98b - fix to bad release of 0.98 - 0.98 - fix push-mode seek recovery; robustify float-to-int and support non-fast mode - 0.97 - builds under c++ (typecasting, don't use 'class' keyword) - 0.96 - somehow MY 0.95 was right, but the web one was wrong, so here's my 0.95 rereleased as 0.96, fixes a typo in the clamping code - 0.95 - clamping code for 16-bit functions - 0.94 - not publically released - 0.93 - fixed all-zero-floor case (was decoding garbage) - 0.92 - fixed a memory leak - 0.91 - conditional compiles to omit parts of the API and the infrastructure to support them: STB_VORBIS_NO_PULLDATA_API, STB_VORBIS_NO_PUSHDATA_API, STB_VORBIS_NO_STDIO, STB_VORBIS_NO_INTEGER_CONVERSION - 0.90 - first public release -*/ - -#endif // STB_VORBIS_HEADER_ONLY - - -/* ------------------------------------------------------------------------------- -This software is available under 2 licenses -- choose whichever you prefer. ------------------------------------------------------------------------------- -ALTERNATIVE A - MIT License -Copyright (c) 2017 Sean Barrett -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. ------------------------------------------------------------------------------- -ALTERNATIVE B - Public Domain (www.unlicense.org) -This is free and unencumbered software released into the public domain. -Anyone is free to copy, modify, publish, use, compile, sell, or distribute this -software, either in source code form or as a compiled binary, for any purpose, -commercial or non-commercial, and by any means. -In jurisdictions that recognize copyright laws, the author or authors of this -software dedicate any and all copyright interest in the software to the public -domain. We make this dedication for the benefit of the public at large and to -the detriment of our heirs and successors. We intend this dedication to be an -overt act of relinquishment in perpetuity of all present and future rights to -this software under copyright law. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN -ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ------------------------------------------------------------------------------- -*/ diff --git a/thirdparty/misc/stb_vorbis.h b/thirdparty/misc/stb_vorbis.h deleted file mode 100644 index 357efcd5fc..0000000000 --- a/thirdparty/misc/stb_vorbis.h +++ /dev/null @@ -1,2 +0,0 @@ -#define STB_VORBIS_HEADER_ONLY -#include "stb_vorbis.c" |