diff options
550 files changed, 19051 insertions, 12607 deletions
diff --git a/.github/workflows/static_checks.yml b/.github/workflows/static_checks.yml index 6133780688..b47ef135a2 100644 --- a/.github/workflows/static_checks.yml +++ b/.github/workflows/static_checks.yml @@ -86,6 +86,7 @@ jobs: - name: Documentation checks run: | + doc/tools/doc_status.py doc/classes modules/*/doc_classes platform/*/doc_classes doc/tools/make_rst.py --dry-run --color doc/classes modules platform - name: Style checks via clang-format (clang_format.sh) diff --git a/core/config/project_settings.cpp b/core/config/project_settings.cpp index 79fab50882..715ed61770 100644 --- a/core/config/project_settings.cpp +++ b/core/config/project_settings.cpp @@ -1344,7 +1344,7 @@ ProjectSettings::ProjectSettings() { GLOBAL_DEF_RST(PropertyInfo(Variant::INT, "rendering/occlusion_culling/bvh_build_quality", PROPERTY_HINT_ENUM, "Low,Medium,High"), 2); GLOBAL_DEF(PropertyInfo(Variant::INT, "memory/limits/multithreaded_server/rid_pool_prealloc", PROPERTY_HINT_RANGE, "0,500,1"), 60); // No negative and limit to 500 due to crashes. GLOBAL_DEF_RST("internationalization/rendering/force_right_to_left_layout_direction", false); - GLOBAL_DEF_BASIC(PropertyInfo(Variant::INT, "internationalization/rendering/root_node_layout_direction", PROPERTY_HINT_RANGE, "Based on Locale,Left-to-Right,Right-to-Left"), 0); + GLOBAL_DEF_BASIC(PropertyInfo(Variant::INT, "internationalization/rendering/root_node_layout_direction", PROPERTY_HINT_ENUM, "Based on Locale,Left-to-Right,Right-to-Left"), 0); GLOBAL_DEF(PropertyInfo(Variant::INT, "gui/timers/incremental_search_max_interval_msec", PROPERTY_HINT_RANGE, "0,10000,1,or_greater"), 2000); diff --git a/core/core_bind.cpp b/core/core_bind.cpp index 2d0d24406c..a73b198be2 100644 --- a/core/core_bind.cpp +++ b/core/core_bind.cpp @@ -442,6 +442,10 @@ bool OS::has_feature(const String &p_feature) const { } } +bool OS::is_sandboxed() const { + return ::OS::get_singleton()->is_sandboxed(); +} + uint64_t OS::get_static_memory_usage() const { return ::OS::get_singleton()->get_static_memory_usage(); } @@ -545,6 +549,10 @@ Vector<String> OS::get_granted_permissions() const { return ::OS::get_singleton()->get_granted_permissions(); } +void OS::revoke_granted_permissions() { + ::OS::get_singleton()->revoke_granted_permissions(); +} + String OS::get_unique_id() const { return ::OS::get_singleton()->get_unique_id(); } @@ -636,10 +644,12 @@ void OS::_bind_methods() { ClassDB::bind_method(D_METHOD("get_main_thread_id"), &OS::get_main_thread_id); ClassDB::bind_method(D_METHOD("has_feature", "tag_name"), &OS::has_feature); + ClassDB::bind_method(D_METHOD("is_sandboxed"), &OS::is_sandboxed); ClassDB::bind_method(D_METHOD("request_permission", "name"), &OS::request_permission); ClassDB::bind_method(D_METHOD("request_permissions"), &OS::request_permissions); ClassDB::bind_method(D_METHOD("get_granted_permissions"), &OS::get_granted_permissions); + ClassDB::bind_method(D_METHOD("revoke_granted_permissions"), &OS::revoke_granted_permissions); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "low_processor_usage_mode"), "set_low_processor_usage_mode", "is_in_low_processor_usage_mode"); ADD_PROPERTY(PropertyInfo(Variant::INT, "low_processor_usage_mode_sleep_usec"), "set_low_processor_usage_mode_sleep_usec", "get_low_processor_usage_mode_sleep_usec"); diff --git a/core/core_bind.h b/core/core_bind.h index 6b25510b14..dc0b2a1cf5 100644 --- a/core/core_bind.h +++ b/core/core_bind.h @@ -230,14 +230,16 @@ public: String get_cache_dir() const; Error set_thread_name(const String &p_name); - Thread::ID get_thread_caller_id() const; - Thread::ID get_main_thread_id() const; + ::Thread::ID get_thread_caller_id() const; + ::Thread::ID get_main_thread_id() const; bool has_feature(const String &p_feature) const; + bool is_sandboxed() const; bool request_permission(const String &p_name); bool request_permissions(); Vector<String> get_granted_permissions() const; + void revoke_granted_permissions(); static OS *get_singleton() { return singleton; } diff --git a/core/debugger/debugger_marshalls.cpp b/core/debugger/debugger_marshalls.cpp index 591b44869f..3e6b7501c7 100644 --- a/core/debugger/debugger_marshalls.cpp +++ b/core/debugger/debugger_marshalls.cpp @@ -67,6 +67,7 @@ Array DebuggerMarshalls::ScriptStackVariable::serialize(int max_size) { Array arr; arr.push_back(name); arr.push_back(type); + arr.push_back(value.get_type()); Variant var = value; if (value.get_type() == Variant::OBJECT && value.get_validated_object() == nullptr) { @@ -74,7 +75,7 @@ Array DebuggerMarshalls::ScriptStackVariable::serialize(int max_size) { } int len = 0; - Error err = encode_variant(var, nullptr, len, true); + Error err = encode_variant(var, nullptr, len, false); if (err != OK) { ERR_PRINT("Failed to encode variant."); } @@ -88,11 +89,12 @@ Array DebuggerMarshalls::ScriptStackVariable::serialize(int max_size) { } bool DebuggerMarshalls::ScriptStackVariable::deserialize(const Array &p_arr) { - CHECK_SIZE(p_arr, 3, "ScriptStackVariable"); + CHECK_SIZE(p_arr, 4, "ScriptStackVariable"); name = p_arr[0]; type = p_arr[1]; - value = p_arr[2]; - CHECK_END(p_arr, 3, "ScriptStackVariable"); + var_type = p_arr[2]; + value = p_arr[3]; + CHECK_END(p_arr, 4, "ScriptStackVariable"); return true; } diff --git a/core/debugger/debugger_marshalls.h b/core/debugger/debugger_marshalls.h index 8ba93c3092..1b81623688 100644 --- a/core/debugger/debugger_marshalls.h +++ b/core/debugger/debugger_marshalls.h @@ -38,6 +38,7 @@ struct DebuggerMarshalls { String name; Variant value; int type = -1; + int var_type = -1; Array serialize(int max_size = 1 << 20); // 1 MiB default. bool deserialize(const Array &p_arr); diff --git a/core/debugger/engine_debugger.cpp b/core/debugger/engine_debugger.cpp index 6c9293a2cf..32dc060aa2 100644 --- a/core/debugger/engine_debugger.cpp +++ b/core/debugger/engine_debugger.cpp @@ -111,14 +111,6 @@ Error EngineDebugger::capture_parse(const StringName &p_name, const String &p_ms return cap.capture(cap.data, p_msg, p_args, r_captured); } -void EngineDebugger::line_poll() { - // The purpose of this is just processing events every now and then when the script might get too busy otherwise bugs like infinite loops can't be caught - if (poll_every % 2048 == 0) { - poll_events(false); - } - poll_every++; -} - void EngineDebugger::iteration(uint64_t p_frame_ticks, uint64_t p_process_ticks, uint64_t p_physics_ticks, double p_physics_frame_time) { frame_time = USEC_TO_SEC(p_frame_ticks); process_time = USEC_TO_SEC(p_process_ticks); diff --git a/core/debugger/engine_debugger.h b/core/debugger/engine_debugger.h index 1bae71e37a..88d5490794 100644 --- a/core/debugger/engine_debugger.h +++ b/core/debugger/engine_debugger.h @@ -126,7 +126,13 @@ public: void profiler_enable(const StringName &p_name, bool p_enabled, const Array &p_opts = Array()); Error capture_parse(const StringName &p_name, const String &p_msg, const Array &p_args, bool &r_captured); - void line_poll(); + void line_poll() { + // The purpose of this is just processing events every now and then when the script might get too busy otherwise bugs like infinite loops can't be caught. + if (unlikely(poll_every % 2048) == 0) { + poll_events(false); + } + poll_every++; + } virtual void poll_events(bool p_is_idle) {} virtual void send_message(const String &p_msg, const Array &p_data) = 0; diff --git a/core/debugger/remote_debugger.cpp b/core/debugger/remote_debugger.cpp index b7471d7c82..b4d6fa4174 100644 --- a/core/debugger/remote_debugger.cpp +++ b/core/debugger/remote_debugger.cpp @@ -94,6 +94,7 @@ public: Error RemoteDebugger::_put_msg(String p_message, Array p_data) { Array msg; msg.push_back(p_message); + msg.push_back(Thread::get_caller_id()); msg.push_back(p_data); Error err = peer->put_message(msg); if (err != OK) { @@ -185,9 +186,9 @@ RemoteDebugger::ErrorMessage RemoteDebugger::_create_overflow_error(const String } void RemoteDebugger::flush_output() { + MutexLock lock(mutex); flush_thread = Thread::get_caller_id(); flushing = true; - MutexLock lock(mutex); if (!is_peer_connected()) { return; } @@ -348,18 +349,65 @@ Error RemoteDebugger::_try_capture(const String &p_msg, const Array &p_data, boo return capture_parse(cap, msg, p_data, r_captured); } +void RemoteDebugger::_poll_messages() { + MutexLock mutex_lock(mutex); + + peer->poll(); + while (peer->has_message()) { + Array cmd = peer->get_message(); + ERR_CONTINUE(cmd.size() != 3); + ERR_CONTINUE(cmd[0].get_type() != Variant::STRING); + ERR_CONTINUE(cmd[1].get_type() != Variant::INT); + ERR_CONTINUE(cmd[2].get_type() != Variant::ARRAY); + + Thread::ID thread = cmd[1]; + + if (!messages.has(thread)) { + continue; // This thread is not around to receive the messages + } + + Message msg; + msg.message = cmd[0]; + msg.data = cmd[2]; + messages[thread].push_back(msg); + } +} + +bool RemoteDebugger::_has_messages() { + MutexLock mutex_lock(mutex); + return messages.has(Thread::get_caller_id()) && !messages[Thread::get_caller_id()].is_empty(); +} + +Array RemoteDebugger::_get_message() { + MutexLock mutex_lock(mutex); + ERR_FAIL_COND_V(!messages.has(Thread::get_caller_id()), Array()); + List<Message> &message_list = messages[Thread::get_caller_id()]; + ERR_FAIL_COND_V(message_list.is_empty(), Array()); + + Array msg; + msg.resize(2); + msg[0] = message_list.front()->get().message; + msg[1] = message_list.front()->get().data; + message_list.pop_front(); + return msg; +} + void RemoteDebugger::debug(bool p_can_continue, bool p_is_error_breakpoint) { //this function is called when there is a debugger break (bug on script) //or when execution is paused from editor - if (script_debugger->is_skipping_breakpoints() && !p_is_error_breakpoint) { - return; - } + { + MutexLock lock(mutex); + // Tests that require mutex. + if (script_debugger->is_skipping_breakpoints() && !p_is_error_breakpoint) { + return; + } - ERR_FAIL_COND_MSG(!is_peer_connected(), "Script Debugger failed to connect, but being used anyway."); + ERR_FAIL_COND_MSG(!is_peer_connected(), "Script Debugger failed to connect, but being used anyway."); - if (!peer->can_block()) { - return; // Peer does not support blocking IO. We could at least send the error though. + if (!peer->can_block()) { + return; // Peer does not support blocking IO. We could at least send the error though. + } } ScriptLanguage *script_lang = script_debugger->get_break_language(); @@ -369,22 +417,33 @@ void RemoteDebugger::debug(bool p_can_continue, bool p_is_error_breakpoint) { msg.push_back(error_str); ERR_FAIL_COND(!script_lang); msg.push_back(script_lang->debug_get_stack_level_count() > 0); + msg.push_back(Thread::get_caller_id() == Thread::get_main_id() ? String(RTR("Main Thread")) : itos(Thread::get_caller_id())); if (allow_focus_steal_fn) { allow_focus_steal_fn(); } send_message("debug_enter", msg); - Input::MouseMode mouse_mode = Input::get_singleton()->get_mouse_mode(); - if (mouse_mode != Input::MOUSE_MODE_VISIBLE) { - Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_VISIBLE); + Input::MouseMode mouse_mode = Input::MOUSE_MODE_VISIBLE; + + if (Thread::get_caller_id() == Thread::get_main_id()) { + mouse_mode = Input::get_singleton()->get_mouse_mode(); + if (mouse_mode != Input::MOUSE_MODE_VISIBLE) { + Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_VISIBLE); + } + } else { + MutexLock mutex_lock(mutex); + messages.insert(Thread::get_caller_id(), List<Message>()); } + mutex.lock(); while (is_peer_connected()) { + mutex.unlock(); flush_output(); - peer->poll(); - if (peer->has_message()) { - Array cmd = peer->get_message(); + _poll_messages(); + + if (_has_messages()) { + Array cmd = _get_message(); ERR_CONTINUE(cmd.size() != 2); ERR_CONTINUE(cmd[0].get_type() != Variant::STRING); @@ -479,14 +538,22 @@ void RemoteDebugger::debug(bool p_can_continue, bool p_is_error_breakpoint) { } } else { OS::get_singleton()->delay_usec(10000); - OS::get_singleton()->process_and_drop_events(); + if (Thread::get_caller_id() == Thread::get_main_id()) { + // If this is a busy loop on the main thread, events still need to be processed. + OS::get_singleton()->process_and_drop_events(); + } } } send_message("debug_exit", Array()); - if (mouse_mode != Input::MOUSE_MODE_VISIBLE) { - Input::get_singleton()->set_mouse_mode(mouse_mode); + if (Thread::get_caller_id() == Thread::get_main_id()) { + if (mouse_mode != Input::MOUSE_MODE_VISIBLE) { + Input::get_singleton()->set_mouse_mode(mouse_mode); + } + } else { + MutexLock mutex_lock(mutex); + messages.erase(Thread::get_caller_id()); } } @@ -496,9 +563,11 @@ void RemoteDebugger::poll_events(bool p_is_idle) { } flush_output(); - peer->poll(); - while (peer->has_message()) { - Array arr = peer->get_message(); + + _poll_messages(); + + while (_has_messages()) { + Array arr = _get_message(); ERR_CONTINUE(arr.size() != 2); ERR_CONTINUE(arr[0].get_type() != Variant::STRING); @@ -604,6 +673,8 @@ RemoteDebugger::RemoteDebugger(Ref<RemoteDebuggerPeer> p_peer) { eh.errfunc = _err_handler; eh.userdata = this; add_error_handler(&eh); + + messages.insert(Thread::get_main_id(), List<Message>()); } RemoteDebugger::~RemoteDebugger() { diff --git a/core/debugger/remote_debugger.h b/core/debugger/remote_debugger.h index 24283b0ed6..7c399178c6 100644 --- a/core/debugger/remote_debugger.h +++ b/core/debugger/remote_debugger.h @@ -80,6 +80,17 @@ private: bool flushing = false; Thread::ID flush_thread = 0; + struct Message { + String message; + Array data; + }; + + HashMap<Thread::ID, List<Message>> messages; + + void _poll_messages(); + bool _has_messages(); + Array _get_message(); + PrintHandlerList phl; static void _print_handler(void *p_this, const String &p_string, bool p_error, bool p_rich); ErrorHandlerList eh; diff --git a/core/debugger/script_debugger.cpp b/core/debugger/script_debugger.cpp index 32725b76c1..e7d8654a0b 100644 --- a/core/debugger/script_debugger.cpp +++ b/core/debugger/script_debugger.cpp @@ -32,22 +32,19 @@ #include "core/debugger/engine_debugger.h" +thread_local int ScriptDebugger::lines_left = -1; +thread_local int ScriptDebugger::depth = -1; +thread_local ScriptLanguage *ScriptDebugger::break_lang = nullptr; +thread_local Vector<ScriptDebugger::StackInfo> ScriptDebugger::error_stack_info; + void ScriptDebugger::set_lines_left(int p_left) { lines_left = p_left; } -int ScriptDebugger::get_lines_left() const { - return lines_left; -} - void ScriptDebugger::set_depth(int p_depth) { depth = p_depth; } -int ScriptDebugger::get_depth() const { - return depth; -} - void ScriptDebugger::insert_breakpoint(int p_line, const StringName &p_source) { if (!breakpoints.has(p_line)) { breakpoints[p_line] = HashSet<StringName>(); @@ -66,13 +63,6 @@ void ScriptDebugger::remove_breakpoint(int p_line, const StringName &p_source) { } } -bool ScriptDebugger::is_breakpoint(int p_line, const StringName &p_source) const { - if (!breakpoints.has(p_line)) { - return false; - } - return breakpoints[p_line].has(p_source); -} - String ScriptDebugger::breakpoint_find_source(const String &p_source) const { return p_source; } @@ -100,7 +90,7 @@ void ScriptDebugger::send_error(const String &p_func, const String &p_file, int // Store stack info, this is ugly, but allows us to separate EngineDebugger and ScriptDebugger. There might be a better way. error_stack_info.append_array(p_stack_info); EngineDebugger::get_singleton()->send_error(p_func, p_file, p_line, p_err, p_descr, p_editor_notify, p_type); - error_stack_info.clear(); + error_stack_info.clear(); // Clear because this is thread local } Vector<ScriptLanguage::StackInfo> ScriptDebugger::get_error_stack_info() const { diff --git a/core/debugger/script_debugger.h b/core/debugger/script_debugger.h index edce089179..ee037b91fa 100644 --- a/core/debugger/script_debugger.h +++ b/core/debugger/script_debugger.h @@ -40,21 +40,25 @@ class ScriptDebugger { typedef ScriptLanguage::StackInfo StackInfo; - int lines_left = -1; - int depth = -1; bool skip_breakpoints = false; HashMap<int, HashSet<StringName>> breakpoints; - ScriptLanguage *break_lang = nullptr; - Vector<StackInfo> error_stack_info; + static thread_local int lines_left; + static thread_local int depth; + static thread_local ScriptLanguage *break_lang; + static thread_local Vector<StackInfo> error_stack_info; public: void set_lines_left(int p_left); - int get_lines_left() const; + _ALWAYS_INLINE_ int get_lines_left() const { + return lines_left; + } void set_depth(int p_depth); - int get_depth() const; + _ALWAYS_INLINE_ int get_depth() const { + return depth; + } String breakpoint_find_source(const String &p_source) const; void set_break_language(ScriptLanguage *p_lang) { break_lang = p_lang; } @@ -63,7 +67,12 @@ public: bool is_skipping_breakpoints(); void insert_breakpoint(int p_line, const StringName &p_source); void remove_breakpoint(int p_line, const StringName &p_source); - bool is_breakpoint(int p_line, const StringName &p_source) const; + _ALWAYS_INLINE_ bool is_breakpoint(int p_line, const StringName &p_source) const { + if (likely(!breakpoints.has(p_line))) { + return false; + } + return breakpoints[p_line].has(p_source); + } void clear_breakpoints(); const HashMap<int, HashSet<StringName>> &get_breakpoints() const { return breakpoints; } diff --git a/core/io/resource.cpp b/core/io/resource.cpp index 6b8ec8d5f6..07677337b4 100644 --- a/core/io/resource.cpp +++ b/core/io/resource.cpp @@ -147,15 +147,28 @@ bool Resource::editor_can_reload_from_file() { return true; //by default yes } +void Resource::connect_changed(const Callable &p_callable, uint32_t p_flags) { + if (!is_connected(CoreStringNames::get_singleton()->changed, p_callable) || p_flags & CONNECT_REFERENCE_COUNTED) { + connect(CoreStringNames::get_singleton()->changed, p_callable, p_flags); + } +} + +void Resource::disconnect_changed(const Callable &p_callable) { + if (is_connected(CoreStringNames::get_singleton()->changed, p_callable)) { + disconnect(CoreStringNames::get_singleton()->changed, p_callable); + } +} + void Resource::reset_state() { } + Error Resource::copy_from(const Ref<Resource> &p_resource) { ERR_FAIL_COND_V(p_resource.is_null(), ERR_INVALID_PARAMETER); if (get_class() != p_resource->get_class()) { return ERR_INVALID_PARAMETER; } - reset_state(); //may want to reset state + reset_state(); // May want to reset state. List<PropertyInfo> pi; p_resource->get_property_list(&pi); @@ -322,23 +335,6 @@ RID Resource::get_rid() const { return RID(); } -void Resource::register_owner(Object *p_owner) { - owners.insert(p_owner->get_instance_id()); -} - -void Resource::unregister_owner(Object *p_owner) { - owners.erase(p_owner->get_instance_id()); -} - -void Resource::notify_change_to_owners() { - for (const ObjectID &E : owners) { - Object *obj = ObjectDB::get_instance(E); - ERR_CONTINUE_MSG(!obj, "Object was deleted, while still owning a resource."); //wtf - //TODO store string - obj->call("resource_changed", Ref<Resource>(this)); - } -} - #ifdef TOOLS_ENABLED uint32_t Resource::hash_edited_version() const { @@ -443,6 +439,7 @@ void Resource::_bind_methods() { ClassDB::bind_method(D_METHOD("is_local_to_scene"), &Resource::is_local_to_scene); ClassDB::bind_method(D_METHOD("get_local_scene"), &Resource::get_local_scene); ClassDB::bind_method(D_METHOD("setup_local_to_scene"), &Resource::setup_local_to_scene); + ClassDB::bind_method(D_METHOD("emit_changed"), &Resource::emit_changed); ClassDB::bind_method(D_METHOD("duplicate", "subresources"), &Resource::duplicate, DEFVAL(false)); @@ -469,9 +466,6 @@ Resource::~Resource() { ResourceCache::resources.erase(path_cache); ResourceCache::lock.unlock(); } - if (owners.size()) { - WARN_PRINT("Resource is still owned."); - } } HashMap<String, Resource *> ResourceCache::resources; diff --git a/core/io/resource.h b/core/io/resource.h index 5135664f36..af8c275a1c 100644 --- a/core/io/resource.h +++ b/core/io/resource.h @@ -54,8 +54,6 @@ public: virtual String get_base_extension() const { return "res"; } private: - HashSet<ObjectID> owners; - friend class ResBase; friend class ResourceCache; @@ -76,10 +74,6 @@ private: SelfList<Resource> remapped_list; protected: - void emit_changed(); - - void notify_change_to_owners(); - virtual void _resource_path_changed(); static void _bind_methods(); @@ -96,8 +90,9 @@ public: virtual Error copy_from(const Ref<Resource> &p_resource); virtual void reload_from_file(); - void register_owner(Object *p_owner); - void unregister_owner(Object *p_owner); + void emit_changed(); + void connect_changed(const Callable &p_callable, uint32_t p_flags = 0); + void disconnect_changed(const Callable &p_callable); void set_name(const String &p_name); String get_name() const; diff --git a/core/io/resource_format_binary.cpp b/core/io/resource_format_binary.cpp index 3037f603c4..551d3268b8 100644 --- a/core/io/resource_format_binary.cpp +++ b/core/io/resource_format_binary.cpp @@ -1960,6 +1960,8 @@ void ResourceFormatSaverBinaryInstance::_find_resources(const Variant &p_variant return; } + resource_set.insert(res); + List<PropertyInfo> property_list; res->get_property_list(&property_list); @@ -1968,14 +1970,17 @@ void ResourceFormatSaverBinaryInstance::_find_resources(const Variant &p_variant if (E.usage & PROPERTY_USAGE_STORAGE) { Variant value = res->get(E.name); if (E.usage & PROPERTY_USAGE_RESOURCE_NOT_PERSISTENT) { + NonPersistentKey npk; + npk.base = res; + npk.property = E.name; + non_persistent_map[npk] = value; + Ref<Resource> sres = value; if (sres.is_valid()) { - NonPersistentKey npk; - npk.base = res; - npk.property = E.name; - non_persistent_map[npk] = sres; resource_set.insert(sres); saved_resources.push_back(sres); + } else { + _find_resources(value); } } else { _find_resources(value); @@ -1983,7 +1988,6 @@ void ResourceFormatSaverBinaryInstance::_find_resources(const Variant &p_variant } } - resource_set.insert(res); saved_resources.push_back(res); } break; diff --git a/core/io/resource_format_binary.h b/core/io/resource_format_binary.h index 30f1664983..e64485d404 100644 --- a/core/io/resource_format_binary.h +++ b/core/io/resource_format_binary.h @@ -139,7 +139,7 @@ class ResourceFormatSaverBinaryInstance { bool operator<(const NonPersistentKey &p_key) const { return base == p_key.base ? property < p_key.property : base < p_key.base; } }; - RBMap<NonPersistentKey, Ref<Resource>> non_persistent_map; + RBMap<NonPersistentKey, Variant> non_persistent_map; HashMap<StringName, int> string_map; Vector<StringName> strings; diff --git a/core/object/callable_method_pointer.cpp b/core/object/callable_method_pointer.cpp index b53985e6b7..ed400788b1 100644 --- a/core/object/callable_method_pointer.cpp +++ b/core/object/callable_method_pointer.cpp @@ -38,13 +38,10 @@ bool CallableCustomMethodPointerBase::compare_equal(const CallableCustom *p_a, c return false; } - for (uint32_t i = 0; i < a->comp_size; i++) { - if (a->comp_ptr[i] != b->comp_ptr[i]) { - return false; - } - } - - return true; + // Avoid sorting by memory address proximity, which leads to unpredictable performance over time + // due to the reuse of old addresses for newer objects. Use byte-wise comparison to leverage the + // backwards encoding of little-endian systems as a way to decouple spatiality and time. + return memcmp(a->comp_ptr, b->comp_ptr, a->comp_size * 4) == 0; } bool CallableCustomMethodPointerBase::compare_less(const CallableCustom *p_a, const CallableCustom *p_b) { @@ -55,15 +52,8 @@ bool CallableCustomMethodPointerBase::compare_less(const CallableCustom *p_a, co return a->comp_size < b->comp_size; } - for (uint32_t i = 0; i < a->comp_size; i++) { - if (a->comp_ptr[i] == b->comp_ptr[i]) { - continue; - } - - return a->comp_ptr[i] < b->comp_ptr[i]; - } - - return false; + // See note in compare_equal(). + return memcmp(a->comp_ptr, b->comp_ptr, a->comp_size * 4) < 0; } CallableCustom::CompareEqualFunc CallableCustomMethodPointerBase::get_compare_equal_func() const { diff --git a/core/object/class_db.cpp b/core/object/class_db.cpp index cc4a29164d..c8c50fb957 100644 --- a/core/object/class_db.cpp +++ b/core/object/class_db.cpp @@ -53,7 +53,7 @@ MethodDefinition D_METHODP(const char *p_name, const char *const **p_args, uint3 #endif ClassDB::APIType ClassDB::current_api = API_CORE; -HashMap<ClassDB::APIType, uint64_t> ClassDB::api_hashes_cache; +HashMap<ClassDB::APIType, uint32_t> ClassDB::api_hashes_cache; void ClassDB::set_current_api(APIType p_api) { DEV_ASSERT(!api_hashes_cache.has(p_api)); // This API type may not be suitable for caching of hash if it can change later. @@ -163,7 +163,7 @@ ClassDB::APIType ClassDB::get_api_type(const StringName &p_class) { return ti->api; } -uint64_t ClassDB::get_api_hash(APIType p_api) { +uint32_t ClassDB::get_api_hash(APIType p_api) { OBJTYPE_RLOCK; #ifdef DEBUG_METHODS_ENABLED diff --git a/core/object/class_db.h b/core/object/class_db.h index ce64336a45..3aae3b452e 100644 --- a/core/object/class_db.h +++ b/core/object/class_db.h @@ -155,7 +155,7 @@ public: #endif static APIType current_api; - static HashMap<APIType, uint64_t> api_hashes_cache; + static HashMap<APIType, uint32_t> api_hashes_cache; static void _add_class2(const StringName &p_class, const StringName &p_inherits); @@ -246,7 +246,7 @@ public: static APIType get_api_type(const StringName &p_class); - static uint64_t get_api_hash(APIType p_api); + static uint32_t get_api_hash(APIType p_api); template <typename> struct member_function_traits; diff --git a/core/object/message_queue.cpp b/core/object/message_queue.cpp index 18ba5d5b30..dd7aba1384 100644 --- a/core/object/message_queue.cpp +++ b/core/object/message_queue.cpp @@ -35,6 +35,10 @@ #include "core/object/class_db.h" #include "core/object/script_language.h" +#ifdef DEBUG_ENABLED +#include "core/config/engine.h" +#endif + #ifdef DEV_ENABLED // Includes sanity checks to ensure that a queue set as a thread singleton override // is only ever called from the thread it was set for. @@ -222,62 +226,66 @@ void CallQueue::_call_function(const Callable &p_callable, const Variant *p_args } } -Error CallQueue::flush() { - LOCK_MUTEX; - - // Thread overrides are not meant to be flushed, but appended to the main one. - if (this == MessageQueue::thread_singleton) { - if (pages.size() == 0) { - return OK; - } - - CallQueue *mq = MessageQueue::main_singleton; - DEV_ASSERT(!mq->allocator_is_custom && !allocator_is_custom); // Transferring pages is only safe if using the same alloator parameters. - - mq->mutex.lock(); - - // Here we're transferring the data from this queue to the main one. - // However, it's very unlikely big amounts of messages will be queued here, - // so PagedArray/Pool would be overkill. Also, in most cases the data will fit - // an already existing page of the main queue. +Error CallQueue::_transfer_messages_to_main_queue() { + if (pages.size() == 0) { + return OK; + } - // Let's see if our first (likely only) page fits the current target queue page. - uint32_t src_page = 0; - { - if (mq->pages_used) { - uint32_t dst_page = mq->pages_used - 1; - uint32_t dst_offset = mq->page_bytes[dst_page]; - if (dst_offset + page_bytes[0] < uint32_t(PAGE_SIZE_BYTES)) { - memcpy(mq->pages[dst_page]->data + dst_offset, pages[0]->data, page_bytes[0]); - mq->page_bytes[dst_page] += page_bytes[0]; - src_page++; - } + CallQueue *mq = MessageQueue::main_singleton; + DEV_ASSERT(!mq->allocator_is_custom && !allocator_is_custom); // Transferring pages is only safe if using the same alloator parameters. + + mq->mutex.lock(); + + // Here we're transferring the data from this queue to the main one. + // However, it's very unlikely big amounts of messages will be queued here, + // so PagedArray/Pool would be overkill. Also, in most cases the data will fit + // an already existing page of the main queue. + + // Let's see if our first (likely only) page fits the current target queue page. + uint32_t src_page = 0; + { + if (mq->pages_used) { + uint32_t dst_page = mq->pages_used - 1; + uint32_t dst_offset = mq->page_bytes[dst_page]; + if (dst_offset + page_bytes[0] < uint32_t(PAGE_SIZE_BYTES)) { + memcpy(mq->pages[dst_page]->data + dst_offset, pages[0]->data, page_bytes[0]); + mq->page_bytes[dst_page] += page_bytes[0]; + src_page++; } } + } - // Any other possibly existing source page needs to be added. + // Any other possibly existing source page needs to be added. - if (mq->pages_used + (pages_used - src_page) > mq->max_pages) { - ERR_PRINT("Failed appending thread queue. Message queue out of memory. " + mq->error_text); - mq->statistics(); - mq->mutex.unlock(); - return ERR_OUT_OF_MEMORY; - } + if (mq->pages_used + (pages_used - src_page) > mq->max_pages) { + ERR_PRINT("Failed appending thread queue. Message queue out of memory. " + mq->error_text); + mq->statistics(); + mq->mutex.unlock(); + return ERR_OUT_OF_MEMORY; + } - for (; src_page < pages_used; src_page++) { - mq->_add_page(); - memcpy(mq->pages[mq->pages_used - 1]->data, pages[src_page]->data, page_bytes[src_page]); - mq->page_bytes[mq->pages_used - 1] = page_bytes[src_page]; - } + for (; src_page < pages_used; src_page++) { + mq->_add_page(); + memcpy(mq->pages[mq->pages_used - 1]->data, pages[src_page]->data, page_bytes[src_page]); + mq->page_bytes[mq->pages_used - 1] = page_bytes[src_page]; + } - mq->mutex.unlock(); + mq->mutex.unlock(); - page_bytes[0] = 0; - pages_used = 1; + page_bytes[0] = 0; + pages_used = 1; - return OK; + return OK; +} + +Error CallQueue::flush() { + // Thread overrides are not meant to be flushed, but appended to the main one. + if (unlikely(this == MessageQueue::thread_singleton)) { + return _transfer_messages_to_main_queue(); } + LOCK_MUTEX; + if (pages.size() == 0) { // Never allocated UNLOCK_MUTEX; @@ -312,25 +320,34 @@ Error CallQueue::flush() { Object *target = message->callable.get_object(); UNLOCK_MUTEX; - - switch (message->type & FLAG_MASK) { - case TYPE_CALL: { - if (target || (message->type & FLAG_NULL_IS_OK)) { - Variant *args = (Variant *)(message + 1); - _call_function(message->callable, args, message->args, message->type & FLAG_SHOW_ERROR); - } - } break; - case TYPE_NOTIFICATION: { - if (target) { - target->notification(message->notification); - } - } break; - case TYPE_SET: { - if (target) { - Variant *arg = (Variant *)(message + 1); - target->set(message->callable.get_method(), *arg); - } - } break; +#ifdef DEBUG_ENABLED + if (!message->callable.is_valid()) { + // The editor would cause many of these. + if (!Engine::get_singleton()->is_editor_hint()) { + ERR_PRINT("Trying to execute a deferred call/notification/set on a previously freed instance. Consider using queue_free() instead of free()."); + } + } else +#endif + { + switch (message->type & FLAG_MASK) { + case TYPE_CALL: { + if (target || (message->type & FLAG_NULL_IS_OK)) { + Variant *args = (Variant *)(message + 1); + _call_function(message->callable, args, message->args, message->type & FLAG_SHOW_ERROR); + } + } break; + case TYPE_NOTIFICATION: { + if (target) { + target->notification(message->notification); + } + } break; + case TYPE_SET: { + if (target) { + Variant *arg = (Variant *)(message + 1); + target->set(message->callable.get_method(), *arg); + } + } break; + } } if ((message->type & FLAG_MASK) != TYPE_NOTIFICATION) { diff --git a/core/object/message_queue.h b/core/object/message_queue.h index 9f567e4dd0..c2f4ad1643 100644 --- a/core/object/message_queue.h +++ b/core/object/message_queue.h @@ -98,6 +98,8 @@ private: } } + Error _transfer_messages_to_main_queue(); + void _add_page(); void _call_function(const Callable &p_callable, const Variant *p_args, int p_argcount, bool p_show_error); diff --git a/core/object/object.h b/core/object/object.h index a3e9d025ea..318dbf98de 100644 --- a/core/object/object.h +++ b/core/object/object.h @@ -430,6 +430,9 @@ protected: _FORCE_INLINE_ static void (*_get_bind_methods())() { \ return &m_class::_bind_methods; \ } \ + _FORCE_INLINE_ static void (*_get_bind_compatibility_methods())() { \ + return &m_class::_bind_compatibility_methods; \ + } \ \ public: \ static void initialize_class() { \ @@ -442,6 +445,9 @@ public: if (m_class::_get_bind_methods() != m_inherits::_get_bind_methods()) { \ _bind_methods(); \ } \ + if (m_class::_get_bind_compatibility_methods() != m_inherits::_get_bind_compatibility_methods()) { \ + _bind_compatibility_methods(); \ + } \ initialized = true; \ } \ \ @@ -674,6 +680,7 @@ protected: virtual void _notificationv(int p_notification, bool p_reversed) {} static void _bind_methods(); + static void _bind_compatibility_methods() {} bool _set(const StringName &p_name, const Variant &p_property) { return false; }; bool _get(const StringName &p_name, Variant &r_property) const { return false; }; void _get_property_list(List<PropertyInfo> *p_list) const {}; @@ -685,6 +692,9 @@ protected: _FORCE_INLINE_ static void (*_get_bind_methods())() { return &Object::_bind_methods; } + _FORCE_INLINE_ static void (*_get_bind_compatibility_methods())() { + return &Object::_bind_compatibility_methods; + } _FORCE_INLINE_ bool (Object::*_get_get() const)(const StringName &p_name, Variant &r_ret) const { return &Object::_get; } diff --git a/core/object/script_language.h b/core/object/script_language.h index 2b685c77a3..3ea6a6e4c3 100644 --- a/core/object/script_language.h +++ b/core/object/script_language.h @@ -263,6 +263,7 @@ public: }; struct ScriptError { + String path; int line = -1; int column = -1; String message; diff --git a/core/object/worker_thread_pool.h b/core/object/worker_thread_pool.h index d4d9387765..9fe8497eaf 100644 --- a/core/object/worker_thread_pool.h +++ b/core/object/worker_thread_pool.h @@ -202,4 +202,25 @@ public: ~WorkerThreadPool(); }; +template <typename F> +static _FORCE_INLINE_ void for_range(int i_begin, int i_end, bool parallel, String name, F f) { + if (!parallel) { + for (int i = i_begin; i < i_end; i++) { + f(i); + } + return; + } + + auto wrapper = [&](int i, void *unused) { + f(i + i_begin); + }; + + WorkerThreadPool *wtp = WorkerThreadPool::get_singleton(); + WorkerThreadPool::GroupID gid = wtp->add_template_group_task( + &wrapper, &decltype(wrapper)::operator(), nullptr, + i_end - i_begin, -1, + true, name); + wtp->wait_for_group_task_completion(gid); +} + #endif // WORKER_THREAD_POOL_H diff --git a/core/os/os.cpp b/core/os/os.cpp index 67423128a3..38ea4a0fdd 100644 --- a/core/os/os.cpp +++ b/core/os/os.cpp @@ -505,6 +505,10 @@ bool OS::has_feature(const String &p_feature) { return false; } +bool OS::is_sandboxed() const { + return false; +} + void OS::set_restart_on_exit(bool p_restart, const List<String> &p_restart_arguments) { restart_on_exit = p_restart; restart_commandline = p_restart_arguments; diff --git a/core/os/os.h b/core/os/os.h index f2787d6381..965dc1f912 100644 --- a/core/os/os.h +++ b/core/os/os.h @@ -295,6 +295,8 @@ public: bool has_feature(const String &p_feature); + virtual bool is_sandboxed() const; + void set_has_server_feature_callback(HasServerFeatureCallback p_callback); void set_restart_on_exit(bool p_restart, const List<String> &p_restart_arguments); @@ -304,6 +306,7 @@ public: virtual bool request_permission(const String &p_name) { return true; } virtual bool request_permissions() { return true; } virtual Vector<String> get_granted_permissions() const { return Vector<String>(); } + virtual void revoke_granted_permissions() {} // For recording / measuring benchmark data. Only enabled with tools void set_use_benchmark(bool p_use_benchmark); diff --git a/core/string/print_string.cpp b/core/string/print_string.cpp index 7b90710308..dcdde3c175 100644 --- a/core/string/print_string.cpp +++ b/core/string/print_string.cpp @@ -164,6 +164,8 @@ void __print_line_rich(String p_string) { p_string_ansi = p_string_ansi.replace("[/fgcolor]", "\u001b[39;49m"); } + p_string_ansi += "\u001b[0m"; // Reset. + OS::get_singleton()->print_rich("%s\n", p_string_ansi.utf8().get_data()); _global_lock(); diff --git a/core/string/translation.cpp b/core/string/translation.cpp index 3ca2e5ccdf..02380c92bb 100644 --- a/core/string/translation.cpp +++ b/core/string/translation.cpp @@ -82,6 +82,15 @@ void Translation::_set_messages(const Dictionary &p_messages) { void Translation::set_locale(const String &p_locale) { locale = TranslationServer::get_singleton()->standardize_locale(p_locale); + if (Thread::is_main_thread()) { + _notify_translation_changed_if_applies(); + } else { + // Avoid calling non-thread-safe functions here. + callable_mp(this, &Translation::_notify_translation_changed_if_applies).call_deferred(); + } +} + +void Translation::_notify_translation_changed_if_applies() { if (OS::get_singleton()->get_main_loop() && TranslationServer::get_singleton()->get_loaded_locales().has(get_locale())) { OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_TRANSLATION_CHANGED); } diff --git a/core/string/translation.h b/core/string/translation.h index 01d239f81c..ca8b460312 100644 --- a/core/string/translation.h +++ b/core/string/translation.h @@ -47,6 +47,8 @@ class Translation : public Resource { virtual Dictionary _get_messages() const; virtual void _set_messages(const Dictionary &p_messages); + void _notify_translation_changed_if_applies(); + protected: static void _bind_methods(); diff --git a/core/templates/hash_map.h b/core/templates/hash_map.h index 4da73f1cfb..e1745110d7 100644 --- a/core/templates/hash_map.h +++ b/core/templates/hash_map.h @@ -353,6 +353,40 @@ public: return true; } + // Replace the key of an entry in-place, without invalidating iterators or changing the entries position during iteration. + // p_old_key must exist in the map and p_new_key must not, unless it is equal to p_old_key. + bool replace_key(const TKey &p_old_key, const TKey &p_new_key) { + if (p_old_key == p_new_key) { + return true; + } + uint32_t pos = 0; + ERR_FAIL_COND_V(_lookup_pos(p_new_key, pos), false); + ERR_FAIL_COND_V(!_lookup_pos(p_old_key, pos), false); + HashMapElement<TKey, TValue> *element = elements[pos]; + + // Delete the old entries in hashes and elements. + const uint32_t capacity = hash_table_size_primes[capacity_index]; + const uint64_t capacity_inv = hash_table_size_primes_inv[capacity_index]; + uint32_t next_pos = fastmod((pos + 1), capacity_inv, capacity); + while (hashes[next_pos] != EMPTY_HASH && _get_probe_length(next_pos, hashes[next_pos], capacity, capacity_inv) != 0) { + SWAP(hashes[next_pos], hashes[pos]); + SWAP(elements[next_pos], elements[pos]); + pos = next_pos; + next_pos = fastmod((pos + 1), capacity_inv, capacity); + } + hashes[pos] = EMPTY_HASH; + elements[pos] = nullptr; + // _insert_with_hash will increment this again. + num_elements--; + + // Update the HashMapElement with the new key and reinsert it. + const_cast<TKey &>(element->data.key) = p_new_key; + uint32_t hash = _hash(p_new_key); + _insert_with_hash(hash, element); + + return true; + } + // Reserves space for a number of elements, useful to avoid many resizes and rehashes. // If adding a known (possibly large) number of elements at once, must be larger than old capacity. void reserve(uint32_t p_new_capacity) { diff --git a/core/variant/array.cpp b/core/variant/array.cpp index 5215142dd3..5a0ded6c01 100644 --- a/core/variant/array.cpp +++ b/core/variant/array.cpp @@ -454,17 +454,21 @@ Array Array::slice(int p_begin, int p_end, int p_step, bool p_deep) const { const int s = size(); - int begin = CLAMP(p_begin, -s, s); + if (s == 0 || (p_begin < -s && p_step < 0) || (p_begin >= s && p_step > 0)) { + return result; + } + + int begin = CLAMP(p_begin, -s, s - 1); if (begin < 0) { begin += s; } - int end = CLAMP(p_end, -s, s); + int end = CLAMP(p_end, -s - 1, s); if (end < 0) { end += s; } - ERR_FAIL_COND_V_MSG(p_step > 0 && begin > end, result, "Slice is positive, but bounds is decreasing."); - ERR_FAIL_COND_V_MSG(p_step < 0 && begin < end, result, "Slice is negative, but bounds is increasing."); + ERR_FAIL_COND_V_MSG(p_step > 0 && begin > end, result, "Slice step is positive, but bounds are decreasing."); + ERR_FAIL_COND_V_MSG(p_step < 0 && begin < end, result, "Slice step is negative, but bounds are increasing."); int result_size = (end - begin) / p_step + (((end - begin) % p_step != 0) ? 1 : 0); result.resize(result_size); diff --git a/doc/classes/AStarGrid2D.xml b/doc/classes/AStarGrid2D.xml index ec55ccedb2..5d54af60ec 100644 --- a/doc/classes/AStarGrid2D.xml +++ b/doc/classes/AStarGrid2D.xml @@ -17,7 +17,7 @@ [/gdscript] [csharp] AStarGrid2D astarGrid = new AStarGrid2D(); - astarGrid.Size = new Vector2I(32, 32); + astarGrid.Region = new Rect2I(0, 0, 32, 32); astarGrid.CellSize = new Vector2I(16, 16); astarGrid.Update(); GD.Print(astarGrid.GetIdPath(Vector2I.Zero, new Vector2I(3, 4))); // prints (0, 0), (1, 1), (2, 2), (3, 3), (3, 4) diff --git a/doc/classes/Array.xml b/doc/classes/Array.xml index f62c44aa0d..999cc6fa29 100644 --- a/doc/classes/Array.xml +++ b/doc/classes/Array.xml @@ -584,6 +584,7 @@ If either [param begin] or [param end] are negative, they will be relative to the end of the array (i.e. [code]arr.slice(0, -2)[/code] is a shorthand for [code]arr.slice(0, arr.size() - 2)[/code]). If specified, [param step] is the relative index between source elements. It can be negative, then [param begin] must be higher than [param end]. For example, [code][0, 1, 2, 3, 4, 5].slice(5, 1, -2)[/code] returns [code][5, 3][/code]. If [param deep] is true, each element will be copied by value rather than by reference. + [b]Note:[/b] To include the first element when [param step] is negative, use [code]arr.slice(begin, -arr.size() - 1, step)[/code] (i.e. [code][0, 1, 2].slice(1, -4, -1)[/code] returns [code][1, 0][/code]). </description> </method> <method name="sort"> diff --git a/doc/classes/AudioStreamPlayer3D.xml b/doc/classes/AudioStreamPlayer3D.xml index 651d2a4b49..f4c9adcaec 100644 --- a/doc/classes/AudioStreamPlayer3D.xml +++ b/doc/classes/AudioStreamPlayer3D.xml @@ -85,7 +85,7 @@ Attenuation factor used if listener is outside of [member emission_angle_degrees] and [member emission_angle_enabled] is set, in decibels. </member> <member name="max_db" type="float" setter="set_max_db" getter="get_max_db" default="3.0"> - Sets the absolute maximum of the soundlevel, in decibels. + Sets the absolute maximum of the sound level, in decibels. </member> <member name="max_distance" type="float" setter="set_max_distance" getter="get_max_distance" default="0.0"> The distance past which the sound can no longer be heard at all. Only has an effect if set to a value greater than [code]0.0[/code]. [member max_distance] works in tandem with [member unit_size]. However, unlike [member unit_size] whose behavior depends on the [member attenuation_model], [member max_distance] always works in a linear fashion. This can be used to prevent the [AudioStreamPlayer3D] from requiring audio mixing when the listener is far away, which saves CPU resources. diff --git a/doc/classes/CPUParticles2D.xml b/doc/classes/CPUParticles2D.xml index 74523ee397..eaacdd590d 100644 --- a/doc/classes/CPUParticles2D.xml +++ b/doc/classes/CPUParticles2D.xml @@ -169,7 +169,7 @@ The sphere's radius if [member emission_shape] is set to [constant EMISSION_SHAPE_SPHERE]. </member> <member name="emitting" type="bool" setter="set_emitting" getter="is_emitting" default="true"> - If [code]true[/code], particles are being emitted. + If [code]true[/code], particles are being emitted. [member emitting] can be used to start and stop particles from emitting. However, if [member one_shot] is [code]true[/code] setting [member emitting] to [code]true[/code] will not restart the emission cycle until after all active particles finish processing. You can use the [signal finished] signal to be notified once all active particles finish processing. </member> <member name="explosiveness" type="float" setter="set_explosiveness_ratio" getter="get_explosiveness_ratio" default="0.0"> How rapidly particles in an emission cycle are emitted. If greater than [code]0[/code], there will be a gap in emissions before the next cycle begins. @@ -285,6 +285,13 @@ Particle texture. If [code]null[/code], particles will be squares. </member> </members> + <signals> + <signal name="finished"> + <description> + Emitted when all active particles have finished processing. When [member one_shot] is disabled, particles will process continuously, so this is never emitted. + </description> + </signal> + </signals> <constants> <constant name="DRAW_ORDER_INDEX" value="0" enum="DrawOrder"> Particles are drawn in the order emitted. diff --git a/doc/classes/CPUParticles3D.xml b/doc/classes/CPUParticles3D.xml index 1714ebec71..f03c463049 100644 --- a/doc/classes/CPUParticles3D.xml +++ b/doc/classes/CPUParticles3D.xml @@ -183,7 +183,7 @@ The sphere's radius if [enum EmissionShape] is set to [constant EMISSION_SHAPE_SPHERE]. </member> <member name="emitting" type="bool" setter="set_emitting" getter="is_emitting" default="true"> - If [code]true[/code], particles are being emitted. + If [code]true[/code], particles are being emitted. [member emitting] can be used to start and stop particles from emitting. However, if [member one_shot] is [code]true[/code] setting [member emitting] to [code]true[/code] will not restart the emission cycle until after all active particles finish processing. You can use the [signal finished] signal to be notified once all active particles finish processing. </member> <member name="explosiveness" type="float" setter="set_explosiveness_ratio" getter="get_explosiveness_ratio" default="0.0"> How rapidly particles in an emission cycle are emitted. If greater than [code]0[/code], there will be a gap in emissions before the next cycle begins. @@ -309,6 +309,13 @@ Minimum tangent acceleration. </member> </members> + <signals> + <signal name="finished"> + <description> + Emitted when all active particles have finished processing. When [member one_shot] is disabled, particles will process continuously, so this is never emitted. + </description> + </signal> + </signals> <constants> <constant name="DRAW_ORDER_INDEX" value="0" enum="DrawOrder"> Particles are drawn in the order emitted. diff --git a/doc/classes/CodeEdit.xml b/doc/classes/CodeEdit.xml index ab63d66aba..0e829127f2 100644 --- a/doc/classes/CodeEdit.xml +++ b/doc/classes/CodeEdit.xml @@ -248,12 +248,20 @@ Returns the full text with char [code]0xFFFF[/code] at the caret location. </description> </method> - <method name="get_text_for_symbol_lookup"> + <method name="get_text_for_symbol_lookup" qualifiers="const"> <return type="String" /> <description> Returns the full text with char [code]0xFFFF[/code] at the cursor location. </description> </method> + <method name="get_text_with_cursor_char" qualifiers="const"> + <return type="String" /> + <param index="0" name="line" type="int" /> + <param index="1" name="column" type="int" /> + <description> + Returns the full text with char [code]0xFFFF[/code] at the specified location. + </description> + </method> <method name="has_auto_brace_completion_close_key" qualifiers="const"> <return type="bool" /> <param index="0" name="close_key" type="String" /> @@ -435,7 +443,7 @@ <return type="void" /> <param index="0" name="force" type="bool" /> <description> - Submits all completion options added with [method add_code_completion_option]. Will try to force the autoccomplete menu to popup, if [param force] is [code]true[/code]. + Submits all completion options added with [method add_code_completion_option]. Will try to force the autocomplete menu to popup, if [param force] is [code]true[/code]. [b]Note:[/b] This will replace all current candidates. </description> </method> diff --git a/doc/classes/CollisionPolygon2D.xml b/doc/classes/CollisionPolygon2D.xml index 783c856066..d6f3b7cb5d 100644 --- a/doc/classes/CollisionPolygon2D.xml +++ b/doc/classes/CollisionPolygon2D.xml @@ -5,7 +5,7 @@ </brief_description> <description> A node that provides a thickened polygon shape (a prism) to a [CollisionObject2D] parent and allows to edit it. The polygon can be concave or convex. This can give a detection shape to an [Area2D] or turn [PhysicsBody2D] into a solid object. - [b]Warning:[/b] A non-uniformly scaled [CollisionShape3D] will likely not behave as expected. Make sure to keep its scale the same on all axes and adjust its shape resource instead. + [b]Warning:[/b] A non-uniformly scaled [CollisionShape2D] will likely not behave as expected. Make sure to keep its scale the same on all axes and adjust its shape resource instead. </description> <tutorials> </tutorials> diff --git a/doc/classes/CollisionShape3D.xml b/doc/classes/CollisionShape3D.xml index 171deb9c62..4e32545f27 100644 --- a/doc/classes/CollisionShape3D.xml +++ b/doc/classes/CollisionShape3D.xml @@ -20,11 +20,11 @@ Sets the collision shape's shape to the addition of all its convexed [MeshInstance3D] siblings geometry. </description> </method> - <method name="resource_changed"> + <method name="resource_changed" is_deprecated="true"> <return type="void" /> <param index="0" name="resource" type="Resource" /> <description> - If this method exists within a script it will be called whenever the shape resource has been modified. + [i]Obsoleted.[/i] Use [signal Resource.changed] instead. </description> </method> </methods> diff --git a/doc/classes/ConvexPolygonShape2D.xml b/doc/classes/ConvexPolygonShape2D.xml index 13134540e0..4f86f3830b 100644 --- a/doc/classes/ConvexPolygonShape2D.xml +++ b/doc/classes/ConvexPolygonShape2D.xml @@ -6,7 +6,7 @@ <description> A 2D convex polygon shape, intended for use in physics. Used internally in [CollisionPolygon2D] when it's in [code]BUILD_SOLIDS[/code] mode. [ConvexPolygonShape2D] is [i]solid[/i], which means it detects collisions from objects that are fully inside it, unlike [ConcavePolygonShape2D] which is hollow. This makes it more suitable for both detection and physics. - [b]Convex decomposition:[/b] A concave polygon can be split up into several convex polygons. This allows dynamic physics bodies to have complex concave collisions (at a performance cost) and can be achieved by using several [ConvexPolygonShape3D] nodes or by using the [CollisionPolygon2D] node in [code]BUILD_SOLIDS[/code] mode. To generate a collision polygon from a sprite, select the [Sprite2D] node, go to the [b]Sprite2D[/b] menu that appears above the viewport, and choose [b]Create Polygon2D Sibling[/b]. + [b]Convex decomposition:[/b] A concave polygon can be split up into several convex polygons. This allows dynamic physics bodies to have complex concave collisions (at a performance cost) and can be achieved by using several [ConvexPolygonShape2D] nodes or by using the [CollisionPolygon2D] node in [code]BUILD_SOLIDS[/code] mode. To generate a collision polygon from a sprite, select the [Sprite2D] node, go to the [b]Sprite2D[/b] menu that appears above the viewport, and choose [b]Create Polygon2D Sibling[/b]. [b]Performance:[/b] [ConvexPolygonShape2D] is faster to check collisions against compared to [ConcavePolygonShape2D], but it is slower than primitive collision shapes such as [CircleShape2D] and [RectangleShape2D]. Its use should generally be limited to medium-sized objects that cannot have their collision accurately represented by primitive shapes. </description> <tutorials> diff --git a/doc/classes/CylinderShape3D.xml b/doc/classes/CylinderShape3D.xml index 3d1204baf0..8bec199ab6 100644 --- a/doc/classes/CylinderShape3D.xml +++ b/doc/classes/CylinderShape3D.xml @@ -4,9 +4,9 @@ A 3D cylinder shape used for physics collision. </brief_description> <description> - A 2D capsule shape, intended for use in physics. Usually used to provide a shape for a [CollisionShape3D]. + A 3D cylinder shape, intended for use in physics. Usually used to provide a shape for a [CollisionShape3D]. [b]Note:[/b] There are several known bugs with cylinder collision shapes. Using [CapsuleShape3D] or [BoxShape3D] instead is recommended. - [b]Performance:[/b] [CylinderShape3D] is fast to check collisions against, but it is slower than [CapsuleShape3D], [BoxShape3D], and [CylinderShape3D]. + [b]Performance:[/b] [CylinderShape3D] is fast to check collisions against, but it is slower than [CapsuleShape3D], [BoxShape3D], and [SphereShape3D]. </description> <tutorials> <link title="Third Person Shooter Demo">https://godotengine.org/asset-library/asset/678</link> diff --git a/doc/classes/Dictionary.xml b/doc/classes/Dictionary.xml index 3f165c2ac5..b39c5c9699 100644 --- a/doc/classes/Dictionary.xml +++ b/doc/classes/Dictionary.xml @@ -261,7 +261,7 @@ [/csharp] [/codeblocks] [b]Note:[/b] Dictionaries with the same entries but in a different order will not have the same hash. - [b]Note:[/b] Dictionaries with equal hash values are [i]not[/i] guaranteed to be the same, because of hash collisions. On the countrary, dictionaries with different hash values are guaranteed to be different. + [b]Note:[/b] Dictionaries with equal hash values are [i]not[/i] guaranteed to be the same, because of hash collisions. On the contrary, dictionaries with different hash values are guaranteed to be different. </description> </method> <method name="is_empty" qualifiers="const"> diff --git a/doc/classes/DisplayServer.xml b/doc/classes/DisplayServer.xml index b09c81d806..dceeb20f0e 100644 --- a/doc/classes/DisplayServer.xml +++ b/doc/classes/DisplayServer.xml @@ -96,6 +96,24 @@ [b]Note:[/b] This method is implemented only on Windows. </description> </method> + <method name="file_dialog_show"> + <return type="int" enum="Error" /> + <param index="0" name="title" type="String" /> + <param index="1" name="current_directory" type="String" /> + <param index="2" name="filename" type="String" /> + <param index="3" name="show_hidden" type="bool" /> + <param index="4" name="mode" type="int" enum="DisplayServer.FileDialogMode" /> + <param index="5" name="filters" type="PackedStringArray" /> + <param index="6" name="callback" type="Callable" /> + <description> + Displays OS native dialog for selecting files or directories in the file system. + Callbacks have the following arguments: [code]bool status, PackedStringArray selected_paths[/code]. + [b]Note:[/b] This method is implemented if the display server has the [code]FEATURE_NATIVE_DIALOG[/code] feature. + [b]Note:[/b] This method is implemented on Windows and macOS. + [b]Note:[/b] On macOS, native file dialogs have no title. + [b]Note:[/b] On macOS, sandboxed apps will save security-scoped bookmarks to retain access to the opened folders across multiple sessions. Use [method OS.get_granted_permissions] to get a list of saved bookmarks. + </description> + </method> <method name="force_process_and_drop_events"> <return type="void" /> <description> @@ -757,7 +775,7 @@ <method name="is_touchscreen_available" qualifiers="const"> <return type="bool" /> <description> - Returns [code]true[/code] if touch events are available (Android or iOS), the capability is detected on the Webplatform or if [member ProjectSettings.input_devices/pointing/emulate_touch_from_mouse] is [code]true[/code]. + Returns [code]true[/code] if touch events are available (Android or iOS), the capability is detected on the Web platform or if [member ProjectSettings.input_devices/pointing/emulate_touch_from_mouse] is [code]true[/code]. </description> </method> <method name="keyboard_get_current_layout" qualifiers="const"> @@ -1729,6 +1747,21 @@ <constant name="CURSOR_MAX" value="17" enum="CursorShape"> Represents the size of the [enum CursorShape] enum. </constant> + <constant name="FILE_DIALOG_MODE_OPEN_FILE" value="0" enum="FileDialogMode"> + The native file dialog allows selecting one, and only one file. + </constant> + <constant name="FILE_DIALOG_MODE_OPEN_FILES" value="1" enum="FileDialogMode"> + The native file dialog allows selecting multiple files. + </constant> + <constant name="FILE_DIALOG_MODE_OPEN_DIR" value="2" enum="FileDialogMode"> + The native file dialog only allows selecting a directory, disallowing the selection of any file. + </constant> + <constant name="FILE_DIALOG_MODE_OPEN_ANY" value="3" enum="FileDialogMode"> + The native file dialog allows selecting one file or directory. + </constant> + <constant name="FILE_DIALOG_MODE_SAVE_FILE" value="4" enum="FileDialogMode"> + The native file dialog will warn when a file exists. + </constant> <constant name="WINDOW_MODE_WINDOWED" value="0" enum="WindowMode"> Windowed mode, i.e. [Window] doesn't occupy the whole screen (unless set to the size of the screen). </constant> diff --git a/doc/classes/EditorDebuggerPlugin.xml b/doc/classes/EditorDebuggerPlugin.xml index 3910566c06..6f6b4f69e4 100644 --- a/doc/classes/EditorDebuggerPlugin.xml +++ b/doc/classes/EditorDebuggerPlugin.xml @@ -81,7 +81,7 @@ <return type="Array" /> <description> Returns an array of [EditorDebuggerSession] currently available to this debugger plugin. - Note: Not sessions in the array may be inactive, check their state via [method EditorDebuggerSession.is_active] + [b]Note:[/b] Not sessions in the array may be inactive, check their state via [method EditorDebuggerSession.is_active] </description> </method> </methods> diff --git a/doc/classes/EditorExportPlugin.xml b/doc/classes/EditorExportPlugin.xml index fa82447894..75641a4486 100644 --- a/doc/classes/EditorExportPlugin.xml +++ b/doc/classes/EditorExportPlugin.xml @@ -8,6 +8,7 @@ To use [EditorExportPlugin], register it using the [method EditorPlugin.add_export_plugin] method first. </description> <tutorials> + <link title="Export Android plugins">$DOCS_URL/tutorials/platform/android/android_plugin.html</link> </tutorials> <methods> <method name="_begin_customize_resources" qualifiers="virtual const"> @@ -84,6 +85,64 @@ Calling [method skip] inside this callback will make the file not included in the export. </description> </method> + <method name="_get_android_dependencies" qualifiers="virtual const"> + <return type="PackedStringArray" /> + <param index="0" name="platform" type="EditorExportPlatform" /> + <param index="1" name="debug" type="bool" /> + <description> + Virtual method to be overridden by the user. This is called to retrieve the set of Android dependencies provided by this plugin. Each returned Android dependency should have the format of an Android remote binary dependency: [code]org.godot.example:my-plugin:0.0.0[/code] + For more information see [url=https://developer.android.com/build/dependencies?agpversion=4.1#dependency-types]Android documentation on dependencies[/url]. + [b]Note:[/b] Only supported on Android and requires [member EditorExportPlatformAndroid.gradle_build/use_gradle_build] to be enabled. + </description> + </method> + <method name="_get_android_dependencies_maven_repos" qualifiers="virtual const"> + <return type="PackedStringArray" /> + <param index="0" name="platform" type="EditorExportPlatform" /> + <param index="1" name="debug" type="bool" /> + <description> + Virtual method to be overridden by the user. This is called to retrieve the URLs of Maven repositories for the set of Android dependencies provided by this plugin. + For more information see [url=https://docs.gradle.org/current/userguide/dependency_management.html#sec:maven_repo]Gradle documentation on dependency management[/url]. + [b]Note:[/b] Google's Maven repo and the Maven Central repo are already included by default. + [b]Note:[/b] Only supported on Android and requires [member EditorExportPlatformAndroid.gradle_build/use_gradle_build] to be enabled. + </description> + </method> + <method name="_get_android_libraries" qualifiers="virtual const"> + <return type="PackedStringArray" /> + <param index="0" name="platform" type="EditorExportPlatform" /> + <param index="1" name="debug" type="bool" /> + <description> + Virtual method to be overridden by the user. This is called to retrieve the local paths of the Android libraries archive (AAR) files provided by this plugin. + [b]Note:[/b] Relative paths **must** be relative to Godot's [code]res://addons/[/code] directory. For example, an AAR file located under [code]res://addons/hello_world_plugin/HelloWorld.release.aar[/code] can be returned as an absolute path using [code]res://addons/hello_world_plugin/HelloWorld.release.aar[/code] or a relative path using [code]hello_world_plugin/HelloWorld.release.aar[/code]. + [b]Note:[/b] Only supported on Android and requires [member EditorExportPlatformAndroid.gradle_build/use_gradle_build] to be enabled. + </description> + </method> + <method name="_get_android_manifest_activity_element_contents" qualifiers="virtual const"> + <return type="String" /> + <param index="0" name="platform" type="EditorExportPlatform" /> + <param index="1" name="debug" type="bool" /> + <description> + Virtual method to be overridden by the user. This is used at export time to update the contents of the [code]activity[/code] element in the generated Android manifest. + [b]Note:[/b] Only supported on Android and requires [member EditorExportPlatformAndroid.gradle_build/use_gradle_build] to be enabled. + </description> + </method> + <method name="_get_android_manifest_application_element_contents" qualifiers="virtual const"> + <return type="String" /> + <param index="0" name="platform" type="EditorExportPlatform" /> + <param index="1" name="debug" type="bool" /> + <description> + Virtual method to be overridden by the user. This is used at export time to update the contents of the [code]application[/code] element in the generated Android manifest. + [b]Note:[/b] Only supported on Android and requires [member EditorExportPlatformAndroid.gradle_build/use_gradle_build] to be enabled. + </description> + </method> + <method name="_get_android_manifest_element_contents" qualifiers="virtual const"> + <return type="String" /> + <param index="0" name="platform" type="EditorExportPlatform" /> + <param index="1" name="debug" type="bool" /> + <description> + Virtual method to be overridden by the user. This is used at export time to update the contents of the [code]manifest[/code] element in the generated Android manifest. + [b]Note:[/b] Only supported on Android and requires [member EditorExportPlatformAndroid.gradle_build/use_gradle_build] to be enabled. + </description> + </method> <method name="_get_customization_configuration_hash" qualifiers="virtual const"> <return type="int" /> <description> @@ -99,6 +158,15 @@ Return a [PackedStringArray] of additional features this preset, for the given [param platform], should have. </description> </method> + <method name="_get_export_option_warning" qualifiers="virtual const"> + <return type="String" /> + <param index="0" name="platform" type="EditorExportPlatform" /> + <param index="1" name="option" type="String" /> + <description> + Check the requirements for the given [param option] and return a non-empty warning string if they are not met. + [b]Note:[/b] Use [method get_option] to check the value of the export options. + </description> + </method> <method name="_get_export_options" qualifiers="virtual const"> <return type="Dictionary[]" /> <param index="0" name="platform" type="EditorExportPlatform" /> @@ -124,6 +192,13 @@ Return [code]true[/code], if the result of [method _get_export_options] has changed and the export options of preset corresponding to [param platform] should be updated. </description> </method> + <method name="_supports_platform" qualifiers="virtual const"> + <return type="bool" /> + <param index="0" name="platform" type="EditorExportPlatform" /> + <description> + Return [code]true[/code] if the plugin supports the given [param platform]. + </description> + </method> <method name="add_file"> <return type="void" /> <param index="0" name="path" type="String" /> diff --git a/doc/classes/EditorFeatureProfile.xml b/doc/classes/EditorFeatureProfile.xml index d652759dc7..3aa1e63aac 100644 --- a/doc/classes/EditorFeatureProfile.xml +++ b/doc/classes/EditorFeatureProfile.xml @@ -51,6 +51,7 @@ <param index="0" name="path" type="String" /> <description> Loads an editor feature profile from a file. The file must follow the JSON format obtained by using the feature profile manager's [b]Export[/b] button or the [method save_to_file] method. + [b]Note:[/b] Feature profiles created via the user interface are loaded from the [code]feature_profiles[/code] directory, as a file with the [code].profile[/code] extension. The editor configuration folder can be found by using [method EditorPaths.get_config_dir]. </description> </method> <method name="save_to_file"> @@ -58,6 +59,7 @@ <param index="0" name="path" type="String" /> <description> Saves the editor feature profile to a file in JSON format. It can then be imported using the feature profile manager's [b]Import[/b] button or the [method load_from_file] method. + [b]Note:[/b] Feature profiles created via the user interface are saved in the [code]feature_profiles[/code] directory, as a file with the [code].profile[/code] extension. The editor configuration folder can be found by using [method EditorPaths.get_config_dir]. </description> </method> <method name="set_disable_class"> diff --git a/doc/classes/EditorInterface.xml b/doc/classes/EditorInterface.xml index f8002c260f..c75e49e87f 100644 --- a/doc/classes/EditorInterface.xml +++ b/doc/classes/EditorInterface.xml @@ -54,6 +54,14 @@ Returns the current directory being viewed in the [FileSystemDock]. If a file is selected, its base directory will be returned using [method String.get_base_dir] instead. </description> </method> + <method name="get_current_feature_profile" qualifiers="const"> + <return type="String" /> + <description> + Returns the name of the currently activated feature profile. If the default profile is currently active, an empty string is returned instead. + In order to get a reference to the [EditorFeatureProfile], you must load the feature profile using [method EditorFeatureProfile.load_from_file]. + [b]Note:[/b] Feature profiles created via the user interface are loaded from the [code]feature_profiles[/code] directory, as a file with the [code].profile[/code] extension. The editor configuration folder can be found by using [method EditorPaths.get_config_dir]. + </description> + </method> <method name="get_current_path" qualifiers="const"> <return type="String" /> <description> @@ -283,6 +291,15 @@ Selects the file, with the path provided by [param file], in the FileSystem dock. </description> </method> + <method name="set_current_feature_profile"> + <return type="void" /> + <param index="0" name="profile_name" type="String" /> + <description> + Selects and activates the specified feature profile with the given [param profile_name]. Set [param profile_name] to an empty string to reset to the default feature profile. + A feature profile can be created programmatically using the [EditorFeatureProfile] class. + [b]Note:[/b] The feature profile that gets activated must be located in the [code]feature_profiles[/code] directory, as a file with the [code].profile[/code] extension. If a profile could not be found, an error occurs. The editor configuration folder can be found by using [method EditorPaths.get_config_dir]. + </description> + </method> <method name="set_main_screen_editor"> <return type="void" /> <param index="0" name="name" type="String" /> diff --git a/doc/classes/EditorPlugin.xml b/doc/classes/EditorPlugin.xml index 957b6d8e88..3429062328 100644 --- a/doc/classes/EditorPlugin.xml +++ b/doc/classes/EditorPlugin.xml @@ -280,6 +280,34 @@ [/codeblock] </description> </method> + <method name="_get_unsaved_status" qualifiers="virtual const"> + <return type="String" /> + <param index="0" name="for_scene" type="String" /> + <description> + Override this method to provide a custom message that lists unsaved changes. The editor will call this method when exiting or when closing a scene, and display the returned string in a confirmation dialog. Return empty string if the plugin has no unsaved changes. + When closing a scene, [param for_scene] is the path to the scene being closed. You can use it to handle built-in resources in that scene. + If the user confirms saving, [method _save_external_data] will be called, before closing the editor. + [codeblock] + func _get_unsaved_status(for_scene): + if not unsaved: + return "" + + if for_scene.is_empty(): + return "Save changes in MyCustomPlugin before closing?" + else: + return "Scene %s has changes from MyCustomPlugin. Save before closing?" % for_scene.get_file() + + func _save_external_data(): + unsaved = false + [/codeblock] + If the plugin has no scene-specific changes, you can ignore the calls when closing scenes: + [codeblock] + func _get_unsaved_status(for_scene): + if not for_scene.is_empty(): + return "" + [/codeblock] + </description> + </method> <method name="_get_window_layout" qualifiers="virtual"> <return type="void" /> <param index="0" name="configuration" type="ConfigFile" /> @@ -541,6 +569,12 @@ Returns the [PopupMenu] under [b]Scene > Export As...[/b]. </description> </method> + <method name="get_plugin_version" qualifiers="const"> + <return type="String" /> + <description> + Provide the version of the plugin declared in the [code]plugin.cfg[/code] config file. + </description> + </method> <method name="get_script_create_dialog"> <return type="ScriptCreateDialog" /> <description> diff --git a/doc/classes/EditorSettings.xml b/doc/classes/EditorSettings.xml index 05a3e22830..85efe4362e 100644 --- a/doc/classes/EditorSettings.xml +++ b/doc/classes/EditorSettings.xml @@ -232,14 +232,16 @@ <member name="editors/2d/bone_outline_color" type="Color" setter="" getter=""> The outline color to use for non-selected bones in the 2D skeleton editor. See also [member editors/2d/bone_selected_color]. </member> - <member name="editors/2d/bone_outline_size" type="int" setter="" getter=""> + <member name="editors/2d/bone_outline_size" type="float" setter="" getter=""> The outline size in the 2D skeleton editor (in pixels). See also [member editors/2d/bone_width]. + [b]Note:[/b] Changes to this value only apply after modifying a [Bone2D] node in any way, or closing and reopening the scene. </member> <member name="editors/2d/bone_selected_color" type="Color" setter="" getter=""> The color to use for selected bones in the 2D skeleton editor. See also [member editors/2d/bone_outline_color]. </member> - <member name="editors/2d/bone_width" type="int" setter="" getter=""> + <member name="editors/2d/bone_width" type="float" setter="" getter=""> The bone width in the 2D skeleton editor (in pixels). See also [member editors/2d/bone_outline_size]. + [b]Note:[/b] Changes to this value only apply after modifying a [Bone2D] node in any way, or closing and reopening the scene. </member> <member name="editors/2d/grid_color" type="Color" setter="" getter=""> The grid color to use in the 2D editor. @@ -250,6 +252,9 @@ <member name="editors/2d/smart_snapping_line_color" type="Color" setter="" getter=""> The color to use when drawing smart snapping lines in the 2D editor. The smart snapping lines will automatically display when moving 2D nodes if smart snapping is enabled in the Snapping Options menu at the top of the 2D editor viewport. </member> + <member name="editors/2d/use_integer_zoom_by_default" type="bool" setter="" getter=""> + If [code]true[/code], the 2D editor will snap to integer zoom values while not holding the [kbd]Alt[/kbd] key and powers of two while holding it. If [code]false[/code], this behavior is swapped. + </member> <member name="editors/2d/viewport_border_color" type="Color" setter="" getter=""> The color of the viewport border in the 2D editor. This border represents the viewport's size at the base resolution defined in the Project Settings. Objects placed outside this border will not be visible unless a [Camera2D] node is used, or unless the window is resized and the stretch mode is set to [code]disabled[/code]. </member> @@ -384,9 +389,6 @@ <member name="editors/animation/autorename_animation_tracks" type="bool" setter="" getter=""> If [code]true[/code], automatically updates animation tracks' target paths when renaming or reparenting nodes in the Scene tree dock. </member> - <member name="editors/animation/confirm_insert_track" type="bool" setter="" getter=""> - If [code]true[/code], display a confirmation dialog when adding a new track to an animation by pressing the "key" icon next to a property. - </member> <member name="editors/animation/default_create_bezier_tracks" type="bool" setter="" getter=""> If [code]true[/code], create a Bezier track instead of a standard track when pressing the "key" icon next to a property. Bezier tracks provide more control over animation curves, but are more difficult to adjust quickly. </member> diff --git a/doc/classes/EditorVCSInterface.xml b/doc/classes/EditorVCSInterface.xml index 0acc53fa58..baf69f2c6d 100644 --- a/doc/classes/EditorVCSInterface.xml +++ b/doc/classes/EditorVCSInterface.xml @@ -240,7 +240,7 @@ <return type="void" /> <param index="0" name="msg" type="String" /> <description> - Pops up an error message in the edior which is shown as coming from the underlying VCS. Use this to show VCS specific error messages. + Pops up an error message in the editor which is shown as coming from the underlying VCS. Use this to show VCS specific error messages. </description> </method> </methods> diff --git a/doc/classes/FileDialog.xml b/doc/classes/FileDialog.xml index 1020d2dfbc..75856e91d8 100644 --- a/doc/classes/FileDialog.xml +++ b/doc/classes/FileDialog.xml @@ -83,6 +83,10 @@ If [code]true[/code], the dialog will show hidden files. </member> <member name="title" type="String" setter="set_title" getter="get_title" overrides="Window" default=""Save a File"" /> + <member name="use_native_dialog" type="bool" setter="set_use_native_dialog" getter="get_use_native_dialog" default="false"> + If [code]true[/code], [member access] is set to [constant ACCESS_FILESYSTEM], and it is supported by the current [DisplayServer], OS native dialog will be used instead of custom one. + [b]Note:[/b] On macOS, sandboxed apps always use native dialogs to access host filesystem. + </member> </members> <signals> <signal name="dir_selected"> diff --git a/doc/classes/FontFile.xml b/doc/classes/FontFile.xml index 2bd964053b..ba4761e900 100644 --- a/doc/classes/FontFile.xml +++ b/doc/classes/FontFile.xml @@ -134,7 +134,7 @@ <return type="int" /> <param index="0" name="cache_index" type="int" /> <description> - Recturns an active face index in the TrueType / OpenType collection. + Returns an active face index in the TrueType / OpenType collection. </description> </method> <method name="get_glyph_advance" qualifiers="const"> diff --git a/doc/classes/GPUParticles2D.xml b/doc/classes/GPUParticles2D.xml index c4193a5b01..2928215c3e 100644 --- a/doc/classes/GPUParticles2D.xml +++ b/doc/classes/GPUParticles2D.xml @@ -18,6 +18,7 @@ <return type="Rect2" /> <description> Returns a rectangle containing the positions of all existing particles. + [b]Note:[/b] When using threaded rendering this method synchronizes the rendering thread. Calling it often may have a negative impact on performance. </description> </method> <method name="emit_particle"> @@ -49,7 +50,7 @@ Particle draw order. Uses [enum DrawOrder] values. </member> <member name="emitting" type="bool" setter="set_emitting" getter="is_emitting" default="true"> - If [code]true[/code], particles are being emitted. + If [code]true[/code], particles are being emitted. [member emitting] can be used to start and stop particles from emitting. However, if [member one_shot] is [code]true[/code] setting [member emitting] to [code]true[/code] will not restart the emission cycle until after all active particles finish processing. You can use the [signal finished] signal to be notified once all active particles finish processing. </member> <member name="explosiveness" type="float" setter="set_explosiveness_ratio" getter="get_explosiveness_ratio" default="0.0"> How rapidly particles in an emission cycle are emitted. If greater than [code]0[/code], there will be a gap in emissions before the next cycle begins. @@ -108,6 +109,14 @@ Grow the rect if particles suddenly appear/disappear when the node enters/exits the screen. The [Rect2] can be grown via code or with the [b]Particles → Generate Visibility Rect[/b] editor tool. </member> </members> + <signals> + <signal name="finished"> + <description> + Emitted when all active particles have finished processing. When [member one_shot] is disabled, particles will process continuously, so this is never emitted. + [b]Note:[/b] Due to the particles being computed on the GPU there might be a delay before the signal gets emitted. + </description> + </signal> + </signals> <constants> <constant name="DRAW_ORDER_INDEX" value="0" enum="DrawOrder"> Particles are drawn in the order emitted. diff --git a/doc/classes/GPUParticles3D.xml b/doc/classes/GPUParticles3D.xml index 8338535a24..4c0b2d12ed 100644 --- a/doc/classes/GPUParticles3D.xml +++ b/doc/classes/GPUParticles3D.xml @@ -78,7 +78,7 @@ <member name="draw_skin" type="Skin" setter="set_skin" getter="get_skin"> </member> <member name="emitting" type="bool" setter="set_emitting" getter="is_emitting" default="true"> - If [code]true[/code], particles are being emitted. + If [code]true[/code], particles are being emitted. [member emitting] can be used to start and stop particles from emitting. However, if [member one_shot] is [code]true[/code] setting [member emitting] to [code]true[/code] will not restart the emission cycle until after all active particles finish processing. You can use the [signal finished] signal to be notified once all active particles finish processing. </member> <member name="explosiveness" type="float" setter="set_explosiveness_ratio" getter="get_explosiveness_ratio" default="0.0"> Time ratio between each emission. If [code]0[/code], particles are emitted continuously. If [code]1[/code], all particles are emitted simultaneously. @@ -130,6 +130,14 @@ Grow the box if particles suddenly appear/disappear when the node enters/exits the screen. The [AABB] can be grown via code or with the [b]Particles → Generate AABB[/b] editor tool. </member> </members> + <signals> + <signal name="finished"> + <description> + Emitted when all active particles have finished processing. When [member one_shot] is disabled, particles will process continuously, so this is never emitted. + [b]Note:[/b] Due to the particles being computed on the GPU there might be a delay before the signal gets emitted. + </description> + </signal> + </signals> <constants> <constant name="DRAW_ORDER_INDEX" value="0" enum="DrawOrder"> Particles are drawn in the order emitted. diff --git a/doc/classes/GraphEdit.xml b/doc/classes/GraphEdit.xml index f6876ac21f..0aea0c5727 100644 --- a/doc/classes/GraphEdit.xml +++ b/doc/classes/GraphEdit.xml @@ -157,7 +157,7 @@ Returns an Array containing the list of connections. A connection consists in a structure of the form [code]{ from_port: 0, from: "GraphNode name 0", to_port: 1, to: "GraphNode name 1" }[/code]. </description> </method> - <method name="get_zoom_hbox"> + <method name="get_menu_hbox"> <return type="HBoxContainer" /> <description> Gets the [HBoxContainer] that contains the zooming and grid snap controls in the top left of the graph. You can use this method to reposition the toolbar or to add your own custom controls to it. @@ -255,16 +255,19 @@ <member name="right_disconnects" type="bool" setter="set_right_disconnects" getter="is_right_disconnects_enabled" default="false"> If [code]true[/code], enables disconnection of existing connections in the GraphEdit by dragging the right end. </member> - <member name="scroll_offset" type="Vector2" setter="set_scroll_ofs" getter="get_scroll_ofs" default="Vector2(0, 0)"> + <member name="scroll_offset" type="Vector2" setter="set_scroll_offset" getter="get_scroll_offset" default="Vector2(0, 0)"> The scroll offset. </member> + <member name="show_grid" type="bool" setter="set_show_grid" getter="is_showing_grid" default="true"> + If [code]true[/code], the grid is visible. + </member> <member name="show_zoom_label" type="bool" setter="set_show_zoom_label" getter="is_showing_zoom_label" default="false"> If [code]true[/code], makes a label with the current zoom level visible. The zoom value is displayed in percents. </member> - <member name="snap_distance" type="int" setter="set_snap" getter="get_snap" default="20"> - The snapping distance in pixels. + <member name="snapping_distance" type="int" setter="set_snapping_distance" getter="get_snapping_distance" default="20"> + The snapping distance in pixels, also determines the grid line distance. </member> - <member name="use_snap" type="bool" setter="set_use_snap" getter="is_using_snap" default="true"> + <member name="snapping_enabled" type="bool" setter="set_snapping_enabled" getter="is_snapping_enabled" default="true"> If [code]true[/code], enables snapping. </member> <member name="zoom" type="float" setter="set_zoom" getter="get_zoom" default="1.0"> @@ -412,23 +415,28 @@ <theme_item name="port_hotzone_outer_extent" data_type="constant" type="int" default="26"> The horizontal range within which a port can be grabbed (outer side). </theme_item> + <theme_item name="grid_toggle" data_type="icon" type="Texture2D"> + The icon for the grid toggle button. + </theme_item> <theme_item name="layout" data_type="icon" type="Texture2D"> + The icon for the layout button for auto-arranging the graph. </theme_item> - <theme_item name="minimap" data_type="icon" type="Texture2D"> + <theme_item name="minimap_toggle" data_type="icon" type="Texture2D"> + The icon for the minimap toggle button. </theme_item> - <theme_item name="minus" data_type="icon" type="Texture2D"> - The icon for the zoom out button. + <theme_item name="snapping_toggle" data_type="icon" type="Texture2D"> + The icon for the snapping toggle button. </theme_item> - <theme_item name="more" data_type="icon" type="Texture2D"> + <theme_item name="zoom_in" data_type="icon" type="Texture2D"> The icon for the zoom in button. </theme_item> - <theme_item name="reset" data_type="icon" type="Texture2D"> - The icon for the zoom reset button. + <theme_item name="zoom_out" data_type="icon" type="Texture2D"> + The icon for the zoom out button. </theme_item> - <theme_item name="snap" data_type="icon" type="Texture2D"> - The icon for the snap toggle button. + <theme_item name="zoom_reset" data_type="icon" type="Texture2D"> + The icon for the zoom reset button. </theme_item> - <theme_item name="bg" data_type="style" type="StyleBox"> + <theme_item name="panel" data_type="style" type="StyleBox"> The background drawn under the grid. </theme_item> </theme_items> diff --git a/doc/classes/GraphNode.xml b/doc/classes/GraphNode.xml index e0dacb2a79..d160141842 100644 --- a/doc/classes/GraphNode.xml +++ b/doc/classes/GraphNode.xml @@ -236,9 +236,6 @@ </method> </methods> <members> - <member name="comment" type="bool" setter="set_comment" getter="is_comment" default="false"> - If [code]true[/code], the GraphNode is a comment node. - </member> <member name="draggable" type="bool" setter="set_draggable" getter="is_draggable" default="true"> If [code]true[/code], the user can drag the GraphNode. </member> @@ -373,12 +370,6 @@ <theme_item name="breakpoint" data_type="style" type="StyleBox"> The background used when [member overlay] is set to [constant OVERLAY_BREAKPOINT]. </theme_item> - <theme_item name="comment" data_type="style" type="StyleBox"> - The [StyleBox] used when [member comment] is enabled. - </theme_item> - <theme_item name="comment_focus" data_type="style" type="StyleBox"> - The [StyleBox] used when [member comment] is enabled and the [GraphNode] is focused. - </theme_item> <theme_item name="frame" data_type="style" type="StyleBox"> The default background for [GraphNode]. </theme_item> diff --git a/doc/classes/HTTPClient.xml b/doc/classes/HTTPClient.xml index 9a8207d1fc..f0385a7814 100644 --- a/doc/classes/HTTPClient.xml +++ b/doc/classes/HTTPClient.xml @@ -41,6 +41,7 @@ <description> Returns the response's body length. [b]Note:[/b] Some Web servers may not send a body length. In this case, the value returned will be [code]-1[/code]. If using chunked transfer encoding, the body length will also be [code]-1[/code]. + [b]Note:[/b] This function always returns [code]-1[/code] on the Web platform due to browsers limitations. </description> </method> <method name="get_response_code" qualifiers="const"> diff --git a/doc/classes/Image.xml b/doc/classes/Image.xml index 3150d0f08f..81954dd7de 100644 --- a/doc/classes/Image.xml +++ b/doc/classes/Image.xml @@ -679,13 +679,13 @@ <constant name="FORMAT_DXT5_RA_AS_RG" value="34" enum="Format"> </constant> <constant name="FORMAT_ASTC_4x4" value="35" enum="Format"> - [url=https://en.wikipedia.org/wiki/Adaptive_scalable_texture_compression]Adaptive Scalable Texutre Compression[/url]. This implements the 4x4 (high quality) mode. + [url=https://en.wikipedia.org/wiki/Adaptive_scalable_texture_compression]Adaptive Scalable Texture Compression[/url]. This implements the 4x4 (high quality) mode. </constant> <constant name="FORMAT_ASTC_4x4_HDR" value="36" enum="Format"> Same format as [constant FORMAT_ASTC_4x4], but with the hint to let the GPU know it is used for HDR. </constant> <constant name="FORMAT_ASTC_8x8" value="37" enum="Format"> - [url=https://en.wikipedia.org/wiki/Adaptive_scalable_texture_compression]Adaptive Scalable Texutre Compression[/url]. This implements the 8x8 (low quality) mode. + [url=https://en.wikipedia.org/wiki/Adaptive_scalable_texture_compression]Adaptive Scalable Texture Compression[/url]. This implements the 8x8 (low quality) mode. </constant> <constant name="FORMAT_ASTC_8x8_HDR" value="38" enum="Format"> Same format as [constant FORMAT_ASTC_8x8], but with the hint to let the GPU know it is used for HDR. diff --git a/doc/classes/NavigationAgent2D.xml b/doc/classes/NavigationAgent2D.xml index 8bb1ff1dae..c1570f3149 100644 --- a/doc/classes/NavigationAgent2D.xml +++ b/doc/classes/NavigationAgent2D.xml @@ -53,7 +53,7 @@ <method name="get_final_position"> <return type="Vector2" /> <description> - Returns the reachable final position of the current navigation path in global coordinates. This can change if the navigation path is altered in any way. Because of this, it would be best to check this each frame. + Returns the reachable final position of the current navigation path in global coordinates. This position can change if the agent needs to update the navigation path which makes the agent emit the [signal path_changed] signal. </description> </method> <method name="get_navigation_layer_value" qualifiers="const"> @@ -84,13 +84,14 @@ <method name="is_navigation_finished"> <return type="bool" /> <description> - Returns true if the navigation path's final position has been reached. + Returns [code]true[/code] if the end of the currently loaded navigation path has been reached. + [b]Note:[/b] While true prefer to stop calling update functions like [method get_next_path_position]. This avoids jittering the standing agent due to calling repeated path updates. </description> </method> <method name="is_target_reachable"> <return type="bool" /> <description> - Returns true if [member target_position] is reachable. The target position is set using [member target_position]. + Returns [code]true[/code] if [method get_final_position] is within [member target_desired_distance] of the [member target_position]. </description> </method> <method name="is_target_reached" qualifiers="const"> @@ -229,17 +230,20 @@ </signal> <signal name="navigation_finished"> <description> - Notifies when the final position is reached. + Emitted once per loaded path when the agent internal navigation path index reaches the last index of the loaded path array. The agent internal navigation path index can be received with [method get_current_navigation_path_index]. </description> </signal> <signal name="path_changed"> <description> - Notifies when the navigation path changes. + Emitted when the agent had to update the loaded path: + - because path was previously empty. + - because navigation map has changed. + - because agent pushed further away from the current path segment than the [member path_max_distance]. </description> </signal> <signal name="target_reached"> <description> - Notifies when the player-defined [member target_position] is reached. + Emitted once per loaded path when the agent's global position is the first time within [member target_desired_distance] to the [member target_position]. </description> </signal> <signal name="velocity_computed"> diff --git a/doc/classes/NavigationAgent3D.xml b/doc/classes/NavigationAgent3D.xml index 448984a397..aef9b0ac56 100644 --- a/doc/classes/NavigationAgent3D.xml +++ b/doc/classes/NavigationAgent3D.xml @@ -53,7 +53,7 @@ <method name="get_final_position"> <return type="Vector3" /> <description> - Returns the reachable final position of the current navigation path in global coordinates. This position can change if the navigation path is altered in any way. Because of this, it would be best to check this each frame. + Returns the reachable final position of the current navigation path in global coordinates. This position can change if the agent needs to update the navigation path which makes the agent emit the [signal path_changed] signal. </description> </method> <method name="get_navigation_layer_value" qualifiers="const"> @@ -84,13 +84,14 @@ <method name="is_navigation_finished"> <return type="bool" /> <description> - Returns true if the navigation path's final position has been reached. + Returns [code]true[/code] if the end of the currently loaded navigation path has been reached. + [b]Note:[/b] While true prefer to stop calling update functions like [method get_next_path_position]. This avoids jittering the standing agent due to calling repeated path updates. </description> </method> <method name="is_target_reachable"> <return type="bool" /> <description> - Returns true if [member target_position] is reachable. The target position is set using [member target_position]. + Returns [code]true[/code] if [method get_final_position] is within [member target_desired_distance] of the [member target_position]. </description> </method> <method name="is_target_reached" qualifiers="const"> @@ -230,23 +231,26 @@ - [code]type[/code]: Always [constant NavigationPathQueryResult3D.PATH_SEGMENT_TYPE_LINK]. - [code]rid[/code]: The [RID] of the link. - [code]owner[/code]: The object which manages the link (usually [NavigationLink3D]). - - [code]link_entry_position[/code]: If [code]owner[/code] is available and the owner is a [NavigationLink2D], it will contain the global position of the link's point the agent is entering. - - [code]link_exit_position[/code]: If [code]owner[/code] is available and the owner is a [NavigationLink2D], it will contain the global position of the link's point which the agent is exiting. + - [code]link_entry_position[/code]: If [code]owner[/code] is available and the owner is a [NavigationLink3D], it will contain the global position of the link's point the agent is entering. + - [code]link_exit_position[/code]: If [code]owner[/code] is available and the owner is a [NavigationLink3D], it will contain the global position of the link's point which the agent is exiting. </description> </signal> <signal name="navigation_finished"> <description> - Notifies when the final position is reached. + Emitted once per loaded path when the agent internal navigation path index reaches the last index of the loaded path array. The agent internal navigation path index can be received with [method get_current_navigation_path_index]. </description> </signal> <signal name="path_changed"> <description> - Notifies when the navigation path changes. + Emitted when the agent had to update the loaded path: + - because path was previously empty. + - because navigation map has changed. + - because agent pushed further away from the current path segment than the [member path_max_distance]. </description> </signal> <signal name="target_reached"> <description> - Notifies when the player-defined [member target_position] is reached. + Emitted once per loaded path when the agent's global position is the first time within [member target_desired_distance] to the [member target_position]. </description> </signal> <signal name="velocity_computed"> diff --git a/doc/classes/NavigationMesh.xml b/doc/classes/NavigationMesh.xml index 3dcda01f76..464ffd50af 100644 --- a/doc/classes/NavigationMesh.xml +++ b/doc/classes/NavigationMesh.xml @@ -18,6 +18,12 @@ Adds a polygon using the indices of the vertices you get when calling [method get_vertices]. </description> </method> + <method name="clear"> + <return type="void" /> + <description> + Clears the internal arrays for vertices and polygon indices. + </description> + </method> <method name="clear_polygons"> <return type="void" /> <description> @@ -105,8 +111,8 @@ <member name="edge_max_error" type="float" setter="set_edge_max_error" getter="get_edge_max_error" default="1.3"> The maximum distance a simplified contour's border edges should deviate the original raw contour. </member> - <member name="edge_max_length" type="float" setter="set_edge_max_length" getter="get_edge_max_length" default="12.0"> - The maximum allowed length for contour edges along the border of the mesh. + <member name="edge_max_length" type="float" setter="set_edge_max_length" getter="get_edge_max_length" default="0.0"> + The maximum allowed length for contour edges along the border of the mesh. A value of [code]0.0[/code] disables this feature. [b]Note:[/b] While baking, this value will be rounded up to the nearest multiple of [member cell_size]. </member> <member name="filter_baking_aabb" type="AABB" setter="set_filter_baking_aabb" getter="get_filter_baking_aabb" default="AABB(0, 0, 0, 0, 0, 0)"> diff --git a/doc/classes/NavigationPolygon.xml b/doc/classes/NavigationPolygon.xml index 1c44ffbd57..fe28c5a468 100644 --- a/doc/classes/NavigationPolygon.xml +++ b/doc/classes/NavigationPolygon.xml @@ -69,6 +69,12 @@ Adds a polygon using the indices of the vertices you get when calling [method get_vertices]. </description> </method> + <method name="clear"> + <return type="void" /> + <description> + Clears the internal arrays for vertices and polygon indices. + </description> + </method> <method name="clear_outlines"> <return type="void" /> <description> diff --git a/doc/classes/NavigationServer2D.xml b/doc/classes/NavigationServer2D.xml index 7f0e29676b..32215316d1 100644 --- a/doc/classes/NavigationServer2D.xml +++ b/doc/classes/NavigationServer2D.xml @@ -207,6 +207,13 @@ Create a new link between two positions on a map. </description> </method> + <method name="link_get_enabled" qualifiers="const"> + <return type="bool" /> + <param index="0" name="link" type="RID" /> + <description> + Returns [code]true[/code] if the specified [param link] is enabled. + </description> + </method> <method name="link_get_end_position" qualifiers="const"> <return type="Vector2" /> <param index="0" name="link" type="RID" /> @@ -271,6 +278,14 @@ Sets whether this [param link] can be travelled in both directions. </description> </method> + <method name="link_set_enabled"> + <return type="void" /> + <param index="0" name="link" type="RID" /> + <param index="1" name="enabled" type="bool" /> + <description> + If [param enabled] is [code]true[/code] the specified [param link] will contribute to its current navigation map. + </description> + </method> <method name="link_set_end_position"> <return type="void" /> <param index="0" name="link" type="RID" /> @@ -601,6 +616,13 @@ Returns how many connections this [param region] has with other regions in the map. </description> </method> + <method name="region_get_enabled" qualifiers="const"> + <return type="bool" /> + <param index="0" name="region" type="RID" /> + <description> + Returns [code]true[/code] if the specified [param region] is enabled. + </description> + </method> <method name="region_get_enter_cost" qualifiers="const"> <return type="float" /> <param index="0" name="region" type="RID" /> @@ -653,6 +675,14 @@ [b]Note:[/b] If navigation meshes from different navigation regions overlap (which should be avoided in general) the result might not be what is expected. </description> </method> + <method name="region_set_enabled"> + <return type="void" /> + <param index="0" name="region" type="RID" /> + <param index="1" name="enabled" type="bool" /> + <description> + If [param enabled] is [code]true[/code] the specified [param region] will contribute to its current navigation map. + </description> + </method> <method name="region_set_enter_cost"> <return type="void" /> <param index="0" name="region" type="RID" /> diff --git a/doc/classes/NavigationServer3D.xml b/doc/classes/NavigationServer3D.xml index b979cd0238..ebde6fdf7b 100644 --- a/doc/classes/NavigationServer3D.xml +++ b/doc/classes/NavigationServer3D.xml @@ -4,7 +4,7 @@ A server interface for low-level 3D navigation access. </brief_description> <description> - NavigationServer2D is the server that handles navigation maps, regions and agents. It does not handle A* navigation from [AStar3D]. + NavigationServer3D is the server that handles navigation maps, regions and agents. It does not handle A* navigation from [AStar3D]. Maps are made up of regions, which are made of navigation meshes. Together, they define the navigable areas in the 3D world. [b]Note:[/b] Most [NavigationServer3D] changes take effect after the next physics frame and not immediately. This includes all changes made to maps, regions or agents by navigation-related nodes in the scene tree or made through scripts. For two regions to be connected to each other, they must share a similar edge. An edge is considered connected to another if both of its two vertices are at a distance less than [code]edge_connection_margin[/code] to the respective other edge's vertex. @@ -248,6 +248,13 @@ Create a new link between two positions on a map. </description> </method> + <method name="link_get_enabled" qualifiers="const"> + <return type="bool" /> + <param index="0" name="link" type="RID" /> + <description> + Returns [code]true[/code] if the specified [param link] is enabled. + </description> + </method> <method name="link_get_end_position" qualifiers="const"> <return type="Vector3" /> <param index="0" name="link" type="RID" /> @@ -312,6 +319,14 @@ Sets whether this [param link] can be travelled in both directions. </description> </method> + <method name="link_set_enabled"> + <return type="void" /> + <param index="0" name="link" type="RID" /> + <param index="1" name="enabled" type="bool" /> + <description> + If [param enabled] is [code]true[/code] the specified [param link] will contribute to its current navigation map. + </description> + </method> <method name="link_set_end_position"> <return type="void" /> <param index="0" name="link" type="RID" /> @@ -734,6 +749,13 @@ Returns how many connections this [param region] has with other regions in the map. </description> </method> + <method name="region_get_enabled" qualifiers="const"> + <return type="bool" /> + <param index="0" name="region" type="RID" /> + <description> + Returns [code]true[/code] if the specified [param region] is enabled. + </description> + </method> <method name="region_get_enter_cost" qualifiers="const"> <return type="float" /> <param index="0" name="region" type="RID" /> @@ -786,6 +808,14 @@ [b]Note:[/b] If navigation meshes from different navigation regions overlap (which should be avoided in general) the result might not be what is expected. </description> </method> + <method name="region_set_enabled"> + <return type="void" /> + <param index="0" name="region" type="RID" /> + <param index="1" name="enabled" type="bool" /> + <description> + If [param enabled] is [code]true[/code] the specified [param region] will contribute to its current navigation map. + </description> + </method> <method name="region_set_enter_cost"> <return type="void" /> <param index="0" name="region" type="RID" /> diff --git a/doc/classes/Node.xml b/doc/classes/Node.xml index 7cf84d72c9..ce02c3e51a 100644 --- a/doc/classes/Node.xml +++ b/doc/classes/Node.xml @@ -761,8 +761,8 @@ <param index="0" name="id" type="int" /> <param index="1" name="recursive" type="bool" default="true" /> <description> - 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 [param recursive], the given peer is recursively set as the authority for all children of this node. - [b]Warning:[/b] This does [b]not[/b] automatically replicate the new authority to other peers. It is developer's responsibility to do so. You can propagate the information about the new authority using [member MultiplayerSpawner.spawn_function], an RPC, or using a [MultiplayerSynchronizer]. + 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]. Defaults to peer ID 1 (the server). If [param recursive], the given peer is recursively set as the authority for all children of this node. + [b]Warning:[/b] This does [b]not[/b] automatically replicate the new authority to other peers. It is developer's responsibility to do so. You can propagate the information about the new authority using [member MultiplayerSpawner.spawn_function], an RPC, or using a [MultiplayerSynchronizer]. Also, the parent's authority does [b]not[/b] propagate to newly added children. </description> </method> <method name="set_physics_process"> diff --git a/doc/classes/Node3D.xml b/doc/classes/Node3D.xml index ea4c6366f2..b5ead07a10 100644 --- a/doc/classes/Node3D.xml +++ b/doc/classes/Node3D.xml @@ -291,7 +291,7 @@ Access to the node rotation as a [Quaternion]. This property is ideal for tweening complex rotations. </member> <member name="rotation" type="Vector3" setter="set_rotation" getter="get_rotation" default="Vector3(0, 0, 0)"> - Rotation part of the local transformation in radians, specified in terms of Euler angles. The angles construct a rotaton in the order specified by the [member rotation_order] property. + Rotation part of the local transformation in radians, specified in terms of Euler angles. The angles construct a rotation in the order specified by the [member rotation_order] property. [b]Note:[/b] In the mathematical sense, rotation is a matrix and not a vector. The three Euler angles, which are the three independent parameters of the Euler-angle parametrization of the rotation matrix, are stored in a [Vector3] data structure not because the rotation is a vector, but only because [Vector3] exists as a convenient data-structure to store 3 floating-point numbers. Therefore, applying affine operations on the rotation "vector" is not meaningful. [b]Note:[/b] This property is edited in the inspector in degrees. If you want to use degrees in a script, use [member rotation_degrees]. </member> diff --git a/doc/classes/OS.xml b/doc/classes/OS.xml index d8d0078b77..03169d390a 100644 --- a/doc/classes/OS.xml +++ b/doc/classes/OS.xml @@ -240,8 +240,8 @@ <method name="get_granted_permissions" qualifiers="const"> <return type="PackedStringArray" /> <description> - With this function, you can get the list of dangerous permissions that have been granted to the Android application. - [b]Note:[/b] This method is implemented only on Android. + On Android devices: With this function, you can get the list of dangerous permissions that have been granted. + On macOS (sandboxed applications only): This function returns the list of user selected folders accessible to the application. Use native file dialog to request folder access permission. </description> </method> <method name="get_keycode_string" qualifiers="const"> @@ -534,6 +534,13 @@ Returns [code]true[/code] if the project will automatically restart when it exits for any reason, [code]false[/code] otherwise. See also [method set_restart_on_exit] and [method get_restart_on_exit_arguments]. </description> </method> + <method name="is_sandboxed" qualifiers="const"> + <return type="bool" /> + <description> + Returns [code]true[/code] if application is running in the sandbox. + [b]Note:[/b] This method is implemented on macOS. + </description> + </method> <method name="is_stdout_verbose" qualifiers="const"> <return type="bool" /> <description> @@ -602,6 +609,12 @@ [b]Note:[/b] This method is implemented only on Android. </description> </method> + <method name="revoke_granted_permissions"> + <return type="void" /> + <description> + On macOS (sandboxed applications only), this function clears list of user selected folders accessible to the application. + </description> + </method> <method name="set_environment" qualifiers="const"> <return type="void" /> <param index="0" name="variable" type="String" /> diff --git a/doc/classes/PackedByteArray.xml b/doc/classes/PackedByteArray.xml index 51f353c17a..60d3a55711 100644 --- a/doc/classes/PackedByteArray.xml +++ b/doc/classes/PackedByteArray.xml @@ -346,7 +346,7 @@ <param index="0" name="byte_offset" type="int" /> <param index="1" name="allow_objects" type="bool" default="false" /> <description> - Returns [code]true[/code] if a valid [Variant] value can be decoded at the [param byte_offset]. Returns [code]false[/code] othewrise or when the value is [Object]-derived and [param allow_objects] is [code]false[/code]. + Returns [code]true[/code] if a valid [Variant] value can be decoded at the [param byte_offset]. Returns [code]false[/code] otherwise or when the value is [Object]-derived and [param allow_objects] is [code]false[/code]. </description> </method> <method name="hex_encode" qualifiers="const"> diff --git a/doc/classes/PhysicsServer3D.xml b/doc/classes/PhysicsServer3D.xml index a21be3f668..f3441f45ed 100644 --- a/doc/classes/PhysicsServer3D.xml +++ b/doc/classes/PhysicsServer3D.xml @@ -4,7 +4,7 @@ A server interface for low-level 3D physics access. </brief_description> <description> - PhysicsServer2D is the server responsible for all 2D physics. It can directly create and manipulate all physics objects: + PhysicsServer3D is the server responsible for all 3D physics. It can directly create and manipulate all physics objects: - A [i]space[/i] is a self-contained world for a physics simulation. It contains bodies, areas, and joints. Its state can be queried for collision and intersection information, and several parameters of the simulation can be modified. - A [i]shape[/i] is a geometric shape such as a sphere, a box, a cylinder, or a polygon. It can be used for collision detection by adding it to a body/area, possibly with an extra transformation relative to the body/area's origin. Bodies/areas can have multiple (transformed) shapes added to them, and a single shape can be added to bodies/areas multiple times with different local transformations. - A [i]body[/i] is a physical object which can be in static, kinematic, or rigid mode. Its state (such as position and velocity) can be queried and updated. A force integration callback can be set to customize the body's physics. @@ -1146,7 +1146,7 @@ A factor applied to the movement across the slider axis once the limits get surpassed. The lower, the slower the movement. </constant> <constant name="SLIDER_JOINT_LINEAR_LIMIT_RESTITUTION" value="3" enum="SliderJointParam"> - The amount of restitution once the limits are surpassed. The lower, the more velocityenergy gets lost. + The amount of restitution once the limits are surpassed. The lower, the more velocity-energy gets lost. </constant> <constant name="SLIDER_JOINT_LINEAR_LIMIT_DAMPING" value="4" enum="SliderJointParam"> The amount of damping once the slider limits are surpassed. diff --git a/doc/classes/PinJoint3D.xml b/doc/classes/PinJoint3D.xml index 3394a1d5fb..9009f0b658 100644 --- a/doc/classes/PinJoint3D.xml +++ b/doc/classes/PinJoint3D.xml @@ -4,7 +4,7 @@ A physics joint that attaches two 3D physics bodies at a single point, allowing them to freely rotate. </brief_description> <description> - A physics joint that attaches two 2D physics bodies at a single point, allowing them to freely rotate. For example, a [RigidBody3D] can be attached to a [StaticBody3D] to create a pendulum or a seesaw. + A physics joint that attaches two 3D physics bodies at a single point, allowing them to freely rotate. For example, a [RigidBody3D] can be attached to a [StaticBody3D] to create a pendulum or a seesaw. </description> <tutorials> </tutorials> diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index 744c72af4d..6f786c5cf1 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -819,7 +819,7 @@ Name of the .NET assembly. This name is used as the name of the [code].csproj[/code] and [code].sln[/code] files. By default, it's set to the name of the project ([member application/config/name]) allowing to change it in the future without affecting the .NET assembly. </member> <member name="dotnet/project/assembly_reload_attempts" type="int" setter="" getter="" default="3"> - Number of times to attempt assembly reloading after rebuilding .NET assembies. Effectively also the timeout in seconds to wait for unloading of script assemblies to finish. + Number of times to attempt assembly reloading after rebuilding .NET assemblies. Effectively also the timeout in seconds to wait for unloading of script assemblies to finish. </member> <member name="dotnet/project/solution_directory" type="String" setter="" getter="" default=""""> Directory that contains the [code].sln[/code] file. By default, the [code].sln[/code] files is in the root of the project directory, next to the [code]project.godot[/code] and [code].csproj[/code] files. @@ -1943,13 +1943,13 @@ <member name="memory/limits/multithreaded_server/rid_pool_prealloc" type="int" setter="" getter="" default="60"> This is used by servers when used in multi-threading mode (servers and visual). RIDs are preallocated to avoid stalling the server requesting them on threads. If servers get stalled too often when loading resources in a thread, increase this number. </member> - <member name="navigation/2d/default_cell_size" type="int" setter="" getter="" default="1"> + <member name="navigation/2d/default_cell_size" type="float" setter="" getter="" default="1.0"> Default cell size for 2D navigation maps. See [method NavigationServer2D.map_set_cell_size]. </member> - <member name="navigation/2d/default_edge_connection_margin" type="int" setter="" getter="" default="1"> + <member name="navigation/2d/default_edge_connection_margin" type="float" setter="" getter="" default="1.0"> Default edge connection margin for 2D navigation maps. See [method NavigationServer2D.map_set_edge_connection_margin]. </member> - <member name="navigation/2d/default_link_connection_radius" type="int" setter="" getter="" default="4"> + <member name="navigation/2d/default_link_connection_radius" type="float" setter="" getter="" default="4.0"> Default link connection radius for 2D navigation maps. See [method NavigationServer2D.map_set_link_connection_radius]. </member> <member name="navigation/2d/use_edge_connections" type="bool" setter="" getter="" default="true"> @@ -1967,6 +1967,9 @@ <member name="navigation/3d/default_link_connection_radius" type="float" setter="" getter="" default="1.0"> Default link connection radius for 3D navigation maps. See [method NavigationServer3D.map_set_link_connection_radius]. </member> + <member name="navigation/3d/default_up" type="Vector3" setter="" getter="" default="Vector3(0, 1, 0)"> + Default up orientation for 3D navigation maps. See [method NavigationServer3D.map_set_up]. + </member> <member name="navigation/3d/use_edge_connections" type="bool" setter="" getter="" default="true"> If enabled 3D navigation regions will use edge connections to connect with other navigation regions within proximity of the navigation map edge connection margin. This setting only affects World3D default navigation maps. </member> diff --git a/doc/classes/RayCast3D.xml b/doc/classes/RayCast3D.xml index 16471036cd..83476a6d48 100644 --- a/doc/classes/RayCast3D.xml +++ b/doc/classes/RayCast3D.xml @@ -131,6 +131,9 @@ <member name="exclude_parent" type="bool" setter="set_exclude_parent_body" getter="get_exclude_parent_body" default="true"> If [code]true[/code], collisions will be ignored for this RayCast3D's immediate parent. </member> + <member name="hit_back_faces" type="bool" setter="set_hit_back_faces" getter="is_hit_back_faces_enabled" default="true"> + If [code]true[/code], the ray will hit back faces with concave polygon shapes with back face enabled or heightmap shapes. + </member> <member name="hit_from_inside" type="bool" setter="set_hit_from_inside" getter="is_hit_from_inside_enabled" default="false"> If [code]true[/code], the ray will detect a hit when starting inside shapes. In this case the collision normal will be [code]Vector3(0, 0, 0)[/code]. Does not affect shapes with no volume like concave polygon or heightmap. </member> diff --git a/doc/classes/RenderingDevice.xml b/doc/classes/RenderingDevice.xml index ab993c372a..3205a33168 100644 --- a/doc/classes/RenderingDevice.xml +++ b/doc/classes/RenderingDevice.xml @@ -16,8 +16,8 @@ <methods> <method name="barrier"> <return type="void" /> - <param index="0" name="from" type="int" enum="RenderingDevice.BarrierMask" is_bitfield="true" default="7" /> - <param index="1" name="to" type="int" enum="RenderingDevice.BarrierMask" is_bitfield="true" default="7" /> + <param index="0" name="from" type="int" enum="RenderingDevice.BarrierMask" is_bitfield="true" default="15" /> + <param index="1" name="to" type="int" enum="RenderingDevice.BarrierMask" is_bitfield="true" default="15" /> <description> Puts a memory barrier in place. This is used for synchronization to avoid data races. See also [method full_barrier], which may be useful for debugging. </description> @@ -27,7 +27,7 @@ <param index="0" name="buffer" type="RID" /> <param index="1" name="offset" type="int" /> <param index="2" name="size_bytes" type="int" /> - <param index="3" name="post_barrier" type="int" enum="RenderingDevice.BarrierMask" is_bitfield="true" default="7" /> + <param index="3" name="post_barrier" type="int" enum="RenderingDevice.BarrierMask" is_bitfield="true" default="15" /> <description> </description> </method> @@ -46,7 +46,7 @@ <param index="1" name="offset" type="int" /> <param index="2" name="size_bytes" type="int" /> <param index="3" name="data" type="PackedByteArray" /> - <param index="4" name="post_barrier" type="int" enum="RenderingDevice.BarrierMask" is_bitfield="true" default="7" /> + <param index="4" name="post_barrier" type="int" enum="RenderingDevice.BarrierMask" is_bitfield="true" default="15" /> <description> </description> </method> @@ -114,7 +114,7 @@ </method> <method name="compute_list_end"> <return type="void" /> - <param index="0" name="post_barrier" type="int" enum="RenderingDevice.BarrierMask" is_bitfield="true" default="7" /> + <param index="0" name="post_barrier" type="int" enum="RenderingDevice.BarrierMask" is_bitfield="true" default="15" /> <description> Finishes a list of compute commands created with the [code]compute_*[/code] methods. </description> @@ -214,7 +214,7 @@ <param index="0" name="screen" type="int" default="0" /> <param index="1" name="clear_color" type="Color" default="Color(0, 0, 0, 1)" /> <description> - High-level variant of [method draw_list_begin], with the parameters automtaically being adjusted for drawing onto the window specified by the [param screen] ID. + High-level variant of [method draw_list_begin], with the parameters automatically being adjusted for drawing onto the window specified by the [param screen] ID. [b]Note:[/b] Cannot be used with local RenderingDevices, as these don't have a screen. If called on a local RenderingDevice, [method draw_list_begin_for_screen] returns [constant INVALID_ID]. </description> </method> @@ -296,7 +296,7 @@ </method> <method name="draw_list_end"> <return type="void" /> - <param index="0" name="post_barrier" type="int" enum="RenderingDevice.BarrierMask" is_bitfield="true" default="7" /> + <param index="0" name="post_barrier" type="int" enum="RenderingDevice.BarrierMask" is_bitfield="true" default="15" /> <description> Finishes a list of raster drawing commands created with the [code]draw_*[/code] methods. </description> @@ -682,7 +682,7 @@ <param index="3" name="mipmap_count" type="int" /> <param index="4" name="base_layer" type="int" /> <param index="5" name="layer_count" type="int" /> - <param index="6" name="post_barrier" type="int" enum="RenderingDevice.BarrierMask" is_bitfield="true" default="7" /> + <param index="6" name="post_barrier" type="int" enum="RenderingDevice.BarrierMask" is_bitfield="true" default="15" /> <description> Clears the specified [param texture] by replacing all of its pixels with the specified [param color]. [param base_mipmap] and [param mipmap_count] determine which mipmaps of the texture are affected by this clear operation, while [param base_layer] and [param layer_count] determine which layers of a 3D texture (or texture array) are affected by this clear operation. For 2D textures (which only have one layer by design), [param base_layer] and [param layer_count] must both be [code]0[/code]. [b]Note:[/b] [param texture] can't be cleared while a draw list that uses it as part of a framebuffer is being created. Ensure the draw list is finalized (and that the color/depth texture using it is not set to [constant FINAL_ACTION_CONTINUE]) to clear this texture. @@ -699,7 +699,7 @@ <param index="6" name="dst_mipmap" type="int" /> <param index="7" name="src_layer" type="int" /> <param index="8" name="dst_layer" type="int" /> - <param index="9" name="post_barrier" type="int" enum="RenderingDevice.BarrierMask" is_bitfield="true" default="7" /> + <param index="9" name="post_barrier" type="int" enum="RenderingDevice.BarrierMask" is_bitfield="true" default="15" /> <description> Copies the [param from_texture] to [param to_texture] with the specified [param from_pos], [param to_pos] and [param size] coordinates. The Z axis of the [param from_pos], [param to_pos] and [param size] must be [code]0[/code] for 2-dimensional textures. Source and destination mipmaps/layers must also be specified, with these parameters being [code]0[/code] for textures without mipmaps or single-layer textures. Returns [constant @GlobalScope.OK] if the texture copy was successful or [constant @GlobalScope.ERR_INVALID_PARAMETER] otherwise. [b]Note:[/b] [param from_texture] texture can't be copied while a draw list that uses it as part of a framebuffer is being created. Ensure the draw list is finalized (and that the color/depth texture using it is not set to [constant FINAL_ACTION_CONTINUE]) to copy this texture. @@ -752,6 +752,13 @@ [b]Note:[/b] [param texture] requires the [constant TEXTURE_USAGE_CAN_COPY_FROM_BIT] to be retrieved. Otherwise, an error is printed and a empty [PackedByteArray] is returned. </description> </method> + <method name="texture_get_format"> + <return type="RDTextureFormat" /> + <param index="0" name="texture" type="RID" /> + <description> + Returns the data format used to create this texture. + </description> + </method> <method name="texture_get_native_handle"> <return type="int" /> <param index="0" name="texture" type="RID" /> @@ -786,7 +793,7 @@ <return type="int" enum="Error" /> <param index="0" name="from_texture" type="RID" /> <param index="1" name="to_texture" type="RID" /> - <param index="2" name="post_barrier" type="int" enum="RenderingDevice.BarrierMask" is_bitfield="true" default="7" /> + <param index="2" name="post_barrier" type="int" enum="RenderingDevice.BarrierMask" is_bitfield="true" default="15" /> <description> Resolves the [param from_texture] texture onto [param to_texture] with multisample antialiasing enabled. This must be used when rendering a framebuffer for MSAA to work. Returns [constant @GlobalScope.OK] if successful, [constant @GlobalScope.ERR_INVALID_PARAMETER] otherwise. [b]Note:[/b] [param from_texture] and [param to_texture] textures must have the same dimension, format and type (color or depth). @@ -803,7 +810,7 @@ <param index="0" name="texture" type="RID" /> <param index="1" name="layer" type="int" /> <param index="2" name="data" type="PackedByteArray" /> - <param index="3" name="post_barrier" type="int" enum="RenderingDevice.BarrierMask" is_bitfield="true" default="7" /> + <param index="3" name="post_barrier" type="int" enum="RenderingDevice.BarrierMask" is_bitfield="true" default="15" /> <description> Updates texture data with new data, replacing the previous data in place. The updated texture data must have the same dimensions and format. For 2D textures (which only have one layer), [param layer] must be [code]0[/code]. Returns [constant @GlobalScope.OK] if the update was successful, [constant @GlobalScope.ERR_INVALID_PARAMETER] otherwise. [b]Note:[/b] Updating textures is forbidden during creation of a draw or compute list. @@ -1580,19 +1587,25 @@ <constant name="DATA_FORMAT_MAX" value="218" enum="DataFormat"> Represents the size of the [enum DataFormat] enum. </constant> - <constant name="BARRIER_MASK_RASTER" value="1" enum="BarrierMask" is_bitfield="true"> - Raster barrier mask. + <constant name="BARRIER_MASK_VERTEX" value="1" enum="BarrierMask" is_bitfield="true"> + Vertex shader barrier mask. + </constant> + <constant name="BARRIER_MASK_FRAGMENT" value="2" enum="BarrierMask" is_bitfield="true"> + Fragment shader barrier mask. </constant> - <constant name="BARRIER_MASK_COMPUTE" value="2" enum="BarrierMask" is_bitfield="true"> + <constant name="BARRIER_MASK_COMPUTE" value="4" enum="BarrierMask" is_bitfield="true"> Compute barrier mask. </constant> - <constant name="BARRIER_MASK_TRANSFER" value="4" enum="BarrierMask" is_bitfield="true"> + <constant name="BARRIER_MASK_TRANSFER" value="8" enum="BarrierMask" is_bitfield="true"> Transfer barrier mask. </constant> - <constant name="BARRIER_MASK_ALL_BARRIERS" value="7" enum="BarrierMask" is_bitfield="true"> + <constant name="BARRIER_MASK_RASTER" value="3" enum="BarrierMask" is_bitfield="true"> + Raster barrier mask (vertex and fragment). Equivalent to [code]BARRIER_MASK_VERTEX | BARRIER_MASK_FRAGMENT[/code]. + </constant> + <constant name="BARRIER_MASK_ALL_BARRIERS" value="15" enum="BarrierMask" is_bitfield="true"> Barrier mask for all types (raster, compute, transfer). Equivalent to [code]BARRIER_MASK_RASTER | BARRIER_MASK_COMPUTE | BARRIER_MASK_TRANSFER[/code]. </constant> - <constant name="BARRIER_MASK_NO_BARRIER" value="8" enum="BarrierMask" is_bitfield="true"> + <constant name="BARRIER_MASK_NO_BARRIER" value="16" enum="BarrierMask" is_bitfield="true"> No barrier for any type. </constant> <constant name="TEXTURE_TYPE_1D" value="0" enum="TextureType"> diff --git a/doc/classes/RenderingServer.xml b/doc/classes/RenderingServer.xml index 868079a516..64d54b3ba0 100644 --- a/doc/classes/RenderingServer.xml +++ b/doc/classes/RenderingServer.xml @@ -3281,6 +3281,13 @@ [b]Note:[/b] The [param texture] must have the same width, height, depth and format as the current texture data. Otherwise, an error will be printed and the original texture won't be modified. If you need to use different width, height, depth or format, use [method texture_replace] instead. </description> </method> + <method name="texture_get_format" qualifiers="const"> + <return type="int" enum="Image.Format" /> + <param index="0" name="texture" type="RID" /> + <description> + Returns the [enum Image.Format] for the texture. + </description> + </method> <method name="texture_get_native_handle" qualifiers="const"> <return type="int" /> <param index="0" name="texture" type="RID" /> @@ -3319,6 +3326,14 @@ [i]Deprecated.[/i] ProxyTexture was removed in Godot 4, so this method cannot be used anymore. </description> </method> + <method name="texture_rd_create"> + <return type="RID" /> + <param index="0" name="rd_texture" type="RID" /> + <param index="1" name="layer_type" type="int" enum="RenderingServer.TextureLayeredType" default="0" /> + <description> + Creates a new texture object based on a texture created directly on the [RenderingDevice]. If the texture contains layers, [param layer_type] is used to define the layer type. + </description> + </method> <method name="texture_replace"> <return type="void" /> <param index="0" name="texture" type="RID" /> diff --git a/doc/classes/Resource.xml b/doc/classes/Resource.xml index 83a878bada..2a2239f660 100644 --- a/doc/classes/Resource.xml +++ b/doc/classes/Resource.xml @@ -32,7 +32,7 @@ <method name="emit_changed"> <return type="void" /> <description> - Emits the [signal changed] signal. This method is called automatically for built-in resources. + Emits the [signal changed] signal. This method is called automatically for some built-in resources. [b]Note:[/b] For custom resources, it's recommended to call this method whenever a meaningful change occurs, such as a modified property. This ensures that custom [Object]s depending on the resource are properly updated. [codeblock] var damage: diff --git a/doc/classes/RichTextLabel.xml b/doc/classes/RichTextLabel.xml index 388f4dfc18..e2030f5927 100644 --- a/doc/classes/RichTextLabel.xml +++ b/doc/classes/RichTextLabel.xml @@ -266,6 +266,18 @@ Terminates the current tag. Use after [code]push_*[/code] methods to close BBCodes manually. Does not need to follow [code]add_*[/code] methods. </description> </method> + <method name="pop_all"> + <return type="void" /> + <description> + Terminates all tags opened by [code]push_*[/code] methods. + </description> + </method> + <method name="pop_context"> + <return type="void" /> + <description> + Terminates tags opened after the last [method push_context] call (including context marker), or all tags if there's no context marker on the stack. + </description> + </method> <method name="push_bgcolor"> <return type="void" /> <param index="0" name="bgcolor" type="Color" /> @@ -298,6 +310,12 @@ Adds a [code][color][/code] tag to the tag stack. </description> </method> + <method name="push_context"> + <return type="void" /> + <description> + Adds a context marker to the tag stack. See [method pop_context]. + </description> + </method> <method name="push_customfx"> <return type="void" /> <param index="0" name="effect" type="RichTextEffect" /> diff --git a/doc/classes/SceneTree.xml b/doc/classes/SceneTree.xml index 70c06eac09..c0d98ef921 100644 --- a/doc/classes/SceneTree.xml +++ b/doc/classes/SceneTree.xml @@ -42,7 +42,7 @@ <description> Changes the running scene to the one at the given [param path], after loading it into a [PackedScene] and creating a new instance. Returns [constant OK] on success, [constant ERR_CANT_OPEN] if the [param path] cannot be loaded into a [PackedScene], or [constant ERR_CANT_CREATE] if that scene cannot be instantiated. - [b]Note:[/b] The scene change is deferred, which means that the new scene node is added to the tree at the end of the frame. This ensures that both scenes aren't running at the same time, while still freeing the previous scene in a safe way similar to [method Node.queue_free]. As such, you won't be able to access the loaded scene immediately after the [method change_scene_to_file] call. + [b]Note:[/b] The new scene node is added to the tree at the end of the frame. This ensures that both scenes aren't running at the same time, while still freeing the previous scene in a safe way similar to [method Node.queue_free]. As such, you won't be able to access the loaded scene immediately after the [method change_scene_to_file] call. </description> </method> <method name="change_scene_to_packed"> @@ -51,7 +51,7 @@ <description> Changes the running scene to a new instance of the given [PackedScene] (which must be valid). Returns [constant OK] on success, [constant ERR_CANT_CREATE] if the scene cannot be instantiated, or [constant ERR_INVALID_PARAMETER] if the scene is invalid. - [b]Note:[/b] The scene change is deferred, which means that the new scene node is added to the tree at the end of the frame. You won't be able to access it immediately after the [method change_scene_to_packed] call. + [b]Note:[/b] The new scene node is added to the tree at the end of the frame. You won't be able to access it immediately after the [method change_scene_to_packed] call. </description> </method> <method name="create_timer"> diff --git a/doc/classes/SeparationRayShape3D.xml b/doc/classes/SeparationRayShape3D.xml index 7d652c2527..aaf8791224 100644 --- a/doc/classes/SeparationRayShape3D.xml +++ b/doc/classes/SeparationRayShape3D.xml @@ -4,7 +4,7 @@ A 3D ray shape used for physics collision that tries to separate itself from any collider. </brief_description> <description> - A 3D ray shape, intended for use in physics. Usually used to provide a shape for a [CollisionShape2D]. When a [SeparationRayShape3D] collides with an object, it tries to separate itself from it by moving its endpoint to the collision point. It can for example be used for spears falling from the sky. + A 3D ray shape, intended for use in physics. Usually used to provide a shape for a [CollisionShape3D]. When a [SeparationRayShape3D] collides with an object, it tries to separate itself from it by moving its endpoint to the collision point. It can for example be used for spears falling from the sky. </description> <tutorials> </tutorials> diff --git a/doc/classes/ShaderMaterial.xml b/doc/classes/ShaderMaterial.xml index 888964e7f0..e2d0696a4b 100644 --- a/doc/classes/ShaderMaterial.xml +++ b/doc/classes/ShaderMaterial.xml @@ -5,6 +5,7 @@ </brief_description> <description> A material that uses a custom [Shader] program to render either items to screen or process particles. You can create multiple materials for the same shader but configure different values for the uniforms defined in the shader. + [b]Note:[/b] For performance reasons the [signal Resource.changed] signal is only emitted when the [member Resource.resource_name] is changed. Only in editor, is also emitted for [member shader] changes. </description> <tutorials> <link title="Shaders documentation index">$DOCS_URL/tutorials/shaders/index.html</link> diff --git a/doc/classes/ShapeCast3D.xml b/doc/classes/ShapeCast3D.xml index 4bbf763a6e..ce72944098 100644 --- a/doc/classes/ShapeCast3D.xml +++ b/doc/classes/ShapeCast3D.xml @@ -119,11 +119,11 @@ Removes a collision exception so the shape does report collisions with the specified [RID]. </description> </method> - <method name="resource_changed"> + <method name="resource_changed" is_deprecated="true"> <return type="void" /> <param index="0" name="resource" type="Resource" /> <description> - This method is used internally to update the debug gizmo in the editor. Any code placed in this function will be called whenever the [member shape] resource is modified. + [i]Obsoleted.[/i] Use [signal Resource.changed] instead. </description> </method> <method name="set_collision_mask_value"> diff --git a/doc/classes/SliderJoint3D.xml b/doc/classes/SliderJoint3D.xml index bdab8d7d78..49b362041b 100644 --- a/doc/classes/SliderJoint3D.xml +++ b/doc/classes/SliderJoint3D.xml @@ -105,7 +105,7 @@ A factor applied to the movement across the slider axis once the limits get surpassed. The lower, the slower the movement. </constant> <constant name="PARAM_LINEAR_LIMIT_RESTITUTION" value="3" enum="Param"> - The amount of restitution once the limits are surpassed. The lower, the more velocityenergy gets lost. + The amount of restitution once the limits are surpassed. The lower, the more velocity-energy gets lost. </constant> <constant name="PARAM_LINEAR_LIMIT_DAMPING" value="4" enum="Param"> The amount of damping once the slider limits are surpassed. diff --git a/doc/classes/SubViewportContainer.xml b/doc/classes/SubViewportContainer.xml index 2f666f5089..08e7ca23f7 100644 --- a/doc/classes/SubViewportContainer.xml +++ b/doc/classes/SubViewportContainer.xml @@ -11,6 +11,7 @@ <tutorials> </tutorials> <members> + <member name="focus_mode" type="int" setter="set_focus_mode" getter="get_focus_mode" overrides="Control" enum="Control.FocusMode" default="1" /> <member name="stretch" type="bool" setter="set_stretch" getter="is_stretch_enabled" default="false"> If [code]true[/code], the sub-viewport will be automatically resized to the control's size. [b]Note:[/b] If [code]true[/code], this will prohibit changing [member SubViewport.size] of its children manually. diff --git a/doc/classes/TextEdit.xml b/doc/classes/TextEdit.xml index 89ce894203..e611b7e3fa 100644 --- a/doc/classes/TextEdit.xml +++ b/doc/classes/TextEdit.xml @@ -1234,7 +1234,7 @@ <param index="1" name="to_line" type="int" /> <description> Emitted immediately when the text changes. - When text is added [param from_line] will be less then [param to_line]. On a remove [param to_line] will be less then [param from_line]. + When text is added [param from_line] will be less than [param to_line]. On a remove [param to_line] will be less than [param from_line]. </description> </signal> <signal name="text_changed"> diff --git a/doc/classes/Texture2DArrayRD.xml b/doc/classes/Texture2DArrayRD.xml new file mode 100644 index 0000000000..b28cdd436c --- /dev/null +++ b/doc/classes/Texture2DArrayRD.xml @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="Texture2DArrayRD" inherits="TextureLayeredRD" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> + <brief_description> + Texture Array for 2D that is bound to a texture created on the [RenderingDevice]. + </brief_description> + <description> + This texture array class allows you to use a 2D array texture created directly on the [RenderingDevice] as a texture for materials, meshes, etc. + </description> + <tutorials> + </tutorials> +</class> diff --git a/doc/classes/Texture2DRD.xml b/doc/classes/Texture2DRD.xml new file mode 100644 index 0000000000..b935a7763b --- /dev/null +++ b/doc/classes/Texture2DRD.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="Texture2DRD" inherits="Texture2D" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> + <brief_description> + Texture for 2D that is bound to a texture created on the [RenderingDevice]. + </brief_description> + <description> + This texture class allows you to use a 2D texture created directly on the [RenderingDevice] as a texture for materials, meshes, etc. + </description> + <tutorials> + </tutorials> + <members> + <member name="resource_local_to_scene" type="bool" setter="set_local_to_scene" getter="is_local_to_scene" overrides="Resource" default="false" /> + <member name="texture_rd_rid" type="RID" setter="set_texture_rd_rid" getter="get_texture_rd_rid" default="RID()"> + The RID of the texture object created on the [RenderingDevice]. + </member> + </members> +</class> diff --git a/doc/classes/Texture3DRD.xml b/doc/classes/Texture3DRD.xml new file mode 100644 index 0000000000..f9d72b7a0f --- /dev/null +++ b/doc/classes/Texture3DRD.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="Texture3DRD" inherits="Texture3D" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> + <brief_description> + Texture for 3D that is bound to a texture created on the [RenderingDevice]. + </brief_description> + <description> + This texture class allows you to use a 3D texture created directly on the [RenderingDevice] as a texture for materials, meshes, etc. + </description> + <tutorials> + </tutorials> + <members> + <member name="texture_rd_rid" type="RID" setter="set_texture_rd_rid" getter="get_texture_rd_rid" default="RID()"> + The RID of the texture object created on the [RenderingDevice]. + </member> + </members> +</class> diff --git a/doc/classes/TextureCubemapArrayRD.xml b/doc/classes/TextureCubemapArrayRD.xml new file mode 100644 index 0000000000..38d5010ee7 --- /dev/null +++ b/doc/classes/TextureCubemapArrayRD.xml @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="TextureCubemapArrayRD" inherits="TextureLayeredRD" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> + <brief_description> + Texture Array for Cubemaps that is bound to a texture created on the [RenderingDevice]. + </brief_description> + <description> + This texture class allows you to use a cubemap array texture created directly on the [RenderingDevice] as a texture for materials, meshes, etc. + </description> + <tutorials> + </tutorials> +</class> diff --git a/doc/classes/TextureCubemapRD.xml b/doc/classes/TextureCubemapRD.xml new file mode 100644 index 0000000000..bd7c897ad3 --- /dev/null +++ b/doc/classes/TextureCubemapRD.xml @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="TextureCubemapRD" inherits="TextureLayeredRD" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> + <brief_description> + Texture for Cubemap that is bound to a texture created on the [RenderingDevice]. + </brief_description> + <description> + This texture class allows you to use a cubemap texture created directly on the [RenderingDevice] as a texture for materials, meshes, etc. + </description> + <tutorials> + </tutorials> +</class> diff --git a/doc/classes/TextureLayeredRD.xml b/doc/classes/TextureLayeredRD.xml new file mode 100644 index 0000000000..65f2d87624 --- /dev/null +++ b/doc/classes/TextureLayeredRD.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="TextureLayeredRD" inherits="TextureLayered" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> + <brief_description> + Abstract base class for layered texture RD types. + </brief_description> + <description> + Base class for [Texture2DArrayRD], [TextureCubemapRD] and [TextureCubemapArrayRD]. Cannot be used directly, but contains all the functions necessary for accessing the derived resource types. + </description> + <tutorials> + </tutorials> + <members> + <member name="texture_rd_rid" type="RID" setter="set_texture_rd_rid" getter="get_texture_rd_rid" default="RID()"> + The RID of the texture object created on the [RenderingDevice]. + </member> + </members> +</class> diff --git a/doc/classes/Thread.xml b/doc/classes/Thread.xml index 068f296c98..dbf63c0852 100644 --- a/doc/classes/Thread.xml +++ b/doc/classes/Thread.xml @@ -5,7 +5,6 @@ </brief_description> <description> A unit of execution in a process. Can run methods on [Object]s simultaneously. The use of synchronization via [Mutex] or [Semaphore] is advised if working with shared objects. - [b]Note:[/b] Breakpoints won't break on code if it's running in a thread. This is a current limitation of the GDScript debugger. [b]Warning:[/b] To ensure proper cleanup without crashes or deadlocks, when a [Thread]'s reference count reaches zero and it is therefore destroyed, the following conditions must be met: - It must not have any [Mutex] objects locked. diff --git a/doc/classes/TileMap.xml b/doc/classes/TileMap.xml index 134022866c..4ed831c213 100644 --- a/doc/classes/TileMap.xml +++ b/doc/classes/TileMap.xml @@ -55,6 +55,7 @@ <param index="0" name="layer" type="int" /> <description> Clears all cells on the given layer. + If [param layer] is negative, the layers are accessed from the last one. </description> </method> <method name="erase_cell"> @@ -63,6 +64,7 @@ <param index="1" name="coords" type="Vector2i" /> <description> Erases the cell on layer [param layer] at coordinates [param coords]. + If [param layer] is negative, the layers are accessed from the last one. </description> </method> <method name="fix_invalid_tiles"> @@ -75,7 +77,7 @@ <return type="void" /> <param index="0" name="layer" type="int" default="-1" /> <description> - Triggers an update of the TileMap. If [param layer] is provided, only updates the given layer. + Triggers an update of the TileMap. If [param layer] is provided and is positive, only updates the given layer. [b]Note:[/b] The TileMap node updates automatically when one of its properties is modified. A manual update is only needed if runtime modifications (implemented in [method _tile_data_runtime_update]) need to be applied. [b]Warning:[/b] Updating the TileMap is computationally expensive and may impact performance. Try to limit the number of updates and the tiles they impact (by placing frequently updated tiles in a dedicated layer for example). </description> @@ -87,6 +89,7 @@ <param index="2" name="use_proxies" type="bool" default="false" /> <description> Returns the tile alternative ID of the cell on layer [param layer] at [param coords]. If [param use_proxies] is [code]false[/code], ignores the [TileSet]'s tile proxies, returning the raw alternative identifier. See [method TileSet.map_tile_proxy]. + If [param layer] is negative, the layers are accessed from the last one. </description> </method> <method name="get_cell_atlas_coords" qualifiers="const"> @@ -96,6 +99,7 @@ <param index="2" name="use_proxies" type="bool" default="false" /> <description> Returns the tile atlas coordinates ID of the cell on layer [param layer] at coordinates [param coords]. If [param use_proxies] is [code]false[/code], ignores the [TileSet]'s tile proxies, returning the raw alternative identifier. See [method TileSet.map_tile_proxy]. + If [param layer] is negative, the layers are accessed from the last one. </description> </method> <method name="get_cell_source_id" qualifiers="const"> @@ -106,6 +110,7 @@ <description> Returns the tile source ID of the cell on layer [param layer] at coordinates [param coords]. Returns [code]-1[/code] if the cell does not exist. If [param use_proxies] is [code]false[/code], ignores the [TileSet]'s tile proxies, returning the raw alternative identifier. See [method TileSet.map_tile_proxy]. + If [param layer] is negative, the layers are accessed from the last one. </description> </method> <method name="get_cell_tile_data" qualifiers="const"> @@ -115,6 +120,7 @@ <param index="2" name="use_proxies" type="bool" default="false" /> <description> Returns the [TileData] object associated with the given cell, or [code]null[/code] if the cell does not exist or is not a [TileSetAtlasSource]. + If [param layer] is negative, the layers are accessed from the last one. If [param use_proxies] is [code]false[/code], ignores the [TileSet]'s tile proxies, returning the raw alternative identifier. See [method TileSet.map_tile_proxy]. [codeblock] func get_clicked_tile_power(): @@ -146,6 +152,7 @@ <param index="0" name="layer" type="int" /> <description> Returns a TileMap layer's modulate. + If [param layer] is negative, the layers are accessed from the last one. </description> </method> <method name="get_layer_name" qualifiers="const"> @@ -153,6 +160,17 @@ <param index="0" name="layer" type="int" /> <description> Returns a TileMap layer's name. + If [param layer] is negative, the layers are accessed from the last one. + </description> + </method> + <method name="get_layer_navigation_map" qualifiers="const"> + <return type="RID" /> + <param index="0" name="layer" type="int" /> + <description> + Returns the [NavigationServer2D] navigation map [RID] currently assigned to the specified TileMap [param layer]. + By default the TileMap uses the default [World2D] navigation map for the first TileMap layer. For each additional TileMap layer a new navigation map is created for the additional layer. + In order to make [NavigationAgent2D] switch between TileMap layer navigation maps use [method NavigationAgent2D.set_navigation_map] with the navigation map received from [method get_layer_navigation_map]. + If [param layer] is negative, the layers are accessed from the last one. </description> </method> <method name="get_layer_y_sort_origin" qualifiers="const"> @@ -160,6 +178,7 @@ <param index="0" name="layer" type="int" /> <description> Returns a TileMap layer's Y sort origin. + If [param layer] is negative, the layers are accessed from the last one. </description> </method> <method name="get_layer_z_index" qualifiers="const"> @@ -167,6 +186,7 @@ <param index="0" name="layer" type="int" /> <description> Returns a TileMap layer's Z-index value. + If [param layer] is negative, the layers are accessed from the last one. </description> </method> <method name="get_layers_count" qualifiers="const"> @@ -175,13 +195,11 @@ Returns the number of layers in the TileMap. </description> </method> - <method name="get_navigation_map" qualifiers="const"> + <method name="get_navigation_map" qualifiers="const" is_deprecated="true"> <return type="RID" /> <param index="0" name="layer" type="int" /> <description> - Returns the [NavigationServer2D] navigation map [RID] currently assigned to the specified TileMap [param layer]. - By default the TileMap uses the default [World2D] navigation map for the first TileMap layer. For each additional TileMap layer a new navigation map is created for the additional layer. - In order to make [NavigationAgent2D] switch between TileMap layer navigation maps use [method NavigationAgent2D.set_navigation_map] with the navigation map received from [method get_navigation_map]. + See [method get_layer_navigation_map]. </description> </method> <method name="get_neighbor_cell" qualifiers="const"> @@ -198,6 +216,7 @@ <param index="1" name="coords_array" type="Vector2i[]" /> <description> Creates a new [TileMapPattern] from the given layer and set of cells. + If [param layer] is negative, the layers are accessed from the last one. </description> </method> <method name="get_surrounding_cells"> @@ -212,6 +231,7 @@ <param index="0" name="layer" type="int" /> <description> Returns a [Vector2i] 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. + If [param layer] is negative, the layers are accessed from the last one. </description> </method> <method name="get_used_cells_by_id" qualifiers="const"> @@ -224,9 +244,10 @@ Returns a [Vector2i] array with the positions of all cells containing a tile in the given layer. Tiles may be filtered according to their source ([param source_id]), their atlas coordinates ([param atlas_coords]) or alternative id ([param alternative_tile]). If a parameter has it's value set to the default one, this parameter is not used to filter a cell. Thus, if all parameters have their respective default value, this method returns the same result as [method get_used_cells]. 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. + If [param layer] is negative, the layers are accessed from the last one. </description> </method> - <method name="get_used_rect"> + <method name="get_used_rect" qualifiers="const"> <return type="Rect2i" /> <description> Returns a rectangle enclosing the used (non-empty) tiles of the map, including all layers. @@ -237,6 +258,7 @@ <param index="0" name="layer" type="int" /> <description> Returns if a layer is enabled. + If [param layer] is negative, the layers are accessed from the last one. </description> </method> <method name="is_layer_y_sort_enabled" qualifiers="const"> @@ -244,6 +266,7 @@ <param index="0" name="layer" type="int" /> <description> Returns if a layer Y-sorts its tiles. + If [param layer] is negative, the layers are accessed from the last one. </description> </method> <method name="local_to_map" qualifiers="const"> @@ -298,6 +321,7 @@ - The atlas coordinates identifier [param atlas_coords] identifies a tile coordinates in the atlas (if the source is a [TileSetAtlasSource]). For [TileSetScenesCollectionSource] it should always be [code]Vector2i(0, 0)[/code]), - The alternative tile identifier [param alternative_tile] identifies a tile alternative in the atlas (if the source is a [TileSetAtlasSource]), and the scene for a [TileSetScenesCollectionSource]. If [param source_id] is set to [code]-1[/code], [param atlas_coords] to [code]Vector2i(-1, -1)[/code] or [param alternative_tile] to [code]-1[/code], the cell will be erased. An erased cell gets [b]all[/b] its identifiers automatically set to their respective invalid values, namely [code]-1[/code], [code]Vector2i(-1, -1)[/code] and [code]-1[/code]. + If [param layer] is negative, the layers are accessed from the last one. </description> </method> <method name="set_cells_terrain_connect"> @@ -310,6 +334,7 @@ <description> Update all the cells in the [param cells] coordinates array so that they use the given [param terrain] for the given [param terrain_set]. If an updated cell has the same terrain as one of its neighboring cells, this function tries to join the two. This function might update neighboring tiles if needed to create correct terrain transitions. If [param ignore_empty_terrains] is true, empty terrains will be ignored when trying to find the best fitting tile for the given terrain constraints. + If [param layer] is negative, the layers are accessed from the last one. [b]Note:[/b] To work correctly, this method requires the TileMap's TileSet to have terrains set up with all required terrain combinations. Otherwise, it may produce unexpected results. </description> </method> @@ -323,6 +348,7 @@ <description> Update all the cells in the [param path] coordinates array so that they use the given [param terrain] for the given [param terrain_set]. The function will also connect two successive cell in the path with the same terrain. This function might update neighboring tiles if needed to create correct terrain transitions. If [param ignore_empty_terrains] is true, empty terrains will be ignored when trying to find the best fitting tile for the given terrain constraints. + If [param layer] is negative, the layers are accessed from the last one. [b]Note:[/b] To work correctly, this method requires the TileMap's TileSet to have terrains set up with all required terrain combinations. Otherwise, it may produce unexpected results. </description> </method> @@ -353,6 +379,17 @@ If [param layer] is negative, the layers are accessed from the last one. </description> </method> + <method name="set_layer_navigation_map"> + <return type="void" /> + <param index="0" name="layer" type="int" /> + <param index="1" name="map" type="RID" /> + <description> + Assigns a [NavigationServer2D] navigation map [RID] to the specified TileMap [param layer]. + By default the TileMap uses the default [World2D] navigation map for the first TileMap layer. For each additional TileMap layer a new navigation map is created for the additional layer. + In order to make [NavigationAgent2D] switch between TileMap layer navigation maps use [method NavigationAgent2D.set_navigation_map] with the navigation map received from [method get_layer_navigation_map]. + If [param layer] is negative, the layers are accessed from the last one. + </description> + </method> <method name="set_layer_y_sort_enabled"> <return type="void" /> <param index="0" name="layer" type="int" /> @@ -382,14 +419,12 @@ If [param layer] is negative, the layers are accessed from the last one. </description> </method> - <method name="set_navigation_map"> + <method name="set_navigation_map" is_deprecated="true"> <return type="void" /> <param index="0" name="layer" type="int" /> <param index="1" name="map" type="RID" /> <description> - Assigns a [NavigationServer2D] navigation map [RID] to the specified TileMap [param layer]. - By default the TileMap uses the default [World2D] navigation map for the first TileMap layer. For each additional TileMap layer a new navigation map is created for the additional layer. - In order to make [NavigationAgent2D] switch between TileMap layer navigation maps use [method NavigationAgent2D.set_navigation_map] with the navigation map received from [method get_navigation_map]. + See [method set_layer_navigation_map]. </description> </method> <method name="set_pattern"> @@ -399,6 +434,7 @@ <param index="2" name="pattern" type="TileMapPattern" /> <description> Paste the given [TileMapPattern] at the given [param position] and [param layer] in the tile map. + If [param layer] is negative, the layers are accessed from the last one. </description> </method> </methods> diff --git a/doc/classes/Tween.xml b/doc/classes/Tween.xml index fd8ef507fb..f104c5f107 100644 --- a/doc/classes/Tween.xml +++ b/doc/classes/Tween.xml @@ -206,6 +206,7 @@ <return type="void" /> <description> Pauses the tweening. The animation can be resumed by using [method play]. + [b]Note:[/b] If a Tween is paused and not bound to any node, it will exist indefinitely until manually started or invalidated. If you lose a reference to such Tween, you can retrieve it using [method SceneTree.get_processed_tweens]. </description> </method> <method name="play"> @@ -273,6 +274,7 @@ <return type="void" /> <description> Stops the tweening and resets the [Tween] to its initial state. This will not remove any appended [Tweener]s. + [b]Note:[/b] If a Tween is stopped and not bound to any node, it will exist indefinitely until manually started or invalidated. If you lose a reference to such Tween, you can retrieve it using [method SceneTree.get_processed_tweens]. </description> </method> <method name="tween_callback"> diff --git a/doc/classes/Viewport.xml b/doc/classes/Viewport.xml index c0c53146fe..9a5e7ed6f6 100644 --- a/doc/classes/Viewport.xml +++ b/doc/classes/Viewport.xml @@ -160,8 +160,8 @@ - [method Node._input] - [method Control._gui_input] for [Control] nodes - [method Node._shortcut_input] - - [method Node._unhandled_input] - [method Node._unhandled_key_input] + - [method Node._unhandled_input] If an earlier method marks the input as handled via [method set_input_as_handled], any later method in this list will not be called. If none of the methods handle the event and [member physics_object_picking] is [code]true[/code], the event is used for physics object picking. </description> @@ -183,8 +183,8 @@ While this method serves a similar purpose as [method Input.parse_input_event], it does not remap the specified [param event] based on project settings like [member ProjectSettings.input_devices/pointing/emulate_touch_from_mouse]. Calling this method will propagate calls to child nodes for following methods in the given order: - [method Node._shortcut_input] - - [method Node._unhandled_input] - [method Node._unhandled_key_input] + - [method Node._unhandled_input] If an earlier method marks the input as handled via [method set_input_as_handled], any later method in this list will not be called. If none of the methods handle the event and [member physics_object_picking] is [code]true[/code], the event is used for physics object picking. [b]Note:[/b] This method doesn't propagate input events to embedded [Window]s or [SubViewport]s. diff --git a/doc/classes/VisualShaderNode.xml b/doc/classes/VisualShaderNode.xml index 6b188a607d..5d147bd542 100644 --- a/doc/classes/VisualShaderNode.xml +++ b/doc/classes/VisualShaderNode.xml @@ -16,6 +16,13 @@ Clears the default input ports value. </description> </method> + <method name="get_default_input_port" qualifiers="const"> + <return type="int" /> + <param index="0" name="type" type="int" enum="VisualShaderNode.PortType" /> + <description> + Returns the input port which should be connected by default when this node is created as a result of dragging a connection from an existing node to the empty space on the graph. + </description> + </method> <method name="get_default_input_values" qualifiers="const"> <return type="Array" /> <description> diff --git a/doc/classes/VisualShaderNodeCustom.xml b/doc/classes/VisualShaderNodeCustom.xml index 480f7dfe0e..8a90d5dd0f 100644 --- a/doc/classes/VisualShaderNodeCustom.xml +++ b/doc/classes/VisualShaderNodeCustom.xml @@ -37,6 +37,14 @@ Defining this method is [b]required[/b]. </description> </method> + <method name="_get_default_input_port" qualifiers="virtual const"> + <return type="int" /> + <param index="0" name="type" type="int" enum="VisualShaderNode.PortType" /> + <description> + Override this method to define the input port which should be connected by default when this node is created as a result of dragging a connection from an existing node to the empty space on the graph. + Defining this method is [b]optional[/b]. If not overridden, the connection will be created to the first valid port. + </description> + </method> <method name="_get_description" qualifiers="virtual const"> <return type="String" /> <description> diff --git a/doc/classes/VisualShaderNodeRotationByAxis.xml b/doc/classes/VisualShaderNodeRotationByAxis.xml new file mode 100644 index 0000000000..8a4fbc89d8 --- /dev/null +++ b/doc/classes/VisualShaderNodeRotationByAxis.xml @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="VisualShaderNodeRotationByAxis" inherits="VisualShaderNode" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> + <brief_description> + A visual shader node that modifies the rotation of the object using a rotation matrix. + </brief_description> + <description> + RotationByAxis node will transform the vertices of a mesh with specified axis and angle in radians. It can be used to rotate an object in an arbitrary axis. + </description> + <tutorials> + </tutorials> +</class> diff --git a/doc/classes/VisualShaderNodeScreenNormalWorldSpace.xml b/doc/classes/VisualShaderNodeScreenNormalWorldSpace.xml new file mode 100644 index 0000000000..ccffb62138 --- /dev/null +++ b/doc/classes/VisualShaderNodeScreenNormalWorldSpace.xml @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="VisualShaderNodeScreenNormalWorldSpace" inherits="VisualShaderNode" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> + <brief_description> + A visual shader node that unpacks the screen normal texture in World Space. + </brief_description> + <description> + The ScreenNormalWorldSpace node allows to create outline effects. + </description> + <tutorials> + </tutorials> +</class> diff --git a/doc/classes/VisualShaderNodeWorldPositionFromDepth.xml b/doc/classes/VisualShaderNodeWorldPositionFromDepth.xml new file mode 100644 index 0000000000..9c9016e889 --- /dev/null +++ b/doc/classes/VisualShaderNodeWorldPositionFromDepth.xml @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="VisualShaderNodeWorldPositionFromDepth" inherits="VisualShaderNode" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> + <brief_description> + A visual shader node that calculates the position of the pixel in world space using the depth texture. + </brief_description> + <description> + The WorldPositionFromDepth node reconstructs the depth position of the pixel in world space. This can be used to obtain world space UVs for projection mapping like Caustics. + </description> + <tutorials> + </tutorials> +</class> diff --git a/doc/classes/Window.xml b/doc/classes/Window.xml index 0446f6d73f..4114a83584 100644 --- a/doc/classes/Window.xml +++ b/doc/classes/Window.xml @@ -903,5 +903,8 @@ The background style used when the [Window] is embedded. Note that this is drawn only under the window's content, excluding the title. For proper borders and title bar style, you can use [code]expand_margin_*[/code] properties of [StyleBoxFlat]. [b]Note:[/b] The content background will not be visible unless [member transparent] is enabled. </theme_item> + <theme_item name="embedded_unfocused_border" data_type="style" type="StyleBox"> + The background style used when the [Window] is embedded and unfocused. + </theme_item> </theme_items> </class> diff --git a/doc/tools/doc_status.py b/doc/tools/doc_status.py index 376addcff0..717a468b36 100755 --- a/doc/tools/doc_status.py +++ b/doc/tools/doc_status.py @@ -383,12 +383,6 @@ for file in input_file_list: tree = ET.parse(file) doc = tree.getroot() - if "version" not in doc.attrib: - print('Version missing from "doc"') - sys.exit(255) - - version = doc.attrib["version"] - if doc.attrib["name"] in class_names: continue class_names.append(doc.attrib["name"]) diff --git a/doc/tools/make_rst.py b/doc/tools/make_rst.py index 28d30a594d..54bad7cf05 100755 --- a/doc/tools/make_rst.py +++ b/doc/tools/make_rst.py @@ -66,6 +66,15 @@ BASE_STRINGS = [ "This method doesn't need an instance to be called, so it can be called directly using the class name.", "This method describes a valid operator to use with this type as left-hand operand.", "This value is an integer composed as a bitmask of the following flags.", + "There is currently no description for this class. Please help us by :ref:`contributing one <doc_updating_the_class_reference>`!", + "There is currently no description for this signal. Please help us by :ref:`contributing one <doc_updating_the_class_reference>`!", + "There is currently no description for this annotation. Please help us by :ref:`contributing one <doc_updating_the_class_reference>`!", + "There is currently no description for this property. Please help us by :ref:`contributing one <doc_updating_the_class_reference>`!", + "There is currently no description for this constructor. Please help us by :ref:`contributing one <doc_updating_the_class_reference>`!", + "There is currently no description for this method. Please help us by :ref:`contributing one <doc_updating_the_class_reference>`!", + "There is currently no description for this operator. Please help us by :ref:`contributing one <doc_updating_the_class_reference>`!", + "There is currently no description for this theme property. Please help us by :ref:`contributing one <doc_updating_the_class_reference>`!", + "There are notable differences when using this API with C#. See :ref:`doc_c_sharp_differences` for more information.", ] strings_l10n: Dict[str, str] = {} @@ -92,6 +101,36 @@ EDITOR_CLASSES: List[str] = [ "ScriptEditor", "ScriptEditorBase", ] +# Sync with the types mentioned in https://docs.godotengine.org/en/stable/tutorials/scripting/c_sharp/c_sharp_differences.html +CLASSES_WITH_CSHARP_DIFFERENCES: List[str] = [ + "@GlobalScope", + "String", + "NodePath", + "Signal", + "Callable", + "RID", + "Basis", + "Transform2D", + "Transform3D", + "Rect2", + "Rect2i", + "AABB", + "Quaternion", + "Projection", + "Color", + "Array", + "Dictionary", + "PackedByteArray", + "PackedColorArray", + "PackedFloat32Array", + "PackedFloat64Array", + "PackedInt32Array", + "PackedInt64Array", + "PackedStringArray", + "PackedVector2Array", + "PackedVector3Array", + "Variant", +] class State: @@ -842,6 +881,15 @@ def make_rst_class(class_def: ClassDef, state: State, dry_run: bool, output_dir: + "\n\n" ) + if class_def.name in CLASSES_WITH_CSHARP_DIFFERENCES: + f.write(".. note::\n\n\t") + f.write( + translate( + "There are notable differences when using this API with C#. See :ref:`doc_c_sharp_differences` for more information." + ) + + "\n\n" + ) + # Online tutorials if len(class_def.tutorials) > 0: f.write(".. rst-class:: classref-introduction-group\n\n") diff --git a/drivers/gles3/rasterizer_scene_gles3.cpp b/drivers/gles3/rasterizer_scene_gles3.cpp index e6e7dd6f17..d8ddfe5c32 100644 --- a/drivers/gles3/rasterizer_scene_gles3.cpp +++ b/drivers/gles3/rasterizer_scene_gles3.cpp @@ -608,18 +608,20 @@ void RasterizerSceneGLES3::_setup_sky(const RenderDataGLES3 *p_render_data, cons material = nullptr; } } + } - if (!material) { - sky_material = sky_globals.default_material; - material = static_cast<GLES3::SkyMaterialData *>(material_storage->material_get_data(sky_material, RS::SHADER_SKY)); - } + if (!material) { + sky_material = sky_globals.default_material; + material = static_cast<GLES3::SkyMaterialData *>(material_storage->material_get_data(sky_material, RS::SHADER_SKY)); + } - ERR_FAIL_COND(!material); + ERR_FAIL_COND(!material); - shader_data = material->shader_data; + shader_data = material->shader_data; - ERR_FAIL_COND(!shader_data); + ERR_FAIL_COND(!shader_data); + if (sky) { if (shader_data->uses_time && time - sky->prev_time > 0.00001) { sky->prev_time = time; sky->reflection_dirty = true; @@ -640,111 +642,113 @@ void RasterizerSceneGLES3::_setup_sky(const RenderDataGLES3 *p_render_data, cons sky->prev_position = p_transform.origin; sky->reflection_dirty = true; } + } - glBindBufferBase(GL_UNIFORM_BUFFER, SKY_DIRECTIONAL_LIGHT_UNIFORM_LOCATION, sky_globals.directional_light_buffer); - if (shader_data->uses_light) { - sky_globals.directional_light_count = 0; - for (int i = 0; i < (int)p_lights.size(); i++) { - GLES3::LightInstance *li = GLES3::LightStorage::get_singleton()->get_light_instance(p_lights[i]); - if (!li) { - continue; - } - RID base = li->light; + glBindBufferBase(GL_UNIFORM_BUFFER, SKY_DIRECTIONAL_LIGHT_UNIFORM_LOCATION, sky_globals.directional_light_buffer); + if (shader_data->uses_light) { + sky_globals.directional_light_count = 0; + for (int i = 0; i < (int)p_lights.size(); i++) { + GLES3::LightInstance *li = GLES3::LightStorage::get_singleton()->get_light_instance(p_lights[i]); + if (!li) { + continue; + } + RID base = li->light; - ERR_CONTINUE(base.is_null()); + ERR_CONTINUE(base.is_null()); - RS::LightType type = light_storage->light_get_type(base); - if (type == RS::LIGHT_DIRECTIONAL && light_storage->light_directional_get_sky_mode(base) != RS::LIGHT_DIRECTIONAL_SKY_MODE_LIGHT_ONLY) { - DirectionalLightData &sky_light_data = sky_globals.directional_lights[sky_globals.directional_light_count]; - Transform3D light_transform = li->transform; - Vector3 world_direction = light_transform.basis.xform(Vector3(0, 0, 1)).normalized(); + RS::LightType type = light_storage->light_get_type(base); + if (type == RS::LIGHT_DIRECTIONAL && light_storage->light_directional_get_sky_mode(base) != RS::LIGHT_DIRECTIONAL_SKY_MODE_LIGHT_ONLY) { + DirectionalLightData &sky_light_data = sky_globals.directional_lights[sky_globals.directional_light_count]; + Transform3D light_transform = li->transform; + Vector3 world_direction = light_transform.basis.xform(Vector3(0, 0, 1)).normalized(); - sky_light_data.direction[0] = world_direction.x; - sky_light_data.direction[1] = world_direction.y; - sky_light_data.direction[2] = world_direction.z; + sky_light_data.direction[0] = world_direction.x; + sky_light_data.direction[1] = world_direction.y; + sky_light_data.direction[2] = world_direction.z; - float sign = light_storage->light_is_negative(base) ? -1 : 1; - sky_light_data.energy = sign * light_storage->light_get_param(base, RS::LIGHT_PARAM_ENERGY); + float sign = light_storage->light_is_negative(base) ? -1 : 1; + sky_light_data.energy = sign * light_storage->light_get_param(base, RS::LIGHT_PARAM_ENERGY); - if (is_using_physical_light_units()) { - sky_light_data.energy *= light_storage->light_get_param(base, RS::LIGHT_PARAM_INTENSITY); - } + if (is_using_physical_light_units()) { + sky_light_data.energy *= light_storage->light_get_param(base, RS::LIGHT_PARAM_INTENSITY); + } - if (p_render_data->camera_attributes.is_valid()) { - sky_light_data.energy *= RSG::camera_attributes->camera_attributes_get_exposure_normalization_factor(p_render_data->camera_attributes); - } + if (p_render_data->camera_attributes.is_valid()) { + sky_light_data.energy *= RSG::camera_attributes->camera_attributes_get_exposure_normalization_factor(p_render_data->camera_attributes); + } - Color linear_col = light_storage->light_get_color(base); - sky_light_data.color[0] = linear_col.r; - sky_light_data.color[1] = linear_col.g; - sky_light_data.color[2] = linear_col.b; + Color linear_col = light_storage->light_get_color(base); + sky_light_data.color[0] = linear_col.r; + sky_light_data.color[1] = linear_col.g; + sky_light_data.color[2] = linear_col.b; - sky_light_data.enabled = true; + sky_light_data.enabled = true; - float angular_diameter = light_storage->light_get_param(base, RS::LIGHT_PARAM_SIZE); - if (angular_diameter > 0.0) { - angular_diameter = Math::tan(Math::deg_to_rad(angular_diameter)); - } else { - angular_diameter = 0.0; - } - sky_light_data.size = angular_diameter; - sky_globals.directional_light_count++; - if (sky_globals.directional_light_count >= sky_globals.max_directional_lights) { - break; - } + float angular_diameter = light_storage->light_get_param(base, RS::LIGHT_PARAM_SIZE); + if (angular_diameter > 0.0) { + angular_diameter = Math::tan(Math::deg_to_rad(angular_diameter)); + } else { + angular_diameter = 0.0; } - } - // Check whether the directional_light_buffer changes - bool light_data_dirty = false; - - // Light buffer is dirty if we have fewer or more lights - // If we have fewer lights, make sure that old lights are disabled - if (sky_globals.directional_light_count != sky_globals.last_frame_directional_light_count) { - light_data_dirty = true; - for (uint32_t i = sky_globals.directional_light_count; i < sky_globals.max_directional_lights; i++) { - sky_globals.directional_lights[i].enabled = false; - sky_globals.last_frame_directional_lights[i].enabled = false; + sky_light_data.size = angular_diameter; + sky_globals.directional_light_count++; + if (sky_globals.directional_light_count >= sky_globals.max_directional_lights) { + break; } } + } + // Check whether the directional_light_buffer changes + bool light_data_dirty = false; + + // Light buffer is dirty if we have fewer or more lights + // If we have fewer lights, make sure that old lights are disabled + if (sky_globals.directional_light_count != sky_globals.last_frame_directional_light_count) { + light_data_dirty = true; + for (uint32_t i = sky_globals.directional_light_count; i < sky_globals.max_directional_lights; i++) { + sky_globals.directional_lights[i].enabled = false; + sky_globals.last_frame_directional_lights[i].enabled = false; + } + } - if (!light_data_dirty) { - for (uint32_t i = 0; i < sky_globals.directional_light_count; i++) { - if (sky_globals.directional_lights[i].direction[0] != sky_globals.last_frame_directional_lights[i].direction[0] || - sky_globals.directional_lights[i].direction[1] != sky_globals.last_frame_directional_lights[i].direction[1] || - sky_globals.directional_lights[i].direction[2] != sky_globals.last_frame_directional_lights[i].direction[2] || - sky_globals.directional_lights[i].energy != sky_globals.last_frame_directional_lights[i].energy || - sky_globals.directional_lights[i].color[0] != sky_globals.last_frame_directional_lights[i].color[0] || - sky_globals.directional_lights[i].color[1] != sky_globals.last_frame_directional_lights[i].color[1] || - sky_globals.directional_lights[i].color[2] != sky_globals.last_frame_directional_lights[i].color[2] || - sky_globals.directional_lights[i].enabled != sky_globals.last_frame_directional_lights[i].enabled || - sky_globals.directional_lights[i].size != sky_globals.last_frame_directional_lights[i].size) { - light_data_dirty = true; - break; - } + if (!light_data_dirty) { + for (uint32_t i = 0; i < sky_globals.directional_light_count; i++) { + if (sky_globals.directional_lights[i].direction[0] != sky_globals.last_frame_directional_lights[i].direction[0] || + sky_globals.directional_lights[i].direction[1] != sky_globals.last_frame_directional_lights[i].direction[1] || + sky_globals.directional_lights[i].direction[2] != sky_globals.last_frame_directional_lights[i].direction[2] || + sky_globals.directional_lights[i].energy != sky_globals.last_frame_directional_lights[i].energy || + sky_globals.directional_lights[i].color[0] != sky_globals.last_frame_directional_lights[i].color[0] || + sky_globals.directional_lights[i].color[1] != sky_globals.last_frame_directional_lights[i].color[1] || + sky_globals.directional_lights[i].color[2] != sky_globals.last_frame_directional_lights[i].color[2] || + sky_globals.directional_lights[i].enabled != sky_globals.last_frame_directional_lights[i].enabled || + sky_globals.directional_lights[i].size != sky_globals.last_frame_directional_lights[i].size) { + light_data_dirty = true; + break; } } + } - if (light_data_dirty) { - glBufferData(GL_UNIFORM_BUFFER, sizeof(DirectionalLightData) * sky_globals.max_directional_lights, sky_globals.directional_lights, GL_STREAM_DRAW); - glBindBuffer(GL_UNIFORM_BUFFER, 0); + if (light_data_dirty) { + glBufferData(GL_UNIFORM_BUFFER, sizeof(DirectionalLightData) * sky_globals.max_directional_lights, sky_globals.directional_lights, GL_STREAM_DRAW); + glBindBuffer(GL_UNIFORM_BUFFER, 0); - DirectionalLightData *temp = sky_globals.last_frame_directional_lights; - sky_globals.last_frame_directional_lights = sky_globals.directional_lights; - sky_globals.directional_lights = temp; - sky_globals.last_frame_directional_light_count = sky_globals.directional_light_count; + DirectionalLightData *temp = sky_globals.last_frame_directional_lights; + sky_globals.last_frame_directional_lights = sky_globals.directional_lights; + sky_globals.directional_lights = temp; + sky_globals.last_frame_directional_light_count = sky_globals.directional_light_count; + if (sky) { sky->reflection_dirty = true; } } + } - if (p_render_data->view_count > 1) { - glBindBufferBase(GL_UNIFORM_BUFFER, SKY_MULTIVIEW_UNIFORM_LOCATION, scene_state.multiview_buffer); - glBindBuffer(GL_UNIFORM_BUFFER, 0); - } + if (p_render_data->view_count > 1) { + glBindBufferBase(GL_UNIFORM_BUFFER, SKY_MULTIVIEW_UNIFORM_LOCATION, scene_state.multiview_buffer); + glBindBuffer(GL_UNIFORM_BUFFER, 0); + } - if (!sky->radiance) { - _invalidate_sky(sky); - _update_dirty_skys(); - } + if (sky && !sky->radiance) { + _invalidate_sky(sky); + _update_dirty_skys(); } } diff --git a/drivers/gles3/shaders/scene.glsl b/drivers/gles3/shaders/scene.glsl index aa68febec8..7cba77be2f 100644 --- a/drivers/gles3/shaders/scene.glsl +++ b/drivers/gles3/shaders/scene.glsl @@ -1039,17 +1039,16 @@ void main() { if (alpha < alpha_scissor_threshold) { discard; } -#endif // ALPHA_SCISSOR_USED - +#else +#ifdef MODE_RENDER_DEPTH #ifdef USE_OPAQUE_PREPASS -#if !defined(ALPHA_SCISSOR_USED) if (alpha < opaque_prepass_threshold) { discard; } - -#endif // not ALPHA_SCISSOR_USED #endif // USE_OPAQUE_PREPASS +#endif // MODE_RENDER_DEPTH +#endif // !ALPHA_SCISSOR_USED #endif // !USE_SHADOW_TO_OPACITY @@ -1270,17 +1269,16 @@ void main() { if (alpha < alpha_scissor) { discard; } -#endif // ALPHA_SCISSOR_USED - +#else +#ifdef MODE_RENDER_DEPTH #ifdef USE_OPAQUE_PREPASS -#if !defined(ALPHA_SCISSOR_USED) if (alpha < opaque_prepass_threshold) { discard; } - -#endif // not ALPHA_SCISSOR_USED #endif // USE_OPAQUE_PREPASS +#endif // MODE_RENDER_DEPTH +#endif // !ALPHA_SCISSOR_USED #endif // USE_SHADOW_TO_OPACITY diff --git a/drivers/gles3/storage/material_storage.cpp b/drivers/gles3/storage/material_storage.cpp index d0746a8fc8..aa6319f0ef 100644 --- a/drivers/gles3/storage/material_storage.cpp +++ b/drivers/gles3/storage/material_storage.cpp @@ -1338,7 +1338,7 @@ MaterialStorage::MaterialStorage() { actions.render_mode_defines["cull_front"] = "#define DO_SIDE_CHECK\n"; actions.render_mode_defines["cull_disabled"] = "#define DO_SIDE_CHECK\n"; actions.render_mode_defines["particle_trails"] = "#define USE_PARTICLE_TRAILS\n"; - actions.render_mode_defines["depth_draw_opaque"] = "#define USE_OPAQUE_PREPASS\n"; + actions.render_mode_defines["depth_prepass_alpha"] = "#define USE_OPAQUE_PREPASS\n"; bool force_lambert = GLOBAL_GET("rendering/shading/overrides/force_lambert_over_burley"); diff --git a/drivers/gles3/storage/mesh_storage.cpp b/drivers/gles3/storage/mesh_storage.cpp index ad4bdae272..381cf1a7c6 100644 --- a/drivers/gles3/storage/mesh_storage.cpp +++ b/drivers/gles3/storage/mesh_storage.cpp @@ -1075,6 +1075,7 @@ void MeshStorage::update_mesh_instances() { } glEnable(GL_RASTERIZER_DISCARD); + glBindFramebuffer(GL_FRAMEBUFFER, 0); // Process skeletons and blend shapes using transform feedback while (dirty_mesh_instance_arrays.first()) { MeshInstance *mi = dirty_mesh_instance_arrays.first()->self(); @@ -1284,13 +1285,17 @@ void MeshStorage::multimesh_allocate_data(RID p_multimesh, int p_instances, RS:: multimesh->data_cache_used_dirty_regions = 0; } + // If we have either color or custom data, reserve space for both to make data handling logic simpler. + // This way we can always treat them both as a single, compressed uvec4. + int color_and_custom_strides = (p_use_colors || p_use_custom_data) ? 2 : 0; + multimesh->instances = p_instances; multimesh->xform_format = p_transform_format; multimesh->uses_colors = p_use_colors; multimesh->color_offset_cache = p_transform_format == RS::MULTIMESH_TRANSFORM_2D ? 8 : 12; multimesh->uses_custom_data = p_use_custom_data; - multimesh->custom_data_offset_cache = multimesh->color_offset_cache + (p_use_colors ? 2 : 0); - multimesh->stride_cache = multimesh->custom_data_offset_cache + (p_use_custom_data ? 2 : 0); + multimesh->custom_data_offset_cache = multimesh->color_offset_cache + color_and_custom_strides; + multimesh->stride_cache = multimesh->custom_data_offset_cache + color_and_custom_strides; multimesh->buffer_set = false; multimesh->data_cache = Vector<float>(); diff --git a/drivers/gles3/storage/texture_storage.cpp b/drivers/gles3/storage/texture_storage.cpp index 649123cdca..a3f230f9e2 100644 --- a/drivers/gles3/storage/texture_storage.cpp +++ b/drivers/gles3/storage/texture_storage.cpp @@ -1192,6 +1192,9 @@ Size2 TextureStorage::texture_size_with_proxy(RID p_texture) { } } +void TextureStorage::texture_rd_initialize(RID p_texture, const RID &p_rd_texture, const RS::TextureLayeredType p_layer_type) { +} + RID TextureStorage::texture_get_rd_texture(RID p_texture, bool p_srgb) const { return RID(); } diff --git a/drivers/gles3/storage/texture_storage.h b/drivers/gles3/storage/texture_storage.h index fefcd56570..bad2b31a31 100644 --- a/drivers/gles3/storage/texture_storage.h +++ b/drivers/gles3/storage/texture_storage.h @@ -252,10 +252,10 @@ struct Texture { } Config *config = Config::get_singleton(); state_filter = p_filter; - GLenum pmin = GL_NEAREST; // param min - GLenum pmag = GL_NEAREST; // param mag - GLint max_lod = 1000; - bool use_anisotropy = false; + GLenum pmin = GL_NEAREST; + GLenum pmag = GL_NEAREST; + GLint max_lod = 0; + GLfloat anisotropy = 1.0f; switch (state_filter) { case RS::CANVAS_ITEM_TEXTURE_FILTER_NEAREST: { pmin = GL_NEAREST; @@ -268,7 +268,7 @@ struct Texture { max_lod = 0; } break; case RS::CANVAS_ITEM_TEXTURE_FILTER_NEAREST_WITH_MIPMAPS_ANISOTROPIC: { - use_anisotropy = true; + anisotropy = config->anisotropic_level; }; [[fallthrough]]; case RS::CANVAS_ITEM_TEXTURE_FILTER_NEAREST_WITH_MIPMAPS: { @@ -278,12 +278,14 @@ struct Texture { max_lod = 0; } else if (config->use_nearest_mip_filter) { pmin = GL_NEAREST_MIPMAP_NEAREST; + max_lod = 1000; } else { pmin = GL_NEAREST_MIPMAP_LINEAR; + max_lod = 1000; } } break; case RS::CANVAS_ITEM_TEXTURE_FILTER_LINEAR_WITH_MIPMAPS_ANISOTROPIC: { - use_anisotropy = true; + anisotropy = config->anisotropic_level; }; [[fallthrough]]; case RS::CANVAS_ITEM_TEXTURE_FILTER_LINEAR_WITH_MIPMAPS: { @@ -293,19 +295,22 @@ struct Texture { max_lod = 0; } else if (config->use_nearest_mip_filter) { pmin = GL_LINEAR_MIPMAP_NEAREST; + max_lod = 1000; } else { pmin = GL_LINEAR_MIPMAP_LINEAR; + max_lod = 1000; } } break; default: { + return; } break; } glTexParameteri(target, GL_TEXTURE_MIN_FILTER, pmin); glTexParameteri(target, GL_TEXTURE_MAG_FILTER, pmag); glTexParameteri(target, GL_TEXTURE_BASE_LEVEL, 0); glTexParameteri(target, GL_TEXTURE_MAX_LEVEL, max_lod); - if (config->support_anisotropic_filter && use_anisotropy) { - glTexParameterf(target, _GL_TEXTURE_MAX_ANISOTROPY_EXT, config->anisotropic_level); + if (config->support_anisotropic_filter) { + glTexParameterf(target, _GL_TEXTURE_MAX_ANISOTROPY_EXT, anisotropy); } } void gl_set_repeat(RS::CanvasItemTextureRepeat p_repeat) { @@ -313,8 +318,11 @@ struct Texture { return; } state_repeat = p_repeat; - GLenum prep = GL_CLAMP_TO_EDGE; // parameter repeat + GLenum prep = GL_CLAMP_TO_EDGE; switch (state_repeat) { + case RS::CANVAS_ITEM_TEXTURE_REPEAT_DISABLED: { + prep = GL_CLAMP_TO_EDGE; + } break; case RS::CANVAS_ITEM_TEXTURE_REPEAT_ENABLED: { prep = GL_REPEAT; } break; @@ -322,6 +330,7 @@ struct Texture { prep = GL_MIRRORED_REPEAT; } break; default: { + return; } break; } glTexParameteri(target, GL_TEXTURE_WRAP_T, prep); @@ -330,8 +339,8 @@ struct Texture { } private: - RS::CanvasItemTextureFilter state_filter = RS::CANVAS_ITEM_TEXTURE_FILTER_LINEAR; - RS::CanvasItemTextureRepeat state_repeat = RS::CANVAS_ITEM_TEXTURE_REPEAT_DISABLED; + RS::CanvasItemTextureFilter state_filter = RS::CANVAS_ITEM_TEXTURE_FILTER_MAX; + RS::CanvasItemTextureRepeat state_repeat = RS::CANVAS_ITEM_TEXTURE_REPEAT_MAX; }; struct RenderTarget { @@ -536,11 +545,12 @@ public: virtual Size2 texture_size_with_proxy(RID p_proxy) override; + virtual void texture_rd_initialize(RID p_texture, const RID &p_rd_texture, const RS::TextureLayeredType p_layer_type = RS::TEXTURE_LAYERED_2D_ARRAY) override; virtual RID texture_get_rd_texture(RID p_texture, bool p_srgb = false) const override; virtual uint64_t texture_get_native_handle(RID p_texture, bool p_srgb = false) const override; void texture_set_data(RID p_texture, const Ref<Image> &p_image, int p_layer = 0); - Image::Format texture_get_format(RID p_texture) const; + virtual Image::Format texture_get_format(RID p_texture) const override; uint32_t texture_get_texid(RID p_texture) const; uint32_t texture_get_width(RID p_texture) const; uint32_t texture_get_height(RID p_texture) const; diff --git a/drivers/png/resource_saver_png.cpp b/drivers/png/resource_saver_png.cpp index ab0ff32514..0df6b2ba21 100644 --- a/drivers/png/resource_saver_png.cpp +++ b/drivers/png/resource_saver_png.cpp @@ -33,7 +33,7 @@ #include "core/io/file_access.h" #include "core/io/image.h" #include "drivers/png/png_driver_common.h" -#include "scene/resources/texture.h" +#include "scene/resources/image_texture.h" Error ResourceSaverPNG::save(const Ref<Resource> &p_resource, const String &p_path, uint32_t p_flags) { Ref<ImageTexture> texture = p_resource; diff --git a/drivers/unix/file_access_unix.cpp b/drivers/unix/file_access_unix.cpp index 45f9f14dab..a80b7d449e 100644 --- a/drivers/unix/file_access_unix.cpp +++ b/drivers/unix/file_access_unix.cpp @@ -108,10 +108,7 @@ Error FileAccessUnix::open_internal(const String &p_path, int p_mode_flags) { last_error = ERR_FILE_CANT_OPEN; return last_error; } - // Fix temporary file permissions (defaults to 0600 instead of 0666 & ~umask). - mode_t mask = umask(022); - umask(mask); - fchmod(fd, 0666 & ~mask); + fchmod(fd, 0666); path = String::utf8(cs.ptr()); f = fdopen(fd, mode_string); diff --git a/drivers/unix/net_socket_posix.cpp b/drivers/unix/net_socket_posix.cpp index b46af012f1..a8074aa3f6 100644 --- a/drivers/unix/net_socket_posix.cpp +++ b/drivers/unix/net_socket_posix.cpp @@ -204,6 +204,9 @@ NetSocketPosix::NetError NetSocketPosix::_get_socket_error() const { if (err == WSAEACCES) { return ERR_NET_UNAUTHORIZED; } + if (err == WSAEMSGSIZE || err == WSAENOBUFS) { + return ERR_NET_BUFFER_TOO_SMALL; + } print_verbose("Socket error: " + itos(err)); return ERR_NET_OTHER; #else @@ -222,6 +225,9 @@ NetSocketPosix::NetError NetSocketPosix::_get_socket_error() const { if (errno == EACCES) { return ERR_NET_UNAUTHORIZED; } + if (errno == ENOBUFS) { + return ERR_NET_BUFFER_TOO_SMALL; + } print_verbose("Socket error: " + itos(errno)); return ERR_NET_OTHER; #endif @@ -550,6 +556,10 @@ Error NetSocketPosix::recv(uint8_t *p_buffer, int p_len, int &r_read) { return ERR_BUSY; } + if (err == ERR_NET_BUFFER_TOO_SMALL) { + return ERR_OUT_OF_MEMORY; + } + return FAILED; } @@ -571,6 +581,10 @@ Error NetSocketPosix::recvfrom(uint8_t *p_buffer, int p_len, int &r_read, IPAddr return ERR_BUSY; } + if (err == ERR_NET_BUFFER_TOO_SMALL) { + return ERR_OUT_OF_MEMORY; + } + return FAILED; } @@ -606,6 +620,9 @@ Error NetSocketPosix::send(const uint8_t *p_buffer, int p_len, int &r_sent) { if (err == ERR_NET_WOULD_BLOCK) { return ERR_BUSY; } + if (err == ERR_NET_BUFFER_TOO_SMALL) { + return ERR_OUT_OF_MEMORY; + } return FAILED; } @@ -625,6 +642,9 @@ Error NetSocketPosix::sendto(const uint8_t *p_buffer, int p_len, int &r_sent, IP if (err == ERR_NET_WOULD_BLOCK) { return ERR_BUSY; } + if (err == ERR_NET_BUFFER_TOO_SMALL) { + return ERR_OUT_OF_MEMORY; + } return FAILED; } diff --git a/drivers/unix/net_socket_posix.h b/drivers/unix/net_socket_posix.h index bd2088b4f9..2682530e15 100644 --- a/drivers/unix/net_socket_posix.h +++ b/drivers/unix/net_socket_posix.h @@ -56,6 +56,7 @@ private: ERR_NET_IN_PROGRESS, ERR_NET_ADDRESS_INVALID_OR_UNAVAILABLE, ERR_NET_UNAUTHORIZED, + ERR_NET_BUFFER_TOO_SMALL, ERR_NET_OTHER, }; diff --git a/drivers/vulkan/rendering_device_vulkan.cpp b/drivers/vulkan/rendering_device_vulkan.cpp index 23c6919854..be6f8f3580 100644 --- a/drivers/vulkan/rendering_device_vulkan.cpp +++ b/drivers/vulkan/rendering_device_vulkan.cpp @@ -54,9 +54,13 @@ RenderingDeviceVulkan::Buffer *RenderingDeviceVulkan::_get_buffer_from_owner(RID r_stage_mask |= VK_PIPELINE_STAGE_VERTEX_INPUT_BIT; r_access_mask |= VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT; if (buffer->usage & VK_BUFFER_USAGE_STORAGE_BUFFER_BIT) { - if (p_post_barrier.has_flag(BARRIER_MASK_RASTER)) { + if (p_post_barrier.has_flag(BARRIER_MASK_VERTEX)) { r_access_mask |= VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT; - r_stage_mask |= VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + r_stage_mask |= VK_PIPELINE_STAGE_VERTEX_SHADER_BIT; + } + if (p_post_barrier.has_flag(BARRIER_MASK_FRAGMENT)) { + r_access_mask |= VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT; + r_stage_mask |= VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; } if (p_post_barrier.has_flag(BARRIER_MASK_COMPUTE)) { r_access_mask |= VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT; @@ -68,8 +72,11 @@ RenderingDeviceVulkan::Buffer *RenderingDeviceVulkan::_get_buffer_from_owner(RID r_access_mask |= VK_ACCESS_INDEX_READ_BIT; buffer = index_buffer_owner.get_or_null(p_buffer); } else if (uniform_buffer_owner.owns(p_buffer)) { - if (p_post_barrier.has_flag(BARRIER_MASK_RASTER)) { - r_stage_mask |= VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + if (p_post_barrier.has_flag(BARRIER_MASK_VERTEX)) { + r_stage_mask |= VK_PIPELINE_STAGE_VERTEX_SHADER_BIT; + } + if (p_post_barrier.has_flag(BARRIER_MASK_FRAGMENT)) { + r_stage_mask |= VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; } if (p_post_barrier.has_flag(BARRIER_MASK_COMPUTE)) { r_stage_mask |= VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT; @@ -77,8 +84,12 @@ RenderingDeviceVulkan::Buffer *RenderingDeviceVulkan::_get_buffer_from_owner(RID r_access_mask |= VK_ACCESS_UNIFORM_READ_BIT; buffer = uniform_buffer_owner.get_or_null(p_buffer); } else if (texture_buffer_owner.owns(p_buffer)) { - if (p_post_barrier.has_flag(BARRIER_MASK_RASTER)) { - r_stage_mask |= VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + if (p_post_barrier.has_flag(BARRIER_MASK_VERTEX)) { + r_stage_mask |= VK_PIPELINE_STAGE_VERTEX_SHADER_BIT; + r_access_mask |= VK_ACCESS_SHADER_READ_BIT; + } + if (p_post_barrier.has_flag(BARRIER_MASK_FRAGMENT)) { + r_stage_mask |= VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; r_access_mask |= VK_ACCESS_SHADER_READ_BIT; } if (p_post_barrier.has_flag(BARRIER_MASK_COMPUTE)) { @@ -89,8 +100,12 @@ RenderingDeviceVulkan::Buffer *RenderingDeviceVulkan::_get_buffer_from_owner(RID buffer = &texture_buffer_owner.get_or_null(p_buffer)->buffer; } else if (storage_buffer_owner.owns(p_buffer)) { buffer = storage_buffer_owner.get_or_null(p_buffer); - if (p_post_barrier.has_flag(BARRIER_MASK_RASTER)) { - r_stage_mask |= VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + if (p_post_barrier.has_flag(BARRIER_MASK_VERTEX)) { + r_stage_mask |= VK_PIPELINE_STAGE_VERTEX_SHADER_BIT; + r_access_mask |= VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT; + } + if (p_post_barrier.has_flag(BARRIER_MASK_FRAGMENT)) { + r_stage_mask |= VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; r_access_mask |= VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT; } if (p_post_barrier.has_flag(BARRIER_MASK_COMPUTE)) { @@ -2625,8 +2640,12 @@ Error RenderingDeviceVulkan::_texture_update(RID p_texture, uint32_t p_layer, co barrier_flags |= VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT; access_flags |= VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT; } - if (p_post_barrier.has_flag(BARRIER_MASK_RASTER)) { - barrier_flags |= VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + if (p_post_barrier.has_flag(BARRIER_MASK_VERTEX)) { + barrier_flags |= VK_PIPELINE_STAGE_VERTEX_SHADER_BIT; + access_flags |= VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT; + } + if (p_post_barrier.has_flag(BARRIER_MASK_FRAGMENT)) { + barrier_flags |= VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; access_flags |= VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT; } if (p_post_barrier.has_flag(BARRIER_MASK_TRANSFER)) { @@ -2874,6 +2893,29 @@ bool RenderingDeviceVulkan::texture_is_valid(RID p_texture) { return texture_owner.owns(p_texture); } +RD::TextureFormat RenderingDeviceVulkan::texture_get_format(RID p_texture) { + _THREAD_SAFE_METHOD_ + + Texture *tex = texture_owner.get_or_null(p_texture); + ERR_FAIL_COND_V(!tex, TextureFormat()); + + TextureFormat tf; + + tf.format = tex->format; + tf.width = tex->width; + tf.height = tex->height; + tf.depth = tex->depth; + tf.array_layers = tex->layers; + tf.mipmaps = tex->mipmaps; + tf.texture_type = tex->type; + tf.samples = tex->samples; + tf.usage_bits = tex->usage_flags; + tf.shareable_formats = tex->allowed_shared_formats; + tf.is_resolve_buffer = tex->is_resolve_buffer; + + return tf; +} + Size2i RenderingDeviceVulkan::texture_size(RID p_texture) { _THREAD_SAFE_METHOD_ @@ -3020,8 +3062,12 @@ Error RenderingDeviceVulkan::texture_copy(RID p_from_texture, RID p_to_texture, barrier_flags |= VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT; access_flags |= VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT; } - if (p_post_barrier.has_flag(BARRIER_MASK_RASTER)) { - barrier_flags |= VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + if (p_post_barrier.has_flag(BARRIER_MASK_VERTEX)) { + barrier_flags |= VK_PIPELINE_STAGE_VERTEX_SHADER_BIT; + access_flags |= VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT; + } + if (p_post_barrier.has_flag(BARRIER_MASK_FRAGMENT)) { + barrier_flags |= VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; access_flags |= VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT; } if (p_post_barrier.has_flag(BARRIER_MASK_TRANSFER)) { @@ -3198,8 +3244,12 @@ Error RenderingDeviceVulkan::texture_resolve_multisample(RID p_from_texture, RID barrier_flags |= VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT; access_flags |= VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT; } - if (p_post_barrier.has_flag(BARRIER_MASK_RASTER)) { - barrier_flags |= VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + if (p_post_barrier.has_flag(BARRIER_MASK_VERTEX)) { + barrier_flags |= VK_PIPELINE_STAGE_VERTEX_SHADER_BIT; + access_flags |= VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT; + } + if (p_post_barrier.has_flag(BARRIER_MASK_FRAGMENT)) { + barrier_flags |= VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; access_flags |= VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT; } if (p_post_barrier.has_flag(BARRIER_MASK_TRANSFER)) { @@ -3334,8 +3384,12 @@ Error RenderingDeviceVulkan::texture_clear(RID p_texture, const Color &p_color, barrier_flags |= VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT; access_flags |= VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT; } - if (p_post_barrier.has_flag(BARRIER_MASK_RASTER)) { - barrier_flags |= VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + if (p_post_barrier.has_flag(BARRIER_MASK_VERTEX)) { + barrier_flags |= VK_PIPELINE_STAGE_VERTEX_SHADER_BIT; + access_flags |= VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT; + } + if (p_post_barrier.has_flag(BARRIER_MASK_FRAGMENT)) { + barrier_flags |= VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; access_flags |= VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT; } if (p_post_barrier.has_flag(BARRIER_MASK_TRANSFER)) { @@ -7651,10 +7705,14 @@ void RenderingDeviceVulkan::draw_list_end(BitField<BarrierMask> p_post_barrier) barrier_flags |= VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT; access_flags |= VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT; } - if (p_post_barrier.has_flag(BARRIER_MASK_RASTER)) { - barrier_flags |= VK_PIPELINE_STAGE_VERTEX_INPUT_BIT | VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT /*| VK_PIPELINE_STAGE_DRAW_INDIRECT_BIT*/; + if (p_post_barrier.has_flag(BARRIER_MASK_VERTEX)) { + barrier_flags |= VK_PIPELINE_STAGE_VERTEX_INPUT_BIT | VK_PIPELINE_STAGE_VERTEX_SHADER_BIT /*| VK_PIPELINE_STAGE_DRAW_INDIRECT_BIT*/; access_flags |= VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_INDEX_READ_BIT | VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT /*| VK_ACCESS_INDIRECT_COMMAND_READ_BIT*/; } + if (p_post_barrier.has_flag(BARRIER_MASK_FRAGMENT)) { + barrier_flags |= VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT /*| VK_PIPELINE_STAGE_DRAW_INDIRECT_BIT*/; + access_flags |= VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT /*| VK_ACCESS_INDIRECT_COMMAND_READ_BIT*/; + } if (p_post_barrier.has_flag(BARRIER_MASK_TRANSFER)) { barrier_flags |= VK_PIPELINE_STAGE_TRANSFER_BIT; access_flags |= VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_TRANSFER_READ_BIT; @@ -7731,6 +7789,8 @@ void RenderingDeviceVulkan::draw_list_end(BitField<BarrierMask> p_post_barrier) /***********************/ RenderingDevice::ComputeListID RenderingDeviceVulkan::compute_list_begin(bool p_allow_draw_overlap) { + _THREAD_SAFE_METHOD_ + ERR_FAIL_COND_V_MSG(!p_allow_draw_overlap && draw_list != nullptr, INVALID_ID, "Only one draw list can be active at the same time."); ERR_FAIL_COND_V_MSG(compute_list != nullptr, INVALID_ID, "Only one draw/compute list can be active at the same time."); @@ -7745,6 +7805,8 @@ RenderingDevice::ComputeListID RenderingDeviceVulkan::compute_list_begin(bool p_ } void RenderingDeviceVulkan::compute_list_bind_compute_pipeline(ComputeListID p_list, RID p_compute_pipeline) { + // Must be called within a compute list, the class mutex is locked during that time + ERR_FAIL_COND(p_list != ID_TYPE_COMPUTE_LIST); ERR_FAIL_COND(!compute_list); @@ -7809,6 +7871,8 @@ void RenderingDeviceVulkan::compute_list_bind_compute_pipeline(ComputeListID p_l } void RenderingDeviceVulkan::compute_list_bind_uniform_set(ComputeListID p_list, RID p_uniform_set, uint32_t p_index) { + // Must be called within a compute list, the class mutex is locked during that time + ERR_FAIL_COND(p_list != ID_TYPE_COMPUTE_LIST); ERR_FAIL_COND(!compute_list); @@ -7983,6 +8047,8 @@ void RenderingDeviceVulkan::compute_list_set_push_constant(ComputeListID p_list, } void RenderingDeviceVulkan::compute_list_dispatch(ComputeListID p_list, uint32_t p_x_groups, uint32_t p_y_groups, uint32_t p_z_groups) { + // Must be called within a compute list, the class mutex is locked during that time + ERR_FAIL_COND(p_list != ID_TYPE_COMPUTE_LIST); ERR_FAIL_COND(!compute_list); @@ -8126,6 +8192,8 @@ void RenderingDeviceVulkan::compute_list_dispatch_indirect(ComputeListID p_list, } void RenderingDeviceVulkan::compute_list_add_barrier(ComputeListID p_list) { + // Must be called within a compute list, the class mutex is locked during that time + uint32_t barrier_flags = VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT; uint32_t access_flags = VK_ACCESS_SHADER_READ_BIT; _compute_list_add_barrier(BARRIER_MASK_COMPUTE, barrier_flags, access_flags); @@ -8199,10 +8267,14 @@ void RenderingDeviceVulkan::compute_list_end(BitField<BarrierMask> p_post_barrie barrier_flags |= VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT; access_flags |= VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT; } - if (p_post_barrier.has_flag(BARRIER_MASK_RASTER)) { - barrier_flags |= VK_PIPELINE_STAGE_VERTEX_INPUT_BIT | VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_DRAW_INDIRECT_BIT; + if (p_post_barrier.has_flag(BARRIER_MASK_VERTEX)) { + barrier_flags |= VK_PIPELINE_STAGE_VERTEX_INPUT_BIT | VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_DRAW_INDIRECT_BIT; access_flags |= VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_INDEX_READ_BIT | VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT | VK_ACCESS_INDIRECT_COMMAND_READ_BIT; } + if (p_post_barrier.has_flag(BARRIER_MASK_FRAGMENT)) { + barrier_flags |= VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_DRAW_INDIRECT_BIT; + access_flags |= VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_INDIRECT_COMMAND_READ_BIT; + } if (p_post_barrier.has_flag(BARRIER_MASK_TRANSFER)) { barrier_flags |= VK_PIPELINE_STAGE_TRANSFER_BIT; access_flags |= VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_TRANSFER_READ_BIT; @@ -8227,7 +8299,7 @@ void RenderingDeviceVulkan::barrier(BitField<BarrierMask> p_from, BitField<Barri src_barrier_flags |= VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT; src_access_flags |= VK_ACCESS_SHADER_WRITE_BIT; } - if (p_from.has_flag(BARRIER_MASK_RASTER)) { + if (p_from.has_flag(BARRIER_MASK_FRAGMENT)) { src_barrier_flags |= VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; src_access_flags |= VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; } @@ -8247,10 +8319,14 @@ void RenderingDeviceVulkan::barrier(BitField<BarrierMask> p_from, BitField<Barri dst_barrier_flags |= VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT; dst_access_flags |= VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT; } - if (p_to.has_flag(BARRIER_MASK_RASTER)) { - dst_barrier_flags |= VK_PIPELINE_STAGE_VERTEX_INPUT_BIT | VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_DRAW_INDIRECT_BIT; + if (p_to.has_flag(BARRIER_MASK_VERTEX)) { + dst_barrier_flags |= VK_PIPELINE_STAGE_VERTEX_INPUT_BIT | VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_DRAW_INDIRECT_BIT; dst_access_flags |= VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_INDEX_READ_BIT | VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT | VK_ACCESS_INDIRECT_COMMAND_READ_BIT; } + if (p_to.has_flag(BARRIER_MASK_FRAGMENT)) { + dst_barrier_flags |= VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_DRAW_INDIRECT_BIT; + dst_access_flags |= VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_INDIRECT_COMMAND_READ_BIT; + } if (p_to.has_flag(BARRIER_MASK_TRANSFER)) { dst_barrier_flags |= VK_PIPELINE_STAGE_TRANSFER_BIT; dst_access_flags |= VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_TRANSFER_READ_BIT; @@ -8606,6 +8682,8 @@ void RenderingDeviceVulkan::swap_buffers() { } void RenderingDeviceVulkan::submit() { + _THREAD_SAFE_METHOD_ + ERR_FAIL_COND_MSG(local_device.is_null(), "Only local devices can submit and sync."); ERR_FAIL_COND_MSG(local_device_processing, "device already submitted, call sync to wait until done."); @@ -8617,6 +8695,8 @@ void RenderingDeviceVulkan::submit() { } void RenderingDeviceVulkan::sync() { + _THREAD_SAFE_METHOD_ + ERR_FAIL_COND_MSG(local_device.is_null(), "Only local devices can submit and sync."); ERR_FAIL_COND_MSG(!local_device_processing, "sync can only be called after a submit"); diff --git a/drivers/vulkan/rendering_device_vulkan.h b/drivers/vulkan/rendering_device_vulkan.h index 9c621c1d44..010f7c9337 100644 --- a/drivers/vulkan/rendering_device_vulkan.h +++ b/drivers/vulkan/rendering_device_vulkan.h @@ -1082,6 +1082,7 @@ public: virtual bool texture_is_format_supported_for_usage(DataFormat p_format, BitField<RenderingDevice::TextureUsageBits> p_usage) const; virtual bool texture_is_shared(RID p_texture); virtual bool texture_is_valid(RID p_texture); + virtual TextureFormat texture_get_format(RID p_texture); virtual Size2i texture_size(RID p_texture); virtual uint64_t texture_get_native_handle(RID p_texture); diff --git a/drivers/vulkan/vulkan_context.cpp b/drivers/vulkan/vulkan_context.cpp index 7c52447e44..3a1330b331 100644 --- a/drivers/vulkan/vulkan_context.cpp +++ b/drivers/vulkan/vulkan_context.cpp @@ -1775,6 +1775,7 @@ Error VulkanContext::_clean_up_swap_chain(Window *window) { fpDestroySwapchainKHR(device, window->swapchain, nullptr); window->swapchain = VK_NULL_HANDLE; vkDestroyRenderPass(device, window->render_pass, nullptr); + window->render_pass = VK_NULL_HANDLE; if (window->swapchain_image_resources) { for (uint32_t i = 0; i < swapchainImageCount; i++) { vkDestroyImageView(device, window->swapchain_image_resources[i].view, nullptr); @@ -1783,6 +1784,7 @@ Error VulkanContext::_clean_up_swap_chain(Window *window) { free(window->swapchain_image_resources); window->swapchain_image_resources = nullptr; + swapchainImageCount = 0; } if (separate_present_queue) { vkDestroyCommandPool(device, window->present_cmd_pool, nullptr); diff --git a/editor/animation_track_editor.cpp b/editor/animation_track_editor.cpp index 24a6d89118..3aa3aa567b 100644 --- a/editor/animation_track_editor.cpp +++ b/editor/animation_track_editor.cpp @@ -3297,7 +3297,7 @@ void AnimationTrackEditor::set_animation(const Ref<Animation> &p_anim, bool p_re track_edits[_get_track_selected()]->release_focus(); } if (animation.is_valid()) { - animation->disconnect("changed", callable_mp(this, &AnimationTrackEditor::_animation_changed)); + animation->disconnect_changed(callable_mp(this, &AnimationTrackEditor::_animation_changed)); _clear_selection(); } animation = p_anim; @@ -3308,7 +3308,7 @@ void AnimationTrackEditor::set_animation(const Ref<Animation> &p_anim, bool p_re _update_tracks(); if (animation.is_valid()) { - animation->connect("changed", callable_mp(this, &AnimationTrackEditor::_animation_changed)); + animation->connect_changed(callable_mp(this, &AnimationTrackEditor::_animation_changed)); hscroll->show(); edit->set_disabled(read_only); @@ -3632,13 +3632,15 @@ void AnimationTrackEditor::commit_insert_queue() { } } - if (bool(EDITOR_GET("editors/animation/confirm_insert_track")) && num_tracks > 0) { + // Skip the confirmation dialog if the user holds Shift while clicking the key icon. + if (!Input::get_singleton()->is_key_pressed(Key::SHIFT) && num_tracks > 0) { + String shortcut_hint = TTR("Hold Shift when clicking the key icon to skip this dialog."); // Potentially a new key, does not exist. if (num_tracks == 1) { // TRANSLATORS: %s will be replaced by a phrase describing the target of track. - insert_confirm_text->set_text(vformat(TTR("Create new track for %s and insert key?"), last_track_query)); + insert_confirm_text->set_text(vformat(TTR("Create new track for %s and insert key?") + "\n\n" + shortcut_hint, last_track_query)); } else { - insert_confirm_text->set_text(vformat(TTR("Create %d new tracks and insert keys?"), num_tracks)); + insert_confirm_text->set_text(vformat(TTR("Create %d new tracks and insert keys?") + "\n\n" + shortcut_hint, num_tracks)); } insert_confirm_bezier->set_visible(all_bezier); diff --git a/editor/create_dialog.cpp b/editor/create_dialog.cpp index 6d345a1d84..8b71d7586f 100644 --- a/editor/create_dialog.cpp +++ b/editor/create_dialog.cpp @@ -370,7 +370,8 @@ float CreateDialog::_score_type(const String &p_type, const String &p_search) co // Look through at most 5 recent items bool in_recent = false; - for (int i = 0; i < MIN(5, recent->get_item_count()); i++) { + constexpr int RECENT_COMPLETION_SIZE = 5; + for (int i = 0; i < MIN(RECENT_COMPLETION_SIZE - 1, recent->get_item_count()); i++) { if (recent->get_item_text(i) == p_type) { in_recent = true; break; @@ -406,7 +407,8 @@ void CreateDialog::_confirmed() { if (f.is_valid()) { f->store_line(selected_item); - for (int i = 0; i < MIN(32, recent->get_item_count()); i++) { + constexpr int RECENT_HISTORY_SIZE = 15; + for (int i = 0; i < MIN(RECENT_HISTORY_SIZE - 1, recent->get_item_count()); i++) { if (recent->get_item_text(i) != selected_item) { f->store_line(recent->get_item_text(i)); } diff --git a/editor/debugger/editor_debugger_inspector.cpp b/editor/debugger/editor_debugger_inspector.cpp index e083e1746d..1e9b7c2c60 100644 --- a/editor/debugger/editor_debugger_inspector.cpp +++ b/editor/debugger/editor_debugger_inspector.cpp @@ -232,7 +232,7 @@ void EditorDebuggerInspector::add_stack_variable(const Array &p_array) { PropertyHint h = PROPERTY_HINT_NONE; String hs; - if (v.get_type() == Variant::OBJECT) { + if (var.var_type == Variant::OBJECT) { v = Object::cast_to<EncodedObjectAsID>(v)->get_object_id(); h = PROPERTY_HINT_OBJECT_ID; hs = "Object"; diff --git a/editor/debugger/editor_profiler.cpp b/editor/debugger/editor_profiler.cpp index 5e677c454e..e59fc6186a 100644 --- a/editor/debugger/editor_profiler.cpp +++ b/editor/debugger/editor_profiler.cpp @@ -33,6 +33,7 @@ #include "core/os/os.h" #include "editor/editor_scale.h" #include "editor/editor_settings.h" +#include "scene/resources/image_texture.h" void EditorProfiler::_make_metric_ptrs(Metric &m) { for (int i = 0; i < m.categories.size(); i++) { diff --git a/editor/debugger/editor_profiler.h b/editor/debugger/editor_profiler.h index eea8ed8365..3f7a0cade5 100644 --- a/editor/debugger/editor_profiler.h +++ b/editor/debugger/editor_profiler.h @@ -40,6 +40,8 @@ #include "scene/gui/texture_rect.h" #include "scene/gui/tree.h" +class ImageTexture; + class EditorProfiler : public VBoxContainer { GDCLASS(EditorProfiler, VBoxContainer); diff --git a/editor/debugger/editor_visual_profiler.cpp b/editor/debugger/editor_visual_profiler.cpp index 2ecb029f1a..984d8e33c5 100644 --- a/editor/debugger/editor_visual_profiler.cpp +++ b/editor/debugger/editor_visual_profiler.cpp @@ -33,6 +33,7 @@ #include "core/os/os.h" #include "editor/editor_scale.h" #include "editor/editor_settings.h" +#include "scene/resources/image_texture.h" void EditorVisualProfiler::add_frame_metric(const Metric &p_metric) { ++last_metric; diff --git a/editor/debugger/editor_visual_profiler.h b/editor/debugger/editor_visual_profiler.h index 5831e3322d..492985506a 100644 --- a/editor/debugger/editor_visual_profiler.h +++ b/editor/debugger/editor_visual_profiler.h @@ -41,6 +41,8 @@ #include "scene/gui/texture_rect.h" #include "scene/gui/tree.h" +class ImageTexture; + class EditorVisualProfiler : public VBoxContainer { GDCLASS(EditorVisualProfiler, VBoxContainer); diff --git a/editor/debugger/script_editor_debugger.cpp b/editor/debugger/script_editor_debugger.cpp index 8985387043..2c40f0e120 100644 --- a/editor/debugger/script_editor_debugger.cpp +++ b/editor/debugger/script_editor_debugger.cpp @@ -71,10 +71,12 @@ using CameraOverride = EditorDebuggerNode::CameraOverride; -void ScriptEditorDebugger::_put_msg(String p_message, Array p_data) { +void ScriptEditorDebugger::_put_msg(String p_message, Array p_data, uint64_t p_thread_id) { + ERR_FAIL_COND(p_thread_id == Thread::UNASSIGNED_ID); if (is_session_active()) { Array msg; msg.push_back(p_message); + msg.push_back(p_thread_id); msg.push_back(p_data); peer->put_message(msg); } @@ -98,31 +100,31 @@ void ScriptEditorDebugger::debug_skip_breakpoints() { Array msg; msg.push_back(skip_breakpoints_value); - _put_msg("set_skip_breakpoints", msg); + _put_msg("set_skip_breakpoints", msg, debugging_thread_id != Thread::UNASSIGNED_ID ? debugging_thread_id : Thread::MAIN_ID); } void ScriptEditorDebugger::debug_next() { - ERR_FAIL_COND(!breaked); + ERR_FAIL_COND(!is_breaked()); - _put_msg("next", Array()); + _put_msg("next", Array(), debugging_thread_id); _clear_execution(); } void ScriptEditorDebugger::debug_step() { - ERR_FAIL_COND(!breaked); + ERR_FAIL_COND(!is_breaked()); - _put_msg("step", Array()); + _put_msg("step", Array(), debugging_thread_id); _clear_execution(); } void ScriptEditorDebugger::debug_break() { - ERR_FAIL_COND(breaked); + ERR_FAIL_COND(is_breaked()); _put_msg("break", Array()); } void ScriptEditorDebugger::debug_continue() { - ERR_FAIL_COND(!breaked); + ERR_FAIL_COND(!is_breaked()); // Allow focus stealing only if we actually run this client for security. if (remote_pid && EditorNode::get_singleton()->has_child_process(remote_pid)) { @@ -130,7 +132,7 @@ void ScriptEditorDebugger::debug_continue() { } _clear_execution(); - _put_msg("continue", Array()); + _put_msg("continue", Array(), debugging_thread_id); _put_msg("servers:foreground", Array()); } @@ -299,43 +301,89 @@ Size2 ScriptEditorDebugger::get_minimum_size() const { return ms; } -void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_data) { +void ScriptEditorDebugger::_thread_debug_enter(uint64_t p_thread_id) { + ERR_FAIL_COND(!threads_debugged.has(p_thread_id)); + ThreadDebugged &td = threads_debugged[p_thread_id]; + _set_reason_text(td.error, MESSAGE_ERROR); + emit_signal(SNAME("breaked"), true, td.can_debug, td.error, td.has_stackdump); + if (!td.error.is_empty()) { + tabs->set_current_tab(0); + } + inspector->clear_cache(); // Take a chance to force remote objects update. + _put_msg("get_stack_dump", Array(), p_thread_id); +} + +void ScriptEditorDebugger::_select_thread(int p_index) { + debugging_thread_id = threads->get_item_metadata(threads->get_selected()); + _thread_debug_enter(debugging_thread_id); +} + +void ScriptEditorDebugger::_parse_message(const String &p_msg, uint64_t p_thread_id, const Array &p_data) { emit_signal(SNAME("debug_data"), p_msg, p_data); if (p_msg == "debug_enter") { - _put_msg("get_stack_dump", Array()); - - ERR_FAIL_COND(p_data.size() != 3); - bool can_continue = p_data[0]; - String error = p_data[1]; - bool has_stackdump = p_data[2]; - breaked = true; - can_request_idle_draw = true; - can_debug = can_continue; - _update_buttons_state(); - _set_reason_text(error, MESSAGE_ERROR); - emit_signal(SNAME("breaked"), true, can_continue, error, has_stackdump); - if (is_move_to_foreground()) { - DisplayServer::get_singleton()->window_move_to_foreground(); - } - if (!error.is_empty()) { - tabs->set_current_tab(0); + ERR_FAIL_COND(p_data.size() != 4); + + ThreadDebugged td; + td.name = p_data[3]; + td.error = p_data[1]; + td.can_debug = p_data[0]; + td.has_stackdump = p_data[2]; + td.thread_id = p_thread_id; + static uint32_t order_inc = 0; + td.debug_order = order_inc++; + + threads_debugged.insert(p_thread_id, td); + + if (threads_debugged.size() == 1) { + // First thread that requests debug + debugging_thread_id = p_thread_id; + _thread_debug_enter(p_thread_id); + can_request_idle_draw = true; + if (is_move_to_foreground()) { + DisplayServer::get_singleton()->window_move_to_foreground(); + } + profiler->set_enabled(false, false); + visual_profiler->set_enabled(false); } - profiler->set_enabled(false, false); - visual_profiler->set_enabled(false); - inspector->clear_cache(); // Take a chance to force remote objects update. + _update_buttons_state(); } else if (p_msg == "debug_exit") { - breaked = false; - can_debug = false; - _clear_execution(); - _update_buttons_state(); - _set_reason_text(TTR("Execution resumed."), MESSAGE_SUCCESS); - emit_signal(SNAME("breaked"), false, false, "", false); + threads_debugged.erase(p_thread_id); + if (p_thread_id == debugging_thread_id) { + _clear_execution(); + if (threads_debugged.size() == 0) { + debugging_thread_id = Thread::UNASSIGNED_ID; + } else { + // Find next thread to debug. + uint32_t min_order = 0xFFFFFFFF; + uint64_t next_thread = Thread::UNASSIGNED_ID; + for (KeyValue<uint64_t, ThreadDebugged> T : threads_debugged) { + if (T.value.debug_order < min_order) { + min_order = T.value.debug_order; + next_thread = T.key; + } + } + + debugging_thread_id = next_thread; + } - profiler->set_enabled(true, false); - profiler->disable_seeking(); + if (debugging_thread_id == Thread::UNASSIGNED_ID) { + // Nothing else to debug. + profiler->set_enabled(true, false); + profiler->disable_seeking(); - visual_profiler->set_enabled(true); + visual_profiler->set_enabled(true); + + _set_reason_text(TTR("Execution resumed."), MESSAGE_SUCCESS); + emit_signal(SNAME("breaked"), false, false, "", false); + + _update_buttons_state(); + } else { + _thread_debug_enter(debugging_thread_id); + } + } else { + _update_buttons_state(); + } } else if (p_msg == "set_pid") { ERR_FAIL_COND(p_data.size() < 1); @@ -379,7 +427,6 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da vmem_total->set_tooltip_text(TTR("Bytes:") + " " + itos(total)); vmem_total->set_text(String::humanize_size(total)); - } else if (p_msg == "servers:drawn") { can_request_idle_draw = true; } else if (p_msg == "stack_dump") { @@ -414,11 +461,9 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da inspector->clear_stack_variables(); ERR_FAIL_COND(p_data.size() != 1); emit_signal(SNAME("stack_frame_vars"), p_data[0]); - } else if (p_msg == "stack_frame_var") { inspector->add_stack_variable(p_data); emit_signal(SNAME("stack_frame_var"), p_data); - } else if (p_msg == "output") { ERR_FAIL_COND(p_data.size() != 2); @@ -458,7 +503,6 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da frame_data.write[i] = p_data[i]; } performance_profiler->add_profile_frame(frame_data); - } else if (p_msg == "visual:profile_frame") { ServersDebugger::VisualProfilerFrame frame; frame.deserialize(p_data); @@ -477,7 +521,6 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da } } visual_profiler->add_frame_metric(metric); - } else if (p_msg == "error") { DebuggerMarshalls::OutputError oe; ERR_FAIL_COND_MSG(oe.deserialize(p_data) == false, "Failed to deserialize error message"); @@ -625,13 +668,11 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da } else { error_count++; } - } else if (p_msg == "servers:function_signature") { // Cache a profiler signature. ServersDebugger::ScriptFunctionSignature sig; sig.deserialize(p_data); profiler_signature[sig.id] = sig.name; - } else if (p_msg == "servers:profile_frame" || p_msg == "servers:profile_total") { EditorProfiler::Metric metric; ServersDebugger::ServersProfilerFrame frame; @@ -744,11 +785,9 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da } else { profiler->add_frame_metric(metric, true); } - } else if (p_msg == "request_quit") { emit_signal(SNAME("stop_requested")); _stop_and_notify(); - } else if (p_msg == "performance:profile_names") { Vector<StringName> monitors; monitors.resize(p_data.size()); @@ -757,13 +796,11 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da monitors.set(i, p_data[i]); } performance_profiler->update_monitors(monitors); - } else if (p_msg == "filesystem:update_file") { ERR_FAIL_COND(p_data.size() < 1); if (EditorFileSystem::get_singleton()) { EditorFileSystem::get_singleton()->update_file(p_data[0]); } - } else { int colon_index = p_msg.find_char(':'); ERR_FAIL_COND_MSG(colon_index < 1, "Invalid message received"); @@ -878,7 +915,7 @@ void ScriptEditorDebugger::_notification(int p_what) { msg.push_back(cam->get_far()); _put_msg("scene:override_camera_3D:transform", msg); } - if (breaked && can_request_idle_draw) { + if (is_breaked() && can_request_idle_draw) { _put_msg("servers:draw", Array()); can_request_idle_draw = false; } @@ -888,11 +925,12 @@ void ScriptEditorDebugger::_notification(int p_what) { while (peer.is_valid() && peer->has_message()) { Array arr = peer->get_message(); - if (arr.size() != 2 || arr[0].get_type() != Variant::STRING || arr[1].get_type() != Variant::ARRAY) { + if (arr.size() != 3 || arr[0].get_type() != Variant::STRING || arr[1].get_type() != Variant::INT || arr[2].get_type() != Variant::ARRAY) { _stop_and_notify(); ERR_FAIL_MSG("Invalid message format received from peer"); } - _parse_message(arr[0], arr[1]); + + _parse_message(arr[0], arr[1], arr[2]); if (OS::get_singleton()->get_ticks_msec() > until) { break; @@ -959,8 +997,6 @@ void ScriptEditorDebugger::start(Ref<RemoteDebuggerPeer> p_peer) { performance_profiler->reset(); set_process(true); - breaked = false; - can_debug = true; camera_override = CameraOverride::OVERRIDE_NONE; tabs->set_current_tab(0); @@ -973,13 +1009,35 @@ void ScriptEditorDebugger::_update_buttons_state() { const bool active = is_session_active(); const bool has_editor_tree = active && editor_remote_tree && editor_remote_tree->get_selected(); vmem_refresh->set_disabled(!active); - step->set_disabled(!active || !breaked || !can_debug); - next->set_disabled(!active || !breaked || !can_debug); - copy->set_disabled(!active || !breaked); - docontinue->set_disabled(!active || !breaked); - dobreak->set_disabled(!active || breaked); + step->set_disabled(!active || !is_breaked() || !is_debuggable()); + next->set_disabled(!active || !is_breaked() || !is_debuggable()); + copy->set_disabled(!active || !is_breaked()); + docontinue->set_disabled(!active || !is_breaked()); + dobreak->set_disabled(!active || is_breaked()); le_clear->set_disabled(!active); le_set->set_disabled(!has_editor_tree); + + thread_list_updating = true; + LocalVector<ThreadDebugged *> threadss; + for (KeyValue<uint64_t, ThreadDebugged> &I : threads_debugged) { + threadss.push_back(&I.value); + } + + threadss.sort_custom<ThreadSort>(); + threads->clear(); + int32_t selected_index = -1; + for (uint32_t i = 0; i < threadss.size(); i++) { + if (debugging_thread_id == threadss[i]->thread_id) { + selected_index = i; + } + threads->add_item(threadss[i]->name); + threads->set_item_metadata(threads->get_item_count() - 1, threadss[i]->thread_id); + } + if (selected_index != -1) { + threads->select(selected_index); + } + + thread_list_updating = false; } void ScriptEditorDebugger::_stop_and_notify() { @@ -990,8 +1048,8 @@ void ScriptEditorDebugger::_stop_and_notify() { void ScriptEditorDebugger::stop() { set_process(false); - breaked = false; - can_debug = false; + threads_debugged.clear(); + debugging_thread_id = Thread::UNASSIGNED_ID; remote_pid = 0; _clear_execution(); @@ -1043,7 +1101,7 @@ void ScriptEditorDebugger::_profiler_activate(bool p_enable, int p_type) { } void ScriptEditorDebugger::_profiler_seeked() { - if (breaked) { + if (is_breaked()) { return; } debug_break(); @@ -1067,7 +1125,7 @@ void ScriptEditorDebugger::_export_csv() { } String ScriptEditorDebugger::get_var_value(const String &p_var) const { - if (!breaked) { + if (!is_breaked()) { return String(); } return inspector->get_stack_variable(p_var); @@ -1255,7 +1313,7 @@ bool ScriptEditorDebugger::request_stack_dump(const int &p_frame) { Array msg; msg.push_back(p_frame); - _put_msg("get_stack_frame_vars", msg); + _put_msg("get_stack_frame_vars", msg, debugging_thread_id); return true; } @@ -1407,7 +1465,7 @@ void ScriptEditorDebugger::set_breakpoint(const String &p_path, int p_line, bool msg.push_back(p_path); msg.push_back(p_line); msg.push_back(p_enabled); - _put_msg("breakpoint", msg); + _put_msg("breakpoint", msg, debugging_thread_id != Thread::UNASSIGNED_ID ? debugging_thread_id : Thread::MAIN_ID); TreeItem *path_item = breakpoints_tree->search_item_text(p_path); if (path_item == nullptr) { @@ -1450,7 +1508,7 @@ void ScriptEditorDebugger::set_breakpoint(const String &p_path, int p_line, bool } void ScriptEditorDebugger::reload_scripts() { - _put_msg("reload_scripts", Array()); + _put_msg("reload_scripts", Array(), debugging_thread_id != Thread::UNASSIGNED_ID ? debugging_thread_id : Thread::MAIN_ID); } bool ScriptEditorDebugger::is_skip_breakpoints() { @@ -1804,15 +1862,26 @@ ScriptEditorDebugger::ScriptEditorDebugger() { sc->set_h_size_flags(SIZE_EXPAND_FILL); parent_sc->add_child(sc); + VBoxContainer *stack_vb = memnew(VBoxContainer); + stack_vb->set_h_size_flags(SIZE_EXPAND_FILL); + sc->add_child(stack_vb); + HBoxContainer *thread_hb = memnew(HBoxContainer); + stack_vb->add_child(thread_hb); + thread_hb->add_child(memnew(Label(TTR("Thread:")))); + threads = memnew(OptionButton); + thread_hb->add_child(threads); + threads->set_h_size_flags(SIZE_EXPAND_FILL); + threads->connect("item_selected", callable_mp(this, &ScriptEditorDebugger::_select_thread)); + stack_dump = memnew(Tree); stack_dump->set_allow_reselect(true); stack_dump->set_columns(1); stack_dump->set_column_titles_visible(true); stack_dump->set_column_title(0, TTR("Stack Frames")); - stack_dump->set_h_size_flags(SIZE_EXPAND_FILL); stack_dump->set_hide_root(true); + stack_dump->set_v_size_flags(SIZE_EXPAND_FILL); stack_dump->connect("cell_selected", callable_mp(this, &ScriptEditorDebugger::_stack_dump_frame_selected)); - sc->add_child(stack_dump); + stack_vb->add_child(stack_dump); VBoxContainer *inspector_vbox = memnew(VBoxContainer); inspector_vbox->set_h_size_flags(SIZE_EXPAND_FILL); diff --git a/editor/debugger/script_editor_debugger.h b/editor/debugger/script_editor_debugger.h index 336a113163..7e9a767273 100644 --- a/editor/debugger/script_editor_debugger.h +++ b/editor/debugger/script_editor_debugger.h @@ -138,6 +138,7 @@ private: Tree *stack_dump = nullptr; LineEdit *search = nullptr; + OptionButton *threads = nullptr; EditorDebuggerInspector *inspector = nullptr; SceneDebuggerTree *scene_tree = nullptr; @@ -152,19 +153,39 @@ private: EditorPerformanceProfiler *performance_profiler = nullptr; OS::ProcessID remote_pid = 0; - bool breaked = false; - bool can_debug = false; bool move_to_foreground = true; bool can_request_idle_draw = false; bool live_debug; + uint64_t debugging_thread_id = Thread::UNASSIGNED_ID; + + struct ThreadDebugged { + String name; + String error; + bool can_debug = false; + bool has_stackdump = false; + uint32_t debug_order = 0; + uint64_t thread_id = Thread::UNASSIGNED_ID; // for order + }; + + struct ThreadSort { + bool operator()(const ThreadDebugged *a, const ThreadDebugged *b) const { + return a->debug_order < b->debug_order; + } + }; + + HashMap<uint64_t, ThreadDebugged> threads_debugged; + bool thread_list_updating = false; + + void _select_thread(int p_index); + EditorDebuggerNode::CameraOverride camera_override; void _stack_dump_frame_selected(); void _file_selected(const String &p_file); - void _parse_message(const String &p_msg, const Array &p_data); + void _parse_message(const String &p_msg, uint64_t p_thread_id, const Array &p_data); void _set_reason_text(const String &p_reason, MessageType p_type); void _update_buttons_state(); void _remote_object_selected(ObjectID p_object); @@ -200,7 +221,7 @@ private: void _item_menu_id_pressed(int p_option); void _tab_changed(int p_tab); - void _put_msg(String p_message, Array p_data); + void _put_msg(String p_message, Array p_data, uint64_t p_thread_id = Thread::MAIN_ID); void _export_csv(); void _clear_execution(); @@ -213,6 +234,8 @@ private: String _format_frame_text(const ScriptLanguage::StackInfo *info); + void _thread_debug_enter(uint64_t p_thread_id); + protected: void _notification(int p_what); static void _bind_methods(); @@ -238,9 +261,9 @@ public: void debug_step(); void debug_break(); void debug_continue(); - bool is_breaked() const { return breaked; } - bool is_debuggable() const { return can_debug; } - bool is_session_active() { return peer.is_valid() && peer->is_peer_connected(); }; + bool is_breaked() const { return threads_debugged.size() > 0; } + bool is_debuggable() const { return threads_debugged.size() > 0 && threads_debugged[debugging_thread_id].can_debug; } + bool is_session_active() { return peer.is_valid() && peer->is_peer_connected(); } int get_remote_pid() const { return remote_pid; } bool is_move_to_foreground() const; diff --git a/editor/editor_data.cpp b/editor/editor_data.cpp index af7163eec1..0b2c2bea15 100644 --- a/editor/editor_data.cpp +++ b/editor/editor_data.cpp @@ -1154,15 +1154,6 @@ Ref<Texture2D> EditorData::get_script_icon(const Ref<Script> &p_script) { return ext_icon; } - // Look for the base type in the editor theme. - // This is only relevant for built-in classes. - const Control *gui_base = EditorNode::get_singleton()->get_gui_base(); - if (gui_base && gui_base->has_theme_icon(base_type, SNAME("EditorIcons"))) { - Ref<Texture2D> theme_icon = gui_base->get_theme_icon(base_type, SNAME("EditorIcons")); - _script_icon_cache[p_script] = theme_icon; - return theme_icon; - } - // If no icon found, cache it as null. _script_icon_cache[p_script] = Ref<Texture>(); return nullptr; diff --git a/editor/editor_feature_profile.cpp b/editor/editor_feature_profile.cpp index 7c77fec81a..308bf33da5 100644 --- a/editor/editor_feature_profile.cpp +++ b/editor/editor_feature_profile.cpp @@ -419,13 +419,7 @@ void EditorFeatureProfileManager::_update_profile_list(const String &p_select_pr void EditorFeatureProfileManager::_profile_action(int p_action) { switch (p_action) { case PROFILE_CLEAR: { - EditorSettings::get_singleton()->set("_default_feature_profile", ""); - EditorSettings::get_singleton()->save(); - current_profile = ""; - current.unref(); - - _update_profile_list(); - _emit_current_profile_changed(); + set_current_profile("", false); } break; case PROFILE_SET: { String selected = _get_selected_profile(); @@ -433,13 +427,7 @@ void EditorFeatureProfileManager::_profile_action(int p_action) { if (selected == current_profile) { return; // Nothing to do here. } - EditorSettings::get_singleton()->set("_default_feature_profile", selected); - EditorSettings::get_singleton()->save(); - current_profile = selected; - current = edited; - - _update_profile_list(); - _emit_current_profile_changed(); + set_current_profile(selected, false); } break; case PROFILE_IMPORT: { import_profiles->popup_file_dialog(); @@ -878,11 +866,45 @@ Ref<EditorFeatureProfile> EditorFeatureProfileManager::get_current_profile() { return current; } +String EditorFeatureProfileManager::get_current_profile_name() const { + return current_profile; +} + +void EditorFeatureProfileManager::set_current_profile(const String &p_profile_name, bool p_validate_profile) { + if (p_validate_profile && !p_profile_name.is_empty()) { + // Profile may not exist. + Ref<DirAccess> da = DirAccess::open(EditorPaths::get_singleton()->get_feature_profiles_dir()); + ERR_FAIL_COND_MSG(da.is_null(), "Cannot open directory '" + EditorPaths::get_singleton()->get_feature_profiles_dir() + "'."); + ERR_FAIL_COND_MSG(!da->file_exists(p_profile_name + ".profile"), "Feature profile '" + p_profile_name + "' does not exist."); + + // Change profile selection to emulate the UI interaction. Otherwise, the wrong profile would get activated. + // FIXME: Ideally, _update_selected_profile() should not rely on the user interface state to function properly. + for (int i = 0; i < profile_list->get_item_count(); i++) { + if (profile_list->get_item_metadata(i) == p_profile_name) { + profile_list->select(i); + break; + } + } + _update_selected_profile(); + } + + // Store in editor settings. + EditorSettings::get_singleton()->set("_default_feature_profile", p_profile_name); + EditorSettings::get_singleton()->save(); + + current_profile = p_profile_name; + if (p_profile_name.is_empty()) { + current.unref(); + } else { + current = edited; + } + _update_profile_list(); + _emit_current_profile_changed(); +} + EditorFeatureProfileManager *EditorFeatureProfileManager::singleton = nullptr; void EditorFeatureProfileManager::_bind_methods() { - ClassDB::bind_method("_update_selected_profile", &EditorFeatureProfileManager::_update_selected_profile); - ADD_SIGNAL(MethodInfo("current_feature_profile_changed")); } diff --git a/editor/editor_feature_profile.h b/editor/editor_feature_profile.h index 3f70e03ca8..25ee1c9ba4 100644 --- a/editor/editor_feature_profile.h +++ b/editor/editor_feature_profile.h @@ -177,6 +177,8 @@ protected: public: Ref<EditorFeatureProfile> get_current_profile(); + String get_current_profile_name() const; + void set_current_profile(const String &p_profile_name, bool p_validate_profile); void notify_changed(); static EditorFeatureProfileManager *get_singleton() { return singleton; } diff --git a/editor/editor_help.cpp b/editor/editor_help.cpp index 71fdce149b..c9a95df34e 100644 --- a/editor/editor_help.cpp +++ b/editor/editor_help.cpp @@ -45,37 +45,43 @@ #define CONTRIBUTE_URL vformat("%s/contributing/documentation/updating_the_class_reference.html", VERSION_DOCS_URL) +#ifdef MODULE_MONO_ENABLED +// Sync with the types mentioned in https://docs.godotengine.org/en/stable/tutorials/scripting/c_sharp/c_sharp_differences.html +const Vector<String> classes_with_csharp_differences = { + "@GlobalScope", + "String", + "NodePath", + "Signal", + "Callable", + "RID", + "Basis", + "Transform2D", + "Transform3D", + "Rect2", + "Rect2i", + "AABB", + "Quaternion", + "Projection", + "Color", + "Array", + "Dictionary", + "PackedByteArray", + "PackedColorArray", + "PackedFloat32Array", + "PackedFloat64Array", + "PackedInt32Array", + "PackedInt64Array", + "PackedStringArray", + "PackedVector2Array", + "PackedVector3Array", + "Variant", +}; +#endif + // TODO: this is sometimes used directly as doc->something, other times as EditorHelp::get_doc_data(), which is thread-safe. // Might this be a problem? DocTools *EditorHelp::doc = nullptr; -class DocCache : public Resource { - GDCLASS(DocCache, Resource); - RES_BASE_EXTENSION("doc_cache"); - - String version_hash; - Array classes; - -protected: - static void _bind_methods() { - ClassDB::bind_method(D_METHOD("set_version_hash", "version_hash"), &DocCache::set_version_hash); - ClassDB::bind_method(D_METHOD("get_version_hash"), &DocCache::get_version_hash); - - ClassDB::bind_method(D_METHOD("set_classes", "classes"), &DocCache::set_classes); - ClassDB::bind_method(D_METHOD("get_classes"), &DocCache::get_classes); - - ADD_PROPERTY(PropertyInfo(Variant::STRING, "version_hash"), "set_version_hash", "get_version_hash"); - ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "classes"), "set_classes", "get_classes"); - } - -public: - String get_version_hash() const { return version_hash; } - void set_version_hash(const String &p_version_hash) { version_hash = p_version_hash; } - - Array get_classes() const { return classes; } - void set_classes(const Array &p_classes) { classes = p_classes; } -}; - static bool _attempt_doc_load(const String &p_class) { // Docgen always happens in the outer-most class: it also generates docs for inner classes. String outer_class = p_class.get_slice(".", 0); @@ -888,10 +894,26 @@ void EditorHelp::_update_doc() { class_desc->append_text(TTR("There is currently no description for this class. Please help us by [color=$color][url=$url]contributing one[/url][/color]!").replace("$url", CONTRIBUTE_URL).replace("$color", link_color_text)); } + class_desc->add_newline(); + class_desc->add_newline(); + } + +#ifdef MODULE_MONO_ENABLED + if (classes_with_csharp_differences.has(cd.name)) { + const String &csharp_differences_url = vformat("%s/tutorials/scripting/c_sharp/c_sharp_differences.html", VERSION_DOCS_URL); + + class_desc->push_color(theme_cache.text_color); + _push_normal_font(); + class_desc->push_indent(1); + _add_text("[b]" + TTR("Note:") + "[/b] " + vformat(TTR("There are notable differences when using this API with C#. See [url=%s]C# API differences to GDScript[/url] for more information."), csharp_differences_url)); + class_desc->pop(); + _pop_normal_font(); class_desc->pop(); + class_desc->add_newline(); class_desc->add_newline(); } +#endif // Online tutorials if (cd.tutorials.size()) { @@ -2271,21 +2293,23 @@ void EditorHelp::_wait_for_thread() { } String EditorHelp::get_cache_full_path() { - return EditorPaths::get_singleton()->get_cache_dir().path_join("editor.doc_cache"); + return EditorPaths::get_singleton()->get_cache_dir().path_join("editor_doc_cache.res"); } static bool first_attempt = true; static String _compute_doc_version_hash() { - return vformat("%d/%d/%s", ClassDB::get_api_hash(ClassDB::API_CORE), ClassDB::get_api_hash(ClassDB::API_EDITOR), _doc_data_hash); + uint32_t version_hash = Engine::get_singleton()->get_version_info().hash(); + return vformat("%d/%d/%d/%s", version_hash, ClassDB::get_api_hash(ClassDB::API_CORE), ClassDB::get_api_hash(ClassDB::API_EDITOR), _doc_data_hash); } void EditorHelp::_load_doc_thread(void *p_udata) { DEV_ASSERT(first_attempt); - Ref<DocCache> cache_res = ResourceLoader::load(get_cache_full_path()); - if (cache_res.is_valid() && cache_res->get_version_hash() == _compute_doc_version_hash()) { - for (int i = 0; i < cache_res->get_classes().size(); i++) { - doc->add_doc(DocData::ClassDoc::from_dict(cache_res->get_classes()[i])); + Ref<Resource> cache_res = ResourceLoader::load(get_cache_full_path()); + if (cache_res.is_valid() && cache_res->get_meta("version_hash", "") == _compute_doc_version_hash()) { + Array classes = cache_res->get_meta("classes", Array()); + for (int i = 0; i < classes.size(); i++) { + doc->add_doc(DocData::ClassDoc::from_dict(classes[i])); } } else { // We have to go back to the main thread to start from scratch. @@ -2299,14 +2323,14 @@ void EditorHelp::_gen_doc_thread(void *p_udata) { compdoc.load_compressed(_doc_data_compressed, _doc_data_compressed_size, _doc_data_uncompressed_size); doc->merge_from(compdoc); // Ensure all is up to date. - Ref<DocCache> cache_res; + Ref<Resource> cache_res; cache_res.instantiate(); - cache_res->set_version_hash(_compute_doc_version_hash()); + cache_res->set_meta("version_hash", _compute_doc_version_hash()); Array classes; for (const KeyValue<String, DocData::ClassDoc> &E : doc->class_list) { classes.push_back(DocData::ClassDoc::to_dict(E.value)); } - cache_res->set_classes(classes); + cache_res->set_meta("classes", classes); Error err = ResourceSaver::save(cache_res, get_cache_full_path(), ResourceSaver::FLAG_COMPRESS); if (err) { ERR_PRINT("Cannot save editor help cache (" + get_cache_full_path() + ")."); @@ -2316,9 +2340,6 @@ void EditorHelp::_gen_doc_thread(void *p_udata) { static bool doc_gen_use_threads = true; void EditorHelp::generate_doc(bool p_use_cache) { - // Temporarily disable use of cache for pre-RC stabilization. - p_use_cache = false; - OS::get_singleton()->benchmark_begin_measure("EditorHelp::generate_doc"); if (doc_gen_use_threads) { // In case not the first attempt. diff --git a/editor/editor_inspector.cpp b/editor/editor_inspector.cpp index 367373dc17..d95b1de365 100644 --- a/editor/editor_inspector.cpp +++ b/editor/editor_inspector.cpp @@ -45,6 +45,7 @@ #include "scene/gui/texture_rect.h" #include "scene/property_utils.h" #include "scene/resources/packed_scene.h" +#include "scene/resources/style_box_flat.h" bool EditorInspector::_property_path_matches(const String &p_property_path, const String &p_filter, EditorPropertyNameProcessor::Style p_style) { if (p_property_path.findn(p_filter) != -1) { diff --git a/editor/editor_inspector.h b/editor/editor_inspector.h index a5737b7dc6..9a4c4f7f99 100644 --- a/editor/editor_inspector.h +++ b/editor/editor_inspector.h @@ -44,6 +44,7 @@ class OptionButton; class PanelContainer; class PopupMenu; class SpinBox; +class StyleBoxFlat; class TextureRect; class EditorPropertyRevert { diff --git a/editor/editor_interface.cpp b/editor/editor_interface.cpp index d9d9dc01c0..d0d695f2f8 100644 --- a/editor/editor_interface.cpp +++ b/editor/editor_interface.cpp @@ -31,6 +31,7 @@ #include "editor_interface.h" #include "editor/editor_command_palette.h" +#include "editor/editor_feature_profile.h" #include "editor/editor_node.h" #include "editor/editor_paths.h" #include "editor/editor_resource_preview.h" @@ -239,6 +240,14 @@ void EditorInterface::popup_dialog_centered_clamped(Window *p_dialog, const Size p_dialog->popup_exclusive_centered_clamped(EditorNode::get_singleton(), p_size, p_fallback_ratio); } +String EditorInterface::get_current_feature_profile() const { + return EditorFeatureProfileManager::get_singleton()->get_current_profile_name(); +} + +void EditorInterface::set_current_feature_profile(const String &p_profile_name) { + EditorFeatureProfileManager::get_singleton()->set_current_profile(p_profile_name, true); +} + // Editor docks. FileSystemDock *EditorInterface::get_file_system_dock() const { @@ -407,6 +416,9 @@ void EditorInterface::_bind_methods() { ClassDB::bind_method(D_METHOD("popup_dialog_centered_ratio", "dialog", "ratio"), &EditorInterface::popup_dialog_centered_ratio, DEFVAL(0.8)); ClassDB::bind_method(D_METHOD("popup_dialog_centered_clamped", "dialog", "minsize", "fallback_ratio"), &EditorInterface::popup_dialog_centered_clamped, DEFVAL(Size2i()), DEFVAL(0.75)); + ClassDB::bind_method(D_METHOD("get_current_feature_profile"), &EditorInterface::get_current_feature_profile); + ClassDB::bind_method(D_METHOD("set_current_feature_profile", "profile_name"), &EditorInterface::set_current_feature_profile); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "distraction_free_mode"), "set_distraction_free_mode", "is_distraction_free_mode_enabled"); // Editor docks. diff --git a/editor/editor_interface.h b/editor/editor_interface.h index f7e8cf8d4c..ac31ce4dfb 100644 --- a/editor/editor_interface.h +++ b/editor/editor_interface.h @@ -104,6 +104,9 @@ public: void popup_dialog_centered_ratio(Window *p_dialog, float p_ratio = 0.8); void popup_dialog_centered_clamped(Window *p_dialog, const Size2i &p_size = Size2i(), float p_fallback_ratio = 0.75); + String get_current_feature_profile() const; + void set_current_feature_profile(const String &p_profile_name); + // Editor docks. FileSystemDock *get_file_system_dock() const; diff --git a/editor/editor_log.cpp b/editor/editor_log.cpp index 0fd9d64602..1bc9f00f08 100644 --- a/editor/editor_log.cpp +++ b/editor/editor_log.cpp @@ -338,13 +338,7 @@ void EditorLog::_add_log_line(LogMessage &p_message, bool p_replace_previous) { } else { log->add_text(p_message.text); } - - // Need to use pop() to exit out of the RichTextLabels current "push" stack. - // We only "push" in the above switch when message type != STD and RICH, so only pop when that is the case. - if (p_message.type != MSG_TYPE_STD && p_message.type != MSG_TYPE_STD_RICH) { - log->pop(); - } - + log->pop_all(); // Pop all unclosed tags. log->add_newline(); if (p_replace_previous) { diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 358356adf5..0e4a6eeb3a 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -60,7 +60,9 @@ #include "scene/gui/tab_container.h" #include "scene/main/window.h" #include "scene/property_utils.h" +#include "scene/resources/image_texture.h" #include "scene/resources/packed_scene.h" +#include "scene/resources/portable_compressed_texture.h" #include "servers/display_server.h" #include "servers/navigation_server_3d.h" #include "servers/physics_server_2d.h" @@ -1982,6 +1984,9 @@ void EditorNode::_dialog_action(String p_file) { if (scene_idx != -1) { _discard_changes(); + } else { + // Update the path of the edited scene to ensure later do/undo action history matches. + editor_data.set_scene_path(editor_data.get_edited_scene(), p_file); } } @@ -2775,6 +2780,11 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) { case FILE_QUIT: case RUN_PROJECT_MANAGER: case RELOAD_CURRENT_PROJECT: { + if (p_confirmed && plugin_to_save) { + plugin_to_save->save_external_data(); + p_confirmed = false; + } + if (!p_confirmed) { bool save_each = EDITOR_GET("interface/editor/save_each_scene_on_quit"); if (_next_unsaved_scene(!save_each) == -1) { @@ -2791,6 +2801,28 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) { break; } + plugin_to_save = nullptr; + for (int i = 0; i < editor_data.get_editor_plugin_count(); i++) { + const String unsaved_status = editor_data.get_editor_plugin(i)->get_unsaved_status(); + if (!unsaved_status.is_empty()) { + if (p_option == RELOAD_CURRENT_PROJECT) { + save_confirmation->set_ok_button_text(TTR("Save & Reload")); + save_confirmation->set_text(unsaved_status); + } else { + save_confirmation->set_ok_button_text(TTR("Save & Quit")); + save_confirmation->set_text(unsaved_status); + } + save_confirmation->reset_size(); + save_confirmation->popup_centered(); + plugin_to_save = editor_data.get_editor_plugin(i); + break; + } + } + + if (plugin_to_save) { + break; + } + _discard_changes(); break; } @@ -3029,13 +3061,21 @@ int EditorNode::_next_unsaved_scene(bool p_valid_filename, int p_start) { if (!editor_data.get_edited_scene_root(i)) { continue; } + + String scene_filename = editor_data.get_edited_scene_root(i)->get_scene_file_path(); + if (p_valid_filename && scene_filename.is_empty()) { + continue; + } + bool unsaved = EditorUndoRedoManager::get_singleton()->is_history_unsaved(editor_data.get_scene_history_id(i)); if (unsaved) { - String scene_filename = editor_data.get_edited_scene_root(i)->get_scene_file_path(); - if (p_valid_filename && scene_filename.is_empty()) { - continue; - } return i; + } else { + for (int j = 0; j < editor_data.get_editor_plugin_count(); j++) { + if (!editor_data.get_editor_plugin(j)->get_unsaved_status(scene_filename).is_empty()) { + return i; + } + } } } return -1; @@ -3190,7 +3230,7 @@ void EditorNode::add_editor_plugin(EditorPlugin *p_editor, bool p_config_changed if (icon.is_valid()) { tb->set_icon(icon); // Make sure the control is updated if the icon is reimported. - icon->connect("changed", callable_mp((Control *)tb, &Control::update_minimum_size)); + icon->connect_changed(callable_mp((Control *)tb, &Control::update_minimum_size)); } else if (singleton->gui_base->has_theme_icon(p_editor->get_name(), SNAME("EditorIcons"))) { tb->set_icon(singleton->gui_base->get_theme_icon(p_editor->get_name(), SNAME("EditorIcons"))); } @@ -3326,6 +3366,11 @@ void EditorNode::set_addon_plugin_enabled(const String &p_addon, bool p_enabled, return; } + String plugin_version; + if (cf->has_section_key("plugin", "version")) { + plugin_version = cf->get_value("plugin", "version"); + } + if (!cf->has_section_key("plugin", "script")) { show_warning(vformat(TTR("Unable to find script field for addon plugin at: '%s'."), addon_path)); return; @@ -3371,6 +3416,7 @@ void EditorNode::set_addon_plugin_enabled(const String &p_addon, bool p_enabled, EditorPlugin *ep = memnew(EditorPlugin); ep->set_script(scr); + ep->set_plugin_version(plugin_version); addon_name_to_plugin[addon_path] = ep; add_editor_plugin(ep, p_config_changed); @@ -4234,7 +4280,7 @@ void EditorNode::_pick_main_scene_custom_action(const String &p_custom_action_na } } -Ref<Texture2D> EditorNode::_get_class_or_script_icon(const String &p_class, const Ref<Script> &p_script, const String &p_fallback) { +Ref<Texture2D> EditorNode::_get_class_or_script_icon(const String &p_class, const Ref<Script> &p_script, const String &p_fallback, bool p_fallback_script_to_theme) { ERR_FAIL_COND_V_MSG(p_class.is_empty(), nullptr, "Class name cannot be empty."); EditorData &ed = EditorNode::get_editor_data(); @@ -4244,6 +4290,16 @@ Ref<Texture2D> EditorNode::_get_class_or_script_icon(const String &p_class, cons if (script_icon.is_valid()) { return script_icon; } + + if (p_fallback_script_to_theme) { + // Look for the base type in the editor theme. + // This is only relevant for built-in classes. + String base_type; + p_script->get_language()->get_global_class_name(p_script->get_path(), &base_type); + if (gui_base && gui_base->has_theme_icon(base_type, SNAME("EditorIcons"))) { + return gui_base->get_theme_icon(base_type, SNAME("EditorIcons")); + } + } } // Script was not valid or didn't yield any useful values, try the class name @@ -4296,7 +4352,7 @@ Ref<Texture2D> EditorNode::get_class_icon(const String &p_class, const String &p scr = EditorNode::get_editor_data().script_class_load_script(p_class); } - return _get_class_or_script_icon(p_class, scr, p_fallback); + return _get_class_or_script_icon(p_class, scr, p_fallback, true); } bool EditorNode::is_object_of_custom_type(const Object *p_object, const StringName &p_class) { @@ -4376,6 +4432,9 @@ String EditorNode::_get_system_info() const { godot_version += " " + hash; } +#ifdef LINUXBSD_ENABLED + const String display_server = OS::get_singleton()->get_environment("XDG_SESSION_TYPE").capitalize().replace(" ", ""); // `replace` is necessary, because `capitalize` introduces a whitespace between "x" and "11". +#endif // LINUXBSD_ENABLED String driver_name = GLOBAL_GET("rendering/rendering_device/driver"); String rendering_method = GLOBAL_GET("rendering/renderer/rendering_method"); @@ -4407,17 +4466,18 @@ String EditorNode::_get_system_info() const { const int processor_count = OS::get_singleton()->get_processor_count(); // Prettify - if (driver_name == "vulkan") { - driver_name = "Vulkan"; - } else if (driver_name == "opengl3") { - driver_name = "GLES3"; - } if (rendering_method == "forward_plus") { rendering_method = "Forward+"; } else if (rendering_method == "mobile") { rendering_method = "Mobile"; } else if (rendering_method == "gl_compatibility") { rendering_method = "Compatibility"; + driver_name = GLOBAL_GET("rendering/gl_compatibility/driver"); + } + if (driver_name == "vulkan") { + driver_name = "Vulkan"; + } else if (driver_name == "opengl3") { + driver_name = "GLES3"; } // Join info. @@ -4428,6 +4488,11 @@ String EditorNode::_get_system_info() const { } else { info.push_back(distribution_name); } +#ifdef LINUXBSD_ENABLED + if (!display_server.is_empty()) { + info.push_back(display_server); + } +#endif // LINUXBSD_ENABLED info.push_back(vformat("%s (%s)", driver_name, rendering_method)); String graphics; @@ -5545,19 +5610,36 @@ void EditorNode::_scene_tab_closed(int p_tab, int p_option) { return; } - bool unsaved = EditorUndoRedoManager::get_singleton()->is_history_unsaved(editor_data.get_scene_history_id(p_tab)); - if (unsaved) { + String scene_filename = scene->get_scene_file_path(); + String unsaved_message; + + if (EditorUndoRedoManager::get_singleton()->is_history_unsaved(editor_data.get_scene_history_id(p_tab))) { + if (scene_filename.is_empty()) { + unsaved_message = TTR("This scene was never saved."); + } else { + unsaved_message = vformat(TTR("Scene \"%s\" has unsaved changes."), scene_filename); + } + } else { + // Check if any plugin has unsaved changes in that scene. + for (int i = 0; i < editor_data.get_editor_plugin_count(); i++) { + unsaved_message = editor_data.get_editor_plugin(i)->get_unsaved_status(scene_filename); + if (!unsaved_message.is_empty()) { + break; + } + } + } + + if (!unsaved_message.is_empty()) { if (get_current_tab() != p_tab) { set_current_scene(p_tab); } - String scene_filename = scene->get_scene_file_path(); if (current_menu_option == RELOAD_CURRENT_PROJECT) { save_confirmation->set_ok_button_text(TTR("Save & Reload")); - save_confirmation->set_text(vformat(TTR("Save changes to '%s' before reloading?"), !scene_filename.is_empty() ? scene_filename : "unsaved scene")); + save_confirmation->set_text(unsaved_message + "\n\n" + TTR("Save before reloading?")); } else { save_confirmation->set_ok_button_text(TTR("Save & Close")); - save_confirmation->set_text(vformat(TTR("Save changes to '%s' before closing?"), !scene_filename.is_empty() ? scene_filename : "unsaved scene")); + save_confirmation->set_text(unsaved_message + "\n\n" + TTR("Save before closing?")); } save_confirmation->reset_size(); save_confirmation->popup_centered(); @@ -6740,7 +6822,8 @@ EditorNode::EditorNode() { // No scripting by default if in editor. ScriptServer::set_scripting_enabled(false); - EditorHelp::generate_doc(); // Before any editor classes are created. + EditorSettings::ensure_class_registered(); + EditorHelp::generate_doc(); SceneState::set_disable_placeholders(true); ResourceLoader::clear_translation_remaps(); // Using no remaps if in editor. ResourceLoader::clear_path_remaps(); @@ -7127,7 +7210,7 @@ EditorNode::EditorNode() { dock_select->set_v_size_flags(Control::SIZE_EXPAND_FILL); dock_vb->add_child(dock_select); - if (!SceneTree::get_singleton()->get_root()->is_embedding_subwindows() && EDITOR_GET("interface/multi_window/enable")) { + if (!SceneTree::get_singleton()->get_root()->is_embedding_subwindows() && !EDITOR_GET("interface/editor/single_window_mode") && EDITOR_GET("interface/multi_window/enable")) { dock_float = memnew(Button); dock_float->set_icon(theme->get_icon("MakeFloating", "EditorIcons")); dock_float->set_text(TTR("Make Floating")); @@ -8117,6 +8200,9 @@ EditorNode::~EditorNode() { memdelete(progress_hb); EditorSettings::destroy(); + + GDExtensionEditorPlugins::editor_node_add_plugin = nullptr; + GDExtensionEditorPlugins::editor_node_remove_plugin = nullptr; } /* diff --git a/editor/editor_node.h b/editor/editor_node.h index 65f85a76c9..86b4847e5b 100644 --- a/editor/editor_node.h +++ b/editor/editor_node.h @@ -382,6 +382,7 @@ private: AcceptDialog *save_accept = nullptr; EditorAbout *about = nullptr; AcceptDialog *warning = nullptr; + EditorPlugin *plugin_to_save = nullptr; int overridden_default_layout = -1; Ref<ConfigFile> default_layout; @@ -687,7 +688,7 @@ private: void _feature_profile_changed(); bool _is_class_editor_disabled_by_feature_profile(const StringName &p_class); - Ref<Texture2D> _get_class_or_script_icon(const String &p_class, const Ref<Script> &p_script, const String &p_fallback = "Object"); + Ref<Texture2D> _get_class_or_script_icon(const String &p_class, const Ref<Script> &p_script, const String &p_fallback = "Object", bool p_fallback_script_to_theme = false); void _pick_main_scene_custom_action(const String &p_custom_action_name); diff --git a/editor/editor_plugin.cpp b/editor/editor_plugin.cpp index 4232eacd76..2d4c07b263 100644 --- a/editor/editor_plugin.cpp +++ b/editor/editor_plugin.cpp @@ -51,6 +51,7 @@ #include "editor/scene_tree_dock.h" #include "scene/3d/camera_3d.h" #include "scene/gui/popup_menu.h" +#include "scene/resources/image_texture.h" #include "servers/rendering_server.h" void EditorPlugin::add_custom_type(const String &p_type, const String &p_base, const Ref<Script> &p_script, const Ref<Texture2D> &p_icon) { @@ -306,6 +307,14 @@ const Ref<Texture2D> EditorPlugin::get_icon() const { return icon; } +String EditorPlugin::get_plugin_version() const { + return plugin_version; +} + +void EditorPlugin::set_plugin_version(const String &p_version) { + plugin_version = p_version; +} + bool EditorPlugin::has_main_screen() const { bool success = false; GDVIRTUAL_CALL(_has_main_screen, success); @@ -340,7 +349,12 @@ void EditorPlugin::clear() { GDVIRTUAL_CALL(_clear); } -// if editor references external resources/scenes, save them +String EditorPlugin::get_unsaved_status(const String &p_for_scene) const { + String ret; + GDVIRTUAL_CALL(_get_unsaved_status, p_for_scene, ret); + return ret; +} + void EditorPlugin::save_external_data() { GDVIRTUAL_CALL(_save_external_data); } @@ -577,6 +591,7 @@ void EditorPlugin::_bind_methods() { ClassDB::bind_method(D_METHOD("get_script_create_dialog"), &EditorPlugin::get_script_create_dialog); ClassDB::bind_method(D_METHOD("add_debugger_plugin", "script"), &EditorPlugin::add_debugger_plugin); ClassDB::bind_method(D_METHOD("remove_debugger_plugin", "script"), &EditorPlugin::remove_debugger_plugin); + ClassDB::bind_method(D_METHOD("get_plugin_version"), &EditorPlugin::get_plugin_version); GDVIRTUAL_BIND(_forward_canvas_gui_input, "event"); GDVIRTUAL_BIND(_forward_canvas_draw_over_viewport, "viewport_control"); @@ -593,6 +608,7 @@ void EditorPlugin::_bind_methods() { GDVIRTUAL_BIND(_get_state); GDVIRTUAL_BIND(_set_state, "state"); GDVIRTUAL_BIND(_clear); + GDVIRTUAL_BIND(_get_unsaved_status, "for_scene"); GDVIRTUAL_BIND(_save_external_data); GDVIRTUAL_BIND(_apply_changes); GDVIRTUAL_BIND(_get_breakpoints); diff --git a/editor/editor_plugin.h b/editor/editor_plugin.h index 69789a4d4f..7dcf62144d 100644 --- a/editor/editor_plugin.h +++ b/editor/editor_plugin.h @@ -61,6 +61,7 @@ class EditorPlugin : public Node { bool force_draw_over_forwarding_enabled = false; String last_main_screen_name; + String plugin_version; void _editor_project_settings_changed(); @@ -88,6 +89,7 @@ protected: GDVIRTUAL0RC(Dictionary, _get_state) GDVIRTUAL1(_set_state, Dictionary) GDVIRTUAL0(_clear) + GDVIRTUAL1RC(String, _get_unsaved_status, String) GDVIRTUAL0(_save_external_data) GDVIRTUAL0(_apply_changes) GDVIRTUAL0RC(Vector<String>, _get_breakpoints) @@ -167,6 +169,8 @@ public: virtual String get_name() const; virtual const Ref<Texture2D> get_icon() const; + virtual String get_plugin_version() const; + virtual void set_plugin_version(const String &p_version); virtual bool has_main_screen() const; virtual void make_visible(bool p_visible); virtual void selected_notify() {} //notify that it was raised by the user, not the editor @@ -175,6 +179,7 @@ public: virtual Dictionary get_state() const; //save editor state so it can't be reloaded when reloading scene virtual void set_state(const Dictionary &p_state); //restore editor state (likely was saved with the scene) virtual void clear(); // clear any temporary data in the editor, reset it (likely new scene or load another scene) + virtual String get_unsaved_status(const String &p_for_scene = "") const; virtual void save_external_data(); // if editor references external resources/scenes, save them virtual void apply_changes(); // if changes are pending in editor, apply them virtual void get_breakpoints(List<String> *p_breakpoints); diff --git a/editor/editor_properties.cpp b/editor/editor_properties.cpp index 875351cacd..77d6ec9ab2 100644 --- a/editor/editor_properties.cpp +++ b/editor/editor_properties.cpp @@ -772,7 +772,7 @@ void EditorPropertyFlags::setup(const Vector<String> &p_options) { const int flag_index = flags.size(); // Index of the next element (added by the code below). // Value for a flag can be explicitly overridden. - Vector<String> text_split = p_options[i].split(":"); + Vector<String> text_split = option.split(":"); if (text_split.size() != 1) { current_val = text_split[1].to_int(); } else { @@ -782,7 +782,7 @@ void EditorPropertyFlags::setup(const Vector<String> &p_options) { // Create a CheckBox for the current flag. CheckBox *cb = memnew(CheckBox); - cb->set_text(option); + cb->set_text(text_split[0]); cb->set_clip_text(true); cb->connect("pressed", callable_mp(this, &EditorPropertyFlags::_flag_toggled).bind(flag_index)); add_focusable(cb); diff --git a/editor/editor_resource_picker.cpp b/editor/editor_resource_picker.cpp index 8920ad10dc..ea7e1549f5 100644 --- a/editor/editor_resource_picker.cpp +++ b/editor/editor_resource_picker.cpp @@ -41,6 +41,10 @@ #include "editor/plugins/editor_resource_conversion_plugin.h" #include "editor/plugins/script_editor_plugin.h" #include "editor/scene_tree_dock.h" +#include "scene/gui/button.h" +#include "scene/gui/texture_rect.h" +#include "scene/resources/gradient_texture.h" +#include "scene/resources/image_texture.h" void EditorResourcePicker::_update_resource() { String resource_path; @@ -218,7 +222,7 @@ void EditorResourcePicker::_update_menu_items() { edited_resource->get_property_list(&property_list); bool has_subresources = false; for (PropertyInfo &p : property_list) { - if ((p.type == Variant::OBJECT) && (p.hint == PROPERTY_HINT_RESOURCE_TYPE) && (p.name != "script")) { + if ((p.type == Variant::OBJECT) && (p.hint == PROPERTY_HINT_RESOURCE_TYPE) && (p.name != "script") && ((Object *)edited_resource->get(p.name) != nullptr)) { has_subresources = true; break; } @@ -350,7 +354,7 @@ void EditorResourcePicker::_edit_menu_cbk(int p_which) { } Ref<Resource> unique_resource = edited_resource->duplicate(); - ERR_FAIL_COND(unique_resource.is_null()); + ERR_FAIL_COND(unique_resource.is_null()); // duplicate() may fail. edited_resource = unique_resource; emit_signal(SNAME("resource_changed"), edited_resource); @@ -362,12 +366,30 @@ void EditorResourcePicker::_edit_menu_cbk(int p_which) { return; } - Ref<Resource> unique_resource = edited_resource->duplicate(true); - ERR_FAIL_COND(unique_resource.is_null()); + if (!duplicate_resources_dialog) { + duplicate_resources_dialog = memnew(ConfirmationDialog); + add_child(duplicate_resources_dialog); + duplicate_resources_dialog->set_title(TTR("Make Unique (Recursive)")); + duplicate_resources_dialog->connect("confirmed", callable_mp(this, &EditorResourcePicker::_duplicate_selected_resources)); - edited_resource = unique_resource; - emit_signal(SNAME("resource_changed"), edited_resource); - _update_resource(); + VBoxContainer *vb = memnew(VBoxContainer); + duplicate_resources_dialog->add_child(vb); + + Label *label = memnew(Label(TTR("Select resources to make unique:"))); + vb->add_child(label); + + duplicate_resources_tree = memnew(Tree); + vb->add_child(duplicate_resources_tree); + duplicate_resources_tree->set_columns(2); + duplicate_resources_tree->set_v_size_flags(SIZE_EXPAND_FILL); + } + + duplicate_resources_tree->clear(); + TreeItem *root = duplicate_resources_tree->create_item(); + _gather_resources_to_duplicate(edited_resource, root); + + duplicate_resources_dialog->reset_size(); + duplicate_resources_dialog->popup_centered(Vector2(500, 400) * EDSCALE); } break; case OBJ_MENU_SAVE: { @@ -808,6 +830,11 @@ void EditorResourcePicker::_notification(int p_what) { } } +void EditorResourcePicker::set_assign_button_min_size(const Size2i &p_size) { + assign_button_min_size = p_size; + assign_button->set_custom_minimum_size(assign_button_min_size); +} + void EditorResourcePicker::set_base_type(const String &p_base_type) { base_type = p_base_type; @@ -920,6 +947,89 @@ void EditorResourcePicker::_ensure_resource_menu() { edit_menu->connect("popup_hide", callable_mp((BaseButton *)edit_button, &BaseButton::set_pressed).bind(false)); } +void EditorResourcePicker::_gather_resources_to_duplicate(const Ref<Resource> p_resource, TreeItem *p_item, const String &p_property_name) const { + p_item->set_cell_mode(0, TreeItem::CELL_MODE_CHECK); + + String res_name = p_resource->get_name(); + if (res_name.is_empty() && !p_resource->is_built_in()) { + res_name = p_resource->get_path().get_file(); + } + + if (res_name.is_empty()) { + p_item->set_text(0, p_resource->get_class()); + } else { + p_item->set_text(0, vformat("%s (%s)", p_resource->get_class(), res_name)); + } + + p_item->set_icon(0, EditorNode::get_singleton()->get_object_icon(p_resource.ptr())); + p_item->set_editable(0, true); + + Array meta; + meta.append(p_resource); + p_item->set_metadata(0, meta); + + if (!p_property_name.is_empty()) { + p_item->set_text(1, p_property_name); + } + + static Vector<String> unique_exceptions = { "Image", "Shader", "Mesh", "FontFile" }; + if (!unique_exceptions.has(p_resource->get_class())) { + // Automatically select resource, unless it's something that shouldn't be duplicated. + p_item->set_checked(0, true); + } + + List<PropertyInfo> plist; + p_resource->get_property_list(&plist); + + for (const PropertyInfo &E : plist) { + if (!(E.usage & PROPERTY_USAGE_STORAGE) || E.type != Variant::OBJECT || E.hint != PROPERTY_HINT_RESOURCE_TYPE) { + continue; + } + + Ref<Resource> res = p_resource->get(E.name); + if (res.is_null()) { + continue; + } + + TreeItem *child = p_item->create_child(); + _gather_resources_to_duplicate(res, child, E.name); + + meta = child->get_metadata(0); + // Remember property name. + meta.append(E.name); + + if ((E.usage & PROPERTY_USAGE_NEVER_DUPLICATE)) { + // The resource can't be duplicated, but make it appear on the list anyway. + child->set_checked(0, false); + child->set_editable(0, false); + } + } +} + +void EditorResourcePicker::_duplicate_selected_resources() { + for (TreeItem *item = duplicate_resources_tree->get_root(); item; item = item->get_next_in_tree()) { + if (!item->is_checked(0)) { + continue; + } + + Array meta = item->get_metadata(0); + Ref<Resource> res = meta[0]; + Ref<Resource> unique_resource = res->duplicate(); + ERR_FAIL_COND(unique_resource.is_null()); // duplicate() may fail. + meta[0] = unique_resource; + + if (meta.size() == 1) { // Root. + edited_resource = unique_resource; + emit_signal(SNAME("resource_changed"), edited_resource); + _update_resource(); + } else { + Array parent_meta = item->get_parent()->get_metadata(0); + Ref<Resource> parent = parent_meta[0]; + parent->set(meta[1], unique_resource); + } + } +} + EditorResourcePicker::EditorResourcePicker(bool p_hide_assign_button_controls) { assign_button = memnew(Button); assign_button->set_flat(true); diff --git a/editor/editor_resource_picker.h b/editor/editor_resource_picker.h index a302e24957..856ef974d3 100644 --- a/editor/editor_resource_picker.h +++ b/editor/editor_resource_picker.h @@ -32,12 +32,15 @@ #define EDITOR_RESOURCE_PICKER_H #include "scene/gui/box_container.h" -#include "scene/gui/button.h" -#include "scene/gui/popup_menu.h" -#include "scene/gui/texture_rect.h" +class Button; +class ConfirmationDialog; class EditorFileDialog; class EditorQuickOpen; +class PopupMenu; +class TextureRect; +class Tree; +class TreeItem; class EditorResourcePicker : public HBoxContainer { GDCLASS(EditorResourcePicker, HBoxContainer); @@ -56,6 +59,9 @@ class EditorResourcePicker : public HBoxContainer { EditorFileDialog *file_dialog = nullptr; EditorQuickOpen *quick_open = nullptr; + ConfirmationDialog *duplicate_resources_dialog = nullptr; + Tree *duplicate_resources_tree = nullptr; + Size2i assign_button_min_size = Size2i(1, 1); enum MenuOption { @@ -99,6 +105,8 @@ class EditorResourcePicker : public HBoxContainer { void drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from); void _ensure_resource_menu(); + void _gather_resources_to_duplicate(const Ref<Resource> p_resource, TreeItem *p_item, const String &p_property_name = "") const; + void _duplicate_selected_resources(); protected: virtual void _update_resource(); @@ -107,10 +115,7 @@ protected: static void _bind_methods(); void _notification(int p_what); - void set_assign_button_min_size(const Size2i &p_size) { - assign_button_min_size = p_size; - assign_button->set_custom_minimum_size(assign_button_min_size); - } + void set_assign_button_min_size(const Size2i &p_size); GDVIRTUAL1(_set_create_options, Object *) GDVIRTUAL1R(bool, _handle_menu_selected, int) diff --git a/editor/editor_resource_preview.cpp b/editor/editor_resource_preview.cpp index f5da9da8e7..38a78babfb 100644 --- a/editor/editor_resource_preview.cpp +++ b/editor/editor_resource_preview.cpp @@ -40,6 +40,7 @@ #include "editor/editor_paths.h" #include "editor/editor_scale.h" #include "editor/editor_settings.h" +#include "scene/resources/image_texture.h" bool EditorResourcePreviewGenerator::handles(const String &p_type) const { bool success = false; diff --git a/editor/editor_resource_preview.h b/editor/editor_resource_preview.h index 84835094bb..925039139b 100644 --- a/editor/editor_resource_preview.h +++ b/editor/editor_resource_preview.h @@ -35,7 +35,9 @@ #include "core/os/thread.h" #include "core/templates/safe_refcount.h" #include "scene/main/node.h" -#include "scene/resources/texture.h" + +class ImageTexture; +class Texture2D; class EditorResourcePreviewGenerator : public RefCounted { GDCLASS(EditorResourcePreviewGenerator, RefCounted); diff --git a/editor/editor_run_native.cpp b/editor/editor_run_native.cpp index beccf0f2ec..cf6a8f1368 100644 --- a/editor/editor_run_native.cpp +++ b/editor/editor_run_native.cpp @@ -35,6 +35,7 @@ #include "editor/editor_settings.h" #include "editor/export/editor_export.h" #include "editor/export/editor_export_platform.h" +#include "scene/resources/image_texture.h" void EditorRunNative::_notification(int p_what) { switch (p_what) { diff --git a/editor/editor_settings.cpp b/editor/editor_settings.cpp index 537e862a51..285be7e7ae 100644 --- a/editor/editor_settings.cpp +++ b/editor/editor_settings.cpp @@ -680,14 +680,15 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) { _initial_set("editors/2d/grid_color", Color(1.0, 1.0, 1.0, 0.07)); _initial_set("editors/2d/guides_color", Color(0.6, 0.0, 0.8)); _initial_set("editors/2d/smart_snapping_line_color", Color(0.9, 0.1, 0.1)); - _initial_set("editors/2d/bone_width", 5); + EDITOR_SETTING(Variant::FLOAT, PROPERTY_HINT_RANGE, "editors/2d/bone_width", 5.0, "0.01,20,0.01,or_greater") _initial_set("editors/2d/bone_color1", Color(1.0, 1.0, 1.0, 0.7)); _initial_set("editors/2d/bone_color2", Color(0.6, 0.6, 0.6, 0.7)); _initial_set("editors/2d/bone_selected_color", Color(0.9, 0.45, 0.45, 0.7)); _initial_set("editors/2d/bone_ik_color", Color(0.9, 0.9, 0.45, 0.7)); _initial_set("editors/2d/bone_outline_color", Color(0.35, 0.35, 0.35, 0.5)); - _initial_set("editors/2d/bone_outline_size", 2); + EDITOR_SETTING(Variant::FLOAT, PROPERTY_HINT_RANGE, "editors/2d/bone_outline_size", 2.0, "0.01,8,0.01,or_greater") _initial_set("editors/2d/viewport_border_color", Color(0.4, 0.4, 1.0, 0.4)); + _initial_set("editors/2d/use_integer_zoom_by_default", false); // Panning // Enum should be in sync with ControlScheme in ViewPanner. @@ -708,7 +709,6 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) { // Animation _initial_set("editors/animation/autorename_animation_tracks", true); - _initial_set("editors/animation/confirm_insert_track", true); _initial_set("editors/animation/default_create_bezier_tracks", false); _initial_set("editors/animation/default_create_reset_tracks", true); _initial_set("editors/animation/onion_layers_past_color", Color(1, 0, 0)); @@ -878,6 +878,13 @@ EditorSettings *EditorSettings::get_singleton() { return singleton.ptr(); } +void EditorSettings::ensure_class_registered() { + ClassDB::APIType prev_api = ClassDB::get_current_api(); + ClassDB::set_current_api(ClassDB::API_EDITOR); + GDREGISTER_CLASS(EditorSettings); // Otherwise it can't be unserialized. + ClassDB::set_current_api(prev_api); +} + void EditorSettings::create() { // IMPORTANT: create() *must* create a valid EditorSettings singleton, // as the rest of the engine code will assume it. As such, it should never @@ -888,7 +895,7 @@ void EditorSettings::create() { return; } - GDREGISTER_CLASS(EditorSettings); // Otherwise it can't be unserialized. + ensure_class_registered(); String config_file_path; Ref<ConfigFile> extra_config = memnew(ConfigFile); diff --git a/editor/editor_settings.h b/editor/editor_settings.h index 660a9501a2..55ac6c5a15 100644 --- a/editor/editor_settings.h +++ b/editor/editor_settings.h @@ -117,6 +117,7 @@ public: static EditorSettings *get_singleton(); + static void ensure_class_registered(); static void create(); void setup_language(); void setup_network(); diff --git a/editor/editor_themes.cpp b/editor/editor_themes.cpp index 1c56c23ee1..cbf536899a 100644 --- a/editor/editor_themes.cpp +++ b/editor/editor_themes.cpp @@ -36,6 +36,10 @@ #include "editor/editor_icons.gen.h" #include "editor/editor_scale.h" #include "editor/editor_settings.h" +#include "scene/resources/image_texture.h" +#include "scene/resources/style_box_flat.h" +#include "scene/resources/style_box_line.h" +#include "scene/resources/style_box_texture.h" #include "modules/modules_enabled.gen.h" // For svg. #ifdef MODULE_SVG_ENABLED @@ -1605,6 +1609,7 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { style_window->set_border_width(SIDE_TOP, 24 * EDSCALE); style_window->set_expand_margin(SIDE_TOP, 24 * EDSCALE); theme->set_stylebox("embedded_border", "Window", style_window); + theme->set_stylebox("embedded_unfocused_border", "Window", style_window); theme->set_color("title_color", "Window", font_color); theme->set_icon("close", "Window", theme->get_icon(SNAME("GuiClose"), SNAME("EditorIcons"))); @@ -1788,7 +1793,7 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { theme->set_constant("outline_size", "ProgressBar", 0); // GraphEdit - theme->set_stylebox("bg", "GraphEdit", style_tree_bg); + theme->set_stylebox("panel", "GraphEdit", style_tree_bg); if (dark_theme) { theme->set_color("grid_major", "GraphEdit", Color(1.0, 1.0, 1.0, 0.15)); theme->set_color("grid_minor", "GraphEdit", Color(1.0, 1.0, 1.0, 0.07)); @@ -1799,18 +1804,20 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { theme->set_color("selection_fill", "GraphEdit", theme->get_color(SNAME("box_selection_fill_color"), SNAME("Editor"))); theme->set_color("selection_stroke", "GraphEdit", theme->get_color(SNAME("box_selection_stroke_color"), SNAME("Editor"))); theme->set_color("activity", "GraphEdit", accent_color); - theme->set_icon("minus", "GraphEdit", theme->get_icon(SNAME("ZoomLess"), SNAME("EditorIcons"))); - theme->set_icon("more", "GraphEdit", theme->get_icon(SNAME("ZoomMore"), SNAME("EditorIcons"))); - theme->set_icon("reset", "GraphEdit", theme->get_icon(SNAME("ZoomReset"), SNAME("EditorIcons"))); - theme->set_icon("snap", "GraphEdit", theme->get_icon(SNAME("SnapGrid"), SNAME("EditorIcons"))); - theme->set_icon("minimap", "GraphEdit", theme->get_icon(SNAME("GridMinimap"), SNAME("EditorIcons"))); + + theme->set_icon("zoom_out", "GraphEdit", theme->get_icon(SNAME("ZoomLess"), SNAME("EditorIcons"))); + theme->set_icon("zoom_in", "GraphEdit", theme->get_icon(SNAME("ZoomMore"), SNAME("EditorIcons"))); + theme->set_icon("zoom_reset", "GraphEdit", theme->get_icon(SNAME("ZoomReset"), SNAME("EditorIcons"))); + theme->set_icon("grid_toggle", "GraphEdit", theme->get_icon(SNAME("GridToggle"), SNAME("EditorIcons"))); + theme->set_icon("minimap_toggle", "GraphEdit", theme->get_icon(SNAME("GridMinimap"), SNAME("EditorIcons"))); + theme->set_icon("snapping_toggle", "GraphEdit", theme->get_icon(SNAME("SnapGrid"), SNAME("EditorIcons"))); theme->set_icon("layout", "GraphEdit", theme->get_icon(SNAME("GridLayout"), SNAME("EditorIcons"))); // GraphEditMinimap Ref<StyleBoxFlat> style_minimap_bg = make_flat_stylebox(dark_color_1, 0, 0, 0, 0); style_minimap_bg->set_border_color(dark_color_3); style_minimap_bg->set_border_width_all(1); - theme->set_stylebox("bg", "GraphEditMinimap", style_minimap_bg); + theme->set_stylebox("panel", "GraphEditMinimap", style_minimap_bg); Ref<StyleBoxFlat> style_minimap_camera; Ref<StyleBoxFlat> style_minimap_node; @@ -1890,8 +1897,6 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { theme->set_stylebox("frame", "GraphNode", graphsb); theme->set_stylebox("selected_frame", "GraphNode", graphsbselected); - theme->set_stylebox("comment", "GraphNode", graphsbcomment); - theme->set_stylebox("comment_focus", "GraphNode", graphsbcommentselected); theme->set_stylebox("breakpoint", "GraphNode", graphsbbreakpoint); theme->set_stylebox("position", "GraphNode", graphsbposition); theme->set_stylebox("slot", "GraphNode", graphsbslot); diff --git a/editor/editor_undo_redo_manager.cpp b/editor/editor_undo_redo_manager.cpp index fd2d51be32..abfbd5e7c0 100644 --- a/editor/editor_undo_redo_manager.cpp +++ b/editor/editor_undo_redo_manager.cpp @@ -264,6 +264,7 @@ void EditorUndoRedoManager::commit_action(bool p_execute) { pending_action.action_name == prev_action.action_name && pending_action.action_name == pre_prev_action.action_name) { pending_action = Action(); is_committing = false; + emit_signal(SNAME("history_changed")); return; } } break; @@ -272,6 +273,7 @@ void EditorUndoRedoManager::commit_action(bool p_execute) { if (pending_action.merge_mode == prev_action.merge_mode && pending_action.action_name == prev_action.action_name) { pending_action = Action(); is_committing = false; + emit_signal(SNAME("history_changed")); return; } } break; diff --git a/editor/export/editor_export_platform.cpp b/editor/export/editor_export_platform.cpp index d04eeafd07..5ee9b187a2 100644 --- a/editor/export/editor_export_platform.cpp +++ b/editor/export/editor_export_platform.cpp @@ -45,6 +45,7 @@ #include "editor/export/editor_export.h" #include "editor/plugins/script_editor_plugin.h" #include "editor_export_plugin.h" +#include "scene/resources/image_texture.h" #include "scene/resources/packed_scene.h" static int _get_pad(int p_alignment, int p_n) { @@ -989,7 +990,7 @@ Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> & struct SortByName { bool operator()(const Ref<EditorExportPlugin> &left, const Ref<EditorExportPlugin> &right) const { - return left->_get_name() < right->_get_name(); + return left->get_name() < right->get_name(); } }; @@ -1032,14 +1033,14 @@ Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> & if (export_plugins.write[i]->_begin_customize_resources(Ref<EditorExportPlatform>(this), features_psa)) { customize_resources_plugins.push_back(export_plugins[i]); - custom_resources_hash = hash_murmur3_one_64(export_plugins[i]->_get_name().hash64(), custom_resources_hash); + custom_resources_hash = hash_murmur3_one_64(export_plugins[i]->get_name().hash64(), custom_resources_hash); uint64_t hash = export_plugins[i]->_get_customization_configuration_hash(); custom_resources_hash = hash_murmur3_one_64(hash, custom_resources_hash); } if (export_plugins.write[i]->_begin_customize_scenes(Ref<EditorExportPlatform>(this), features_psa)) { customize_scenes_plugins.push_back(export_plugins[i]); - custom_resources_hash = hash_murmur3_one_64(export_plugins[i]->_get_name().hash64(), custom_resources_hash); + custom_resources_hash = hash_murmur3_one_64(export_plugins[i]->get_name().hash64(), custom_resources_hash); uint64_t hash = export_plugins[i]->_get_customization_configuration_hash(); custom_scene_hash = hash_murmur3_one_64(hash, custom_scene_hash); } @@ -1799,6 +1800,24 @@ bool EditorExportPlatform::can_export(const Ref<EditorExportPreset> &p_preset, S if (!templates_error.is_empty()) { r_error += templates_error; } + + String export_plugins_warning; + Vector<Ref<EditorExportPlugin>> export_plugins = EditorExport::get_singleton()->get_export_plugins(); + for (int i = 0; i < export_plugins.size(); i++) { + Ref<EditorExportPlatform> export_platform = Ref<EditorExportPlatform>(this); + if (!export_plugins[i]->supports_platform(export_platform)) { + continue; + } + + String plugin_warning = export_plugins.write[i]->_has_valid_export_configuration(export_platform, p_preset); + if (!plugin_warning.is_empty()) { + export_plugins_warning += plugin_warning; + } + } + + if (!export_plugins_warning.is_empty()) { + r_error += export_plugins_warning; + } #endif String project_configuration_error; diff --git a/editor/export/editor_export_platform.h b/editor/export/editor_export_platform.h index 121e00ccae..763836e3ec 100644 --- a/editor/export/editor_export_platform.h +++ b/editor/export/editor_export_platform.h @@ -40,6 +40,7 @@ struct EditorProgress; #include "editor_export_shared_object.h" #include "scene/gui/rich_text_label.h" #include "scene/main/node.h" +#include "scene/resources/image_texture.h" class EditorExportPlugin; diff --git a/editor/export/editor_export_platform_pc.cpp b/editor/export/editor_export_platform_pc.cpp index df1026d0ed..ec34ffd1df 100644 --- a/editor/export/editor_export_platform_pc.cpp +++ b/editor/export/editor_export_platform_pc.cpp @@ -31,6 +31,7 @@ #include "editor_export_platform_pc.h" #include "core/config/project_settings.h" +#include "scene/resources/image_texture.h" void EditorExportPlatformPC::get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features) const { if (p_preset->get("texture_format/bptc")) { diff --git a/editor/export/editor_export_plugin.cpp b/editor/export/editor_export_plugin.cpp index 4e2c1a9af7..6576960b9a 100644 --- a/editor/export/editor_export_plugin.cpp +++ b/editor/export/editor_export_plugin.cpp @@ -132,6 +132,27 @@ Variant EditorExportPlugin::get_option(const StringName &p_name) const { return export_preset->get(p_name); } +String EditorExportPlugin::_has_valid_export_configuration(const Ref<EditorExportPlatform> &p_export_platform, const Ref<EditorExportPreset> &p_preset) { + String warning; + if (!supports_platform(p_export_platform)) { + warning += vformat(TTR("Plugin \"%s\" is not supported on \"%s\""), get_name(), p_export_platform->get_name()); + warning += "\n"; + return warning; + } + + set_export_preset(p_preset); + List<EditorExportPlatform::ExportOption> options; + _get_export_options(p_export_platform, &options); + for (const EditorExportPlatform::ExportOption &E : options) { + String option_warning = _get_export_option_warning(p_export_platform, E.option.name); + if (!option_warning.is_empty()) { + warning += option_warning + "\n"; + } + } + + return warning; +} + void EditorExportPlugin::_export_file_script(const String &p_path, const String &p_type, const Vector<String> &p_features) { GDVIRTUAL_CALL(_export_file, p_path, p_type, p_features); } @@ -184,12 +205,54 @@ void EditorExportPlugin::_end_customize_resources() { GDVIRTUAL_CALL(_end_customize_resources); } -String EditorExportPlugin::_get_name() const { +String EditorExportPlugin::get_name() const { String ret; GDVIRTUAL_REQUIRED_CALL(_get_name, ret); return ret; } +bool EditorExportPlugin::supports_platform(const Ref<EditorExportPlatform> &p_export_platform) const { + bool ret = false; + GDVIRTUAL_CALL(_supports_platform, p_export_platform, ret); + return ret; +} + +PackedStringArray EditorExportPlugin::get_android_dependencies(const Ref<EditorExportPlatform> &p_export_platform, bool p_debug) const { + PackedStringArray ret; + GDVIRTUAL_CALL(_get_android_dependencies, p_export_platform, p_debug, ret); + return ret; +} + +PackedStringArray EditorExportPlugin::get_android_dependencies_maven_repos(const Ref<EditorExportPlatform> &p_export_platform, bool p_debug) const { + PackedStringArray ret; + GDVIRTUAL_CALL(_get_android_dependencies_maven_repos, p_export_platform, p_debug, ret); + return ret; +} + +PackedStringArray EditorExportPlugin::get_android_libraries(const Ref<EditorExportPlatform> &p_export_platform, bool p_debug) const { + PackedStringArray ret; + GDVIRTUAL_CALL(_get_android_libraries, p_export_platform, p_debug, ret); + return ret; +} + +String EditorExportPlugin::get_android_manifest_activity_element_contents(const Ref<EditorExportPlatform> &p_export_platform, bool p_debug) const { + String ret; + GDVIRTUAL_CALL(_get_android_manifest_activity_element_contents, p_export_platform, p_debug, ret); + return ret; +} + +String EditorExportPlugin::get_android_manifest_application_element_contents(const Ref<EditorExportPlatform> &p_export_platform, bool p_debug) const { + String ret; + GDVIRTUAL_CALL(_get_android_manifest_application_element_contents, p_export_platform, p_debug, ret); + return ret; +} + +String EditorExportPlugin::get_android_manifest_element_contents(const Ref<EditorExportPlatform> &p_export_platform, bool p_debug) const { + String ret; + GDVIRTUAL_CALL(_get_android_manifest_element_contents, p_export_platform, p_debug, ret); + return ret; +} + PackedStringArray EditorExportPlugin::_get_export_features(const Ref<EditorExportPlatform> &p_platform, bool p_debug) const { PackedStringArray ret; GDVIRTUAL_CALL(_get_export_features, p_platform, p_debug, ret); @@ -216,6 +279,12 @@ bool EditorExportPlugin::_should_update_export_options(const Ref<EditorExportPla return ret; } +String EditorExportPlugin::_get_export_option_warning(const Ref<EditorExportPlatform> &p_export_platform, const String &p_option_name) const { + String ret; + GDVIRTUAL_CALL(_get_export_option_warning, p_export_platform, p_option_name, ret); + return ret; +} + void EditorExportPlugin::_export_file(const String &p_path, const String &p_type, const HashSet<String> &p_features) { } @@ -257,9 +326,19 @@ void EditorExportPlugin::_bind_methods() { GDVIRTUAL_BIND(_get_export_options, "platform"); GDVIRTUAL_BIND(_should_update_export_options, "platform"); + GDVIRTUAL_BIND(_get_export_option_warning, "platform", "option"); GDVIRTUAL_BIND(_get_export_features, "platform", "debug"); GDVIRTUAL_BIND(_get_name); + + GDVIRTUAL_BIND(_supports_platform, "platform"); + + GDVIRTUAL_BIND(_get_android_dependencies, "platform", "debug"); + GDVIRTUAL_BIND(_get_android_dependencies_maven_repos, "platform", "debug"); + GDVIRTUAL_BIND(_get_android_libraries, "platform", "debug"); + GDVIRTUAL_BIND(_get_android_manifest_activity_element_contents, "platform", "debug"); + GDVIRTUAL_BIND(_get_android_manifest_application_element_contents, "platform", "debug"); + GDVIRTUAL_BIND(_get_android_manifest_element_contents, "platform", "debug"); } EditorExportPlugin::EditorExportPlugin() { diff --git a/editor/export/editor_export_plugin.h b/editor/export/editor_export_plugin.h index 120141b347..7d866ce37e 100644 --- a/editor/export/editor_export_plugin.h +++ b/editor/export/editor_export_plugin.h @@ -42,6 +42,7 @@ class EditorExportPlugin : public RefCounted { friend class EditorExport; friend class EditorExportPlatform; + friend class EditorExportPreset; Ref<EditorExportPreset> export_preset; @@ -85,6 +86,8 @@ class EditorExportPlugin : public RefCounted { void _export_begin_script(const Vector<String> &p_features, bool p_debug, const String &p_path, int p_flags); void _export_end_script(); + String _has_valid_export_configuration(const Ref<EditorExportPlatform> &p_export_platform, const Ref<EditorExportPreset> &p_preset); + protected: void set_export_preset(const Ref<EditorExportPreset> &p_preset); Ref<EditorExportPreset> get_export_preset() const; @@ -125,9 +128,19 @@ protected: GDVIRTUAL2RC(PackedStringArray, _get_export_features, const Ref<EditorExportPlatform> &, bool); GDVIRTUAL1RC(TypedArray<Dictionary>, _get_export_options, const Ref<EditorExportPlatform> &); GDVIRTUAL1RC(bool, _should_update_export_options, const Ref<EditorExportPlatform> &); + GDVIRTUAL2RC(String, _get_export_option_warning, const Ref<EditorExportPlatform> &, String); GDVIRTUAL0RC(String, _get_name) + GDVIRTUAL1RC(bool, _supports_platform, const Ref<EditorExportPlatform> &); + + GDVIRTUAL2RC(PackedStringArray, _get_android_dependencies, const Ref<EditorExportPlatform> &, bool); + GDVIRTUAL2RC(PackedStringArray, _get_android_dependencies_maven_repos, const Ref<EditorExportPlatform> &, bool); + GDVIRTUAL2RC(PackedStringArray, _get_android_libraries, const Ref<EditorExportPlatform> &, bool); + GDVIRTUAL2RC(String, _get_android_manifest_activity_element_contents, const Ref<EditorExportPlatform> &, bool); + GDVIRTUAL2RC(String, _get_android_manifest_application_element_contents, const Ref<EditorExportPlatform> &, bool); + GDVIRTUAL2RC(String, _get_android_manifest_element_contents, const Ref<EditorExportPlatform> &, bool); + virtual bool _begin_customize_resources(const Ref<EditorExportPlatform> &p_platform, const Vector<String> &p_features); // Return true if this plugin does property export customization virtual Ref<Resource> _customize_resource(const Ref<Resource> &p_resource, const String &p_path); // If nothing is returned, it means do not touch (nothing changed). If something is returned (either the same or a different resource) it means changes are made. @@ -142,10 +155,20 @@ protected: virtual PackedStringArray _get_export_features(const Ref<EditorExportPlatform> &p_export_platform, bool p_debug) const; virtual void _get_export_options(const Ref<EditorExportPlatform> &p_export_platform, List<EditorExportPlatform::ExportOption> *r_options) const; virtual bool _should_update_export_options(const Ref<EditorExportPlatform> &p_export_platform) const; - - virtual String _get_name() const; + virtual String _get_export_option_warning(const Ref<EditorExportPlatform> &p_export_platform, const String &p_option_name) const; public: + virtual String get_name() const; + + virtual bool supports_platform(const Ref<EditorExportPlatform> &p_export_platform) const; + + virtual PackedStringArray get_android_dependencies(const Ref<EditorExportPlatform> &p_export_platform, bool p_debug) const; + virtual PackedStringArray get_android_dependencies_maven_repos(const Ref<EditorExportPlatform> &p_export_platform, bool p_debug) const; + virtual PackedStringArray get_android_libraries(const Ref<EditorExportPlatform> &p_export_platform, bool p_debug) const; + virtual String get_android_manifest_activity_element_contents(const Ref<EditorExportPlatform> &p_export_platform, bool p_debug) const; + virtual String get_android_manifest_application_element_contents(const Ref<EditorExportPlatform> &p_export_platform, bool p_debug) const; + virtual String get_android_manifest_element_contents(const Ref<EditorExportPlatform> &p_export_platform, bool p_debug) const; + Vector<String> get_ios_frameworks() const; Vector<String> get_ios_embedded_frameworks() const; Vector<String> get_ios_project_static_libs() const; diff --git a/editor/export/editor_export_preset.cpp b/editor/export/editor_export_preset.cpp index a7dc44e3a8..dfc0c23afc 100644 --- a/editor/export/editor_export_preset.cpp +++ b/editor/export/editor_export_preset.cpp @@ -57,7 +57,26 @@ void EditorExportPreset::_bind_methods() { } String EditorExportPreset::_get_property_warning(const StringName &p_name) const { - return platform->get_export_option_warning(this, p_name); + String warning = platform->get_export_option_warning(this, p_name); + if (!warning.is_empty()) { + warning += "\n"; + } + + // Get property warning from editor export plugins. + Vector<Ref<EditorExportPlugin>> export_plugins = EditorExport::get_singleton()->get_export_plugins(); + for (int i = 0; i < export_plugins.size(); i++) { + if (!export_plugins[i]->supports_platform(platform)) { + continue; + } + + export_plugins.write[i]->set_export_preset(Ref<EditorExportPreset>(this)); + String plugin_warning = export_plugins[i]->_get_export_option_warning(platform, p_name); + if (!plugin_warning.is_empty()) { + warning += plugin_warning + "\n"; + } + } + + return warning; } void EditorExportPreset::_get_property_list(List<PropertyInfo> *p_list) const { diff --git a/editor/export/export_template_manager.cpp b/editor/export/export_template_manager.cpp index e551b0531a..42e4b6f6f6 100644 --- a/editor/export/export_template_manager.cpp +++ b/editor/export/export_template_manager.cpp @@ -669,11 +669,8 @@ Error ExportTemplateManager::install_android_template_from_file(const String &p_ f->store_line(VERSION_FULL_CONFIG); } - // Create the android plugins directory. - Error err = da->make_dir_recursive("android/plugins"); - ERR_FAIL_COND_V(err != OK, err); - - err = da->make_dir_recursive("android/build"); + // Create the android build directory. + Error err = da->make_dir_recursive("android/build"); ERR_FAIL_COND_V(err != OK, err); { // Add an empty .gdignore file to avoid scan. diff --git a/editor/gui/editor_toaster.cpp b/editor/gui/editor_toaster.cpp index 866a6db2a6..f928a0fd30 100644 --- a/editor/gui/editor_toaster.cpp +++ b/editor/gui/editor_toaster.cpp @@ -35,6 +35,7 @@ #include "scene/gui/button.h" #include "scene/gui/label.h" #include "scene/gui/panel_container.h" +#include "scene/resources/style_box_flat.h" EditorToaster *EditorToaster::singleton = nullptr; diff --git a/editor/gui/editor_toaster.h b/editor/gui/editor_toaster.h index 4837756b4e..3e39d9d519 100644 --- a/editor/gui/editor_toaster.h +++ b/editor/gui/editor_toaster.h @@ -37,6 +37,7 @@ class Button; class PanelContainer; +class StyleBoxFlat; class EditorToaster : public HBoxContainer { GDCLASS(EditorToaster, HBoxContainer); diff --git a/editor/icons/GridToggle.svg b/editor/icons/GridToggle.svg new file mode 100644 index 0000000000..595952c3af --- /dev/null +++ b/editor/icons/GridToggle.svg @@ -0,0 +1 @@ +<svg viewBox="0 0 16 16" height="16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="M2 0v2H0v1h2v4H0v1h2v4H0v1h2v2h1v-2h3.125A5.5 5 0 016 12H3V8h4v1.143a5.5 5 0 011-.99V8h.223A5.5 5 0 0111.5 7H8V3h4v4h-.5a5.5 5 0 013.297 1H15V7h-2V3h2V2h-2V0h-1v2H8V0H7v2H3V0H2zm1 3h4v4H3V3zm4 11.857V15h.117A5.5 5 0 017 14.857z" fill="#e0e0e0"/><path d="M11.485 8.261c-1.648 0-3.734 1.256-4.485 3.68a.645.645 0 00-.004.367C7.721 14.846 9.873 16 11.486 16c1.612 0 3.764-1.154 4.489-3.692a.645.645 0 000-.356c-.71-2.443-2.842-3.691-4.49-3.691zm0 1.29a2.58 2.58 0 012.58 2.58 2.58 2.58 0 01-2.58 2.58 2.58 2.58 0 01-2.58-2.58 2.58 2.58 0 012.58-2.58zm0 1.29a1.29 1.29 0 00-1.29 1.29 1.29 1.29 0 001.29 1.29 1.29 1.29 0 001.29-1.29 1.29 1.29 0 00-1.29-1.29z" fill="#e0e0e0" /></svg> diff --git a/editor/icons/Help.svg b/editor/icons/Help.svg index b2a52e8ad9..ecb9d3e359 100644 --- a/editor/icons/Help.svg +++ b/editor/icons/Help.svg @@ -1 +1 @@ -<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M5.029 1C4.03.989 3.02 1.312 2 2v7c2.017-1.353 4.017-1.314 6 0 1.983-1.314 3.983-1.353 6 0V2c-1.02-.688-2.03-1.011-3.029-1-.662.007-1.318.173-1.971.463V6H8V2c-.982-.645-1.971-.989-2.971-1zM0 10v6h2a3 3 0 0 0 0-6zm5 3a3 3 0 0 0 6 0 3 3 0 0 0-6 0zm9 3h1v-2h-1a1 1 0 0 1 0-2h1v-2h-1a3 3 0 0 0 0 6zM2 12a1 1 0 0 1 0 2zm6 0a1 1 0 0 1 0 2 1 1 0 0 1 0-2z" fill="#e0e0e0" fill-opacity=".59"/></svg> +<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M8 2C6 .7 4 .7 2 2v7c2-1.3 4-1.3 6 0 2-1.3 4-1.3 6 0V2C12 .7 10.2.9 9 1.5V6H8zm-8 8v6h2a3 3 0 0 0 0-6zm5 3a3 3 0 0 0 6 0 3 3 0 0 0-6 0zm9 3h1v-2h-1a1 1 0 0 1 0-2h1v-2h-1a3 3 0 0 0 0 6zM2 12a1 1 0 0 1 0 2zm6 0a1 1 0 0 1 0 2 1 1 0 0 1 0-2z" fill="#e0e0e0"/></svg> diff --git a/editor/icons/Info.svg b/editor/icons/Info.svg new file mode 100644 index 0000000000..1a16f74069 --- /dev/null +++ b/editor/icons/Info.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path d="M8 1a7 7 0 0 0 0 14A7 7 0 0 0 8 1m1 12H7v-2h2v2M4.5 6a3.5 3.4 0 1 1 7 0C11.3 8.9 9 8.5 9 10H7c.1-2.6 2.4-2.8 2.5-4a1.5 1.4 0 0 0-3 0z" fill="#e0e0e0"/></svg> diff --git a/editor/import/audio_stream_import_settings.cpp b/editor/import/audio_stream_import_settings.cpp index abf56c7ef8..1f7fbd8c59 100644 --- a/editor/import/audio_stream_import_settings.cpp +++ b/editor/import/audio_stream_import_settings.cpp @@ -395,7 +395,7 @@ void AudioStreamImportSettings::_seek_to(real_t p_x) { void AudioStreamImportSettings::edit(const String &p_path, const String &p_importer, const Ref<AudioStream> &p_stream) { if (!stream.is_null()) { - stream->disconnect("changed", callable_mp(this, &AudioStreamImportSettings::_audio_changed)); + stream->disconnect_changed(callable_mp(this, &AudioStreamImportSettings::_audio_changed)); } importer = p_importer; @@ -408,7 +408,7 @@ void AudioStreamImportSettings::edit(const String &p_path, const String &p_impor _duration_label->set_text(text); if (!stream.is_null()) { - stream->connect("changed", callable_mp(this, &AudioStreamImportSettings::_audio_changed)); + stream->connect_changed(callable_mp(this, &AudioStreamImportSettings::_audio_changed)); _preview->queue_redraw(); _indicator->queue_redraw(); color_rect->queue_redraw(); diff --git a/editor/import/resource_importer_imagefont.cpp b/editor/import/resource_importer_imagefont.cpp index cbd00f0f6b..8e04ce4c7e 100644 --- a/editor/import/resource_importer_imagefont.cpp +++ b/editor/import/resource_importer_imagefont.cpp @@ -111,7 +111,7 @@ Error ResourceImporterImageFont::import(const String &p_source_file, const Strin int chr_width = chr_cell_width - char_margin.position.x - char_margin.size.x; int chr_height = chr_cell_height - char_margin.position.y - char_margin.size.y; - ERR_FAIL_COND_V_MSG(chr_width <= 0 || chr_height <= 0, ERR_FILE_CANT_READ, TTR("Character margin too bit.")); + ERR_FAIL_COND_V_MSG(chr_width <= 0 || chr_height <= 0, ERR_FILE_CANT_READ, TTR("Character margin too big.")); Ref<FontFile> font; font.instantiate(); diff --git a/editor/import/resource_importer_layered_texture.cpp b/editor/import/resource_importer_layered_texture.cpp index 89a0f4ca3c..3c27864eff 100644 --- a/editor/import/resource_importer_layered_texture.cpp +++ b/editor/import/resource_importer_layered_texture.cpp @@ -39,6 +39,7 @@ #include "editor/editor_node.h" #include "editor/import/resource_importer_texture.h" #include "editor/import/resource_importer_texture_settings.h" +#include "scene/resources/compressed_texture.h" #include "scene/resources/texture.h" String ResourceImporterLayeredTexture::get_importer_name() const { diff --git a/editor/import/resource_importer_texture.cpp b/editor/import/resource_importer_texture.cpp index e81e836e9e..92367eab8b 100644 --- a/editor/import/resource_importer_texture.cpp +++ b/editor/import/resource_importer_texture.cpp @@ -39,6 +39,7 @@ #include "editor/editor_scale.h" #include "editor/editor_settings.h" #include "editor/import/resource_importer_texture_settings.h" +#include "scene/resources/compressed_texture.h" void ResourceImporterTexture::_texture_reimport_roughness(const Ref<CompressedTexture2D> &p_tex, const String &p_normal_path, RS::TextureDetectRoughnessChannel p_channel) { ERR_FAIL_COND(p_tex.is_null()); diff --git a/editor/import/resource_importer_texture_atlas.cpp b/editor/import/resource_importer_texture_atlas.cpp index 5a85160690..d437f23740 100644 --- a/editor/import/resource_importer_texture_atlas.cpp +++ b/editor/import/resource_importer_texture_atlas.cpp @@ -36,8 +36,10 @@ #include "core/io/resource_saver.h" #include "core/math/geometry_2d.h" #include "editor/editor_atlas_packer.h" +#include "scene/resources/atlas_texture.h" +#include "scene/resources/image_texture.h" #include "scene/resources/mesh.h" -#include "scene/resources/texture.h" +#include "scene/resources/mesh_texture.h" String ResourceImporterTextureAtlas::get_importer_name() const { return "texture_atlas"; diff --git a/editor/plugins/animation_blend_tree_editor_plugin.cpp b/editor/plugins/animation_blend_tree_editor_plugin.cpp index ea210872f7..8f2839ddb0 100644 --- a/editor/plugins/animation_blend_tree_editor_plugin.cpp +++ b/editor/plugins/animation_blend_tree_editor_plugin.cpp @@ -48,6 +48,7 @@ #include "scene/gui/separator.h" #include "scene/gui/view_panner.h" #include "scene/main/window.h" +#include "scene/resources/style_box_flat.h" void AnimationNodeBlendTreeEditor::add_custom_type(const String &p_name, const Ref<Script> &p_script) { for (int i = 0; i < add_options.size(); i++) { @@ -125,7 +126,7 @@ void AnimationNodeBlendTreeEditor::update_graph() { visible_properties.clear(); - graph->set_scroll_ofs(blend_tree->get_graph_offset() * EDSCALE); + graph->set_scroll_offset(blend_tree->get_graph_offset() * EDSCALE); graph->clear_connections(); //erase all nodes @@ -347,7 +348,7 @@ void AnimationNodeBlendTreeEditor::_add_node(int p_idx) { return; } - Point2 instance_pos = graph->get_scroll_ofs(); + Point2 instance_pos = graph->get_scroll_offset(); if (use_position_from_popup_menu) { instance_pos += position_from_popup_menu; } else { @@ -1091,13 +1092,13 @@ AnimationNodeBlendTreeEditor::AnimationNodeBlendTreeEditor() { graph->set_connection_lines_curvature(graph_lines_curvature); VSeparator *vs = memnew(VSeparator); - graph->get_zoom_hbox()->add_child(vs); - graph->get_zoom_hbox()->move_child(vs, 0); + graph->get_menu_hbox()->add_child(vs); + graph->get_menu_hbox()->move_child(vs, 0); add_node = memnew(MenuButton); - graph->get_zoom_hbox()->add_child(add_node); + graph->get_menu_hbox()->add_child(add_node); add_node->set_text(TTR("Add Node...")); - graph->get_zoom_hbox()->move_child(add_node, 0); + graph->get_menu_hbox()->move_child(add_node, 0); add_node->get_popup()->connect("id_pressed", callable_mp(this, &AnimationNodeBlendTreeEditor::_add_node)); add_node->get_popup()->connect("popup_hide", callable_mp(this, &AnimationNodeBlendTreeEditor::_popup_hide), CONNECT_DEFERRED); add_node->connect("about_to_popup", callable_mp(this, &AnimationNodeBlendTreeEditor::_update_options_menu).bind(false)); diff --git a/editor/plugins/animation_player_editor_plugin.cpp b/editor/plugins/animation_player_editor_plugin.cpp index 1fdb1d4a6e..7f4e7460f8 100644 --- a/editor/plugins/animation_player_editor_plugin.cpp +++ b/editor/plugins/animation_player_editor_plugin.cpp @@ -47,6 +47,7 @@ #include "scene/gui/separator.h" #include "scene/main/window.h" #include "scene/resources/animation.h" +#include "scene/resources/image_texture.h" #include "scene/scene_string_names.h" #include "servers/rendering_server.h" diff --git a/editor/plugins/animation_player_editor_plugin.h b/editor/plugins/animation_player_editor_plugin.h index 327200506f..8c46b5c36e 100644 --- a/editor/plugins/animation_player_editor_plugin.h +++ b/editor/plugins/animation_player_editor_plugin.h @@ -42,6 +42,7 @@ #include "scene/gui/tree.h" class AnimationPlayerEditorPlugin; +class ImageTexture; class AnimationPlayerEditor : public VBoxContainer { GDCLASS(AnimationPlayerEditor, VBoxContainer); diff --git a/editor/plugins/animation_state_machine_editor.cpp b/editor/plugins/animation_state_machine_editor.cpp index 49f073f245..0b2af0172c 100644 --- a/editor/plugins/animation_state_machine_editor.cpp +++ b/editor/plugins/animation_state_machine_editor.cpp @@ -50,6 +50,7 @@ #include "scene/gui/tree.h" #include "scene/main/viewport.h" #include "scene/main/window.h" +#include "scene/resources/style_box_flat.h" #include "scene/scene_string_names.h" bool AnimationNodeStateMachineEditor::can_edit(const Ref<AnimationNode> &p_node) { @@ -1507,6 +1508,10 @@ void AnimationNodeStateMachineEditor::_name_edited(const String &p_text) { int base = 1; String name = base_name; while (state_machine->has_node(name)) { + if (name == prev_name) { + name_edit_popup->hide(); // The old name wins, the name doesn't change, just hide the popup. + return; + } base++; name = base_name + " " + itos(base); } diff --git a/editor/plugins/asset_library_editor_plugin.cpp b/editor/plugins/asset_library_editor_plugin.cpp index b2e40fa6c0..5c26199af1 100644 --- a/editor/plugins/asset_library_editor_plugin.cpp +++ b/editor/plugins/asset_library_editor_plugin.cpp @@ -42,6 +42,7 @@ #include "editor/gui/editor_file_dialog.h" #include "editor/project_settings_editor.h" #include "scene/gui/menu_button.h" +#include "scene/resources/image_texture.h" static inline void setup_http_request(HTTPRequest *request) { request->set_use_threads(EDITOR_DEF("asset_library/use_threads", true)); diff --git a/editor/plugins/audio_stream_editor_plugin.cpp b/editor/plugins/audio_stream_editor_plugin.cpp index e01849ff26..89579150c2 100644 --- a/editor/plugins/audio_stream_editor_plugin.cpp +++ b/editor/plugins/audio_stream_editor_plugin.cpp @@ -30,7 +30,6 @@ #include "audio_stream_editor_plugin.h" -#include "core/core_string_names.h" #include "editor/audio_stream_preview.h" #include "editor/editor_scale.h" #include "editor/editor_settings.h" @@ -195,7 +194,7 @@ void AudioStreamEditor::_seek_to(real_t p_x) { void AudioStreamEditor::set_stream(const Ref<AudioStream> &p_stream) { if (stream.is_valid()) { - stream->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(this, &AudioStreamEditor::_stream_changed)); + stream->disconnect_changed(callable_mp(this, &AudioStreamEditor::_stream_changed)); } stream = p_stream; @@ -203,7 +202,7 @@ void AudioStreamEditor::set_stream(const Ref<AudioStream> &p_stream) { hide(); return; } - stream->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &AudioStreamEditor::_stream_changed)); + stream->connect_changed(callable_mp(this, &AudioStreamEditor::_stream_changed)); _player->set_stream(stream); _current = 0; diff --git a/editor/plugins/bit_map_editor_plugin.cpp b/editor/plugins/bit_map_editor_plugin.cpp index 30fc60b0e0..3388cab006 100644 --- a/editor/plugins/bit_map_editor_plugin.cpp +++ b/editor/plugins/bit_map_editor_plugin.cpp @@ -33,6 +33,7 @@ #include "editor/editor_scale.h" #include "scene/gui/label.h" #include "scene/gui/texture_rect.h" +#include "scene/resources/image_texture.h" void BitMapEditor::setup(const Ref<BitMap> &p_bitmap) { texture_rect->set_texture(ImageTexture::create_from_image(p_bitmap->convert_to_image())); diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp index 937a0ba6ba..0386a05cc2 100644 --- a/editor/plugins/canvas_item_editor_plugin.cpp +++ b/editor/plugins/canvas_item_editor_plugin.cpp @@ -62,6 +62,7 @@ #include "scene/main/canvas_layer.h" #include "scene/main/window.h" #include "scene/resources/packed_scene.h" +#include "scene/resources/style_box_texture.h" // Min and Max are power of two in order to play nicely with successive increment. // That way, we can naturally reach a 100% zoom from boundaries. @@ -1290,7 +1291,13 @@ void CanvasItemEditor::_zoom_callback(float p_zoom_factor, Vector2 p_origin, Ref if (mb.is_valid()) { // Special behvior for scroll events, as the zoom_by_increment method can smartly end up on powers of two. int increment = p_zoom_factor > 1.0 ? 1 : -1; - zoom_widget->set_zoom_by_increments(increment, mb->is_alt_pressed()); + bool by_integer = mb->is_alt_pressed(); + + if (EDITOR_GET("editors/2d/use_integer_zoom_by_default")) { + by_integer = !by_integer; + } + + zoom_widget->set_zoom_by_increments(increment, by_integer); } else { zoom_widget->set_zoom(zoom_widget->get_zoom() * p_zoom_factor); } diff --git a/editor/plugins/canvas_item_editor_plugin.h b/editor/plugins/canvas_item_editor_plugin.h index 4e2a629c7e..2f97cda343 100644 --- a/editor/plugins/canvas_item_editor_plugin.h +++ b/editor/plugins/canvas_item_editor_plugin.h @@ -39,12 +39,13 @@ class AcceptDialog; class CanvasItemEditorViewport; class ConfirmationDialog; class EditorData; -class EditorZoomWidget; class EditorSelection; +class EditorZoomWidget; class HScrollBar; class HSplitContainer; class MenuButton; class PanelContainer; +class StyleBoxTexture; class ViewPanner; class VScrollBar; class VSplitContainer; diff --git a/editor/plugins/curve_editor_plugin.cpp b/editor/plugins/curve_editor_plugin.cpp index a1a692bdd1..6228faaa72 100644 --- a/editor/plugins/curve_editor_plugin.cpp +++ b/editor/plugins/curve_editor_plugin.cpp @@ -31,7 +31,6 @@ #include "curve_editor_plugin.h" #include "canvas_item_editor_plugin.h" -#include "core/core_string_names.h" #include "core/input/input.h" #include "core/math/geometry_2d.h" #include "core/os/keyboard.h" @@ -45,6 +44,7 @@ #include "scene/gui/menu_button.h" #include "scene/gui/popup_menu.h" #include "scene/gui/separator.h" +#include "scene/resources/image_texture.h" CurveEdit::CurveEdit() { set_focus_mode(FOCUS_ALL); @@ -61,14 +61,14 @@ void CurveEdit::set_curve(Ref<Curve> p_curve) { } if (curve.is_valid()) { - curve->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(this, &CurveEdit::_curve_changed)); + curve->disconnect_changed(callable_mp(this, &CurveEdit::_curve_changed)); curve->disconnect(Curve::SIGNAL_RANGE_CHANGED, callable_mp(this, &CurveEdit::_curve_changed)); } curve = p_curve; if (curve.is_valid()) { - curve->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &CurveEdit::_curve_changed)); + curve->connect_changed(callable_mp(this, &CurveEdit::_curve_changed)); curve->connect(Curve::SIGNAL_RANGE_CHANGED, callable_mp(this, &CurveEdit::_curve_changed)); } @@ -184,7 +184,9 @@ void CurveEdit::gui_input(const Ref<InputEvent> &p_event) { toggle_linear(selected_index, selected_tangent_index); } else { int point_to_remove = get_point_at(mpos); - if (point_to_remove != -1) { + if (point_to_remove == -1) { + set_selected_index(-1); // Nothing on the place of the click, just deselect the point. + } else { if (grabbing == GRAB_ADD) { curve->remove_point(point_to_remove); // Point is temporary, so remove directly from curve. set_selected_index(-1); diff --git a/editor/plugins/dedicated_server_export_plugin.h b/editor/plugins/dedicated_server_export_plugin.h index cb014ae52d..8991c052b3 100644 --- a/editor/plugins/dedicated_server_export_plugin.h +++ b/editor/plugins/dedicated_server_export_plugin.h @@ -40,7 +40,7 @@ private: EditorExportPreset::FileExportMode _get_export_mode_for_path(const String &p_path); protected: - String _get_name() const override { return "DedicatedServer"; } + String get_name() const override { return "DedicatedServer"; } PackedStringArray _get_export_features(const Ref<EditorExportPlatform> &p_platform, bool p_debug) const override; uint64_t _get_customization_configuration_hash() const override; diff --git a/editor/plugins/editor_preview_plugins.cpp b/editor/plugins/editor_preview_plugins.cpp index 2b0691b36f..fba45e5372 100644 --- a/editor/plugins/editor_preview_plugins.cpp +++ b/editor/plugins/editor_preview_plugins.cpp @@ -37,8 +37,11 @@ #include "editor/editor_paths.h" #include "editor/editor_scale.h" #include "editor/editor_settings.h" +#include "scene/resources/atlas_texture.h" #include "scene/resources/bit_map.h" #include "scene/resources/font.h" +#include "scene/resources/gradient_texture.h" +#include "scene/resources/image_texture.h" #include "scene/resources/material.h" #include "scene/resources/mesh.h" #include "servers/audio/audio_stream.h" diff --git a/editor/plugins/gdextension_export_plugin.h b/editor/plugins/gdextension_export_plugin.h index d1c47ab14e..54e6899796 100644 --- a/editor/plugins/gdextension_export_plugin.h +++ b/editor/plugins/gdextension_export_plugin.h @@ -36,7 +36,7 @@ class GDExtensionExportPlugin : public EditorExportPlugin { protected: virtual void _export_file(const String &p_path, const String &p_type, const HashSet<String> &p_features); - virtual String _get_name() const { return "GDExtension"; } + virtual String get_name() const { return "GDExtension"; } }; void GDExtensionExportPlugin::_export_file(const String &p_path, const String &p_type, const HashSet<String> &p_features) { diff --git a/editor/plugins/gpu_particles_2d_editor_plugin.cpp b/editor/plugins/gpu_particles_2d_editor_plugin.cpp index 8631ee05c8..c84475982d 100644 --- a/editor/plugins/gpu_particles_2d_editor_plugin.cpp +++ b/editor/plugins/gpu_particles_2d_editor_plugin.cpp @@ -39,6 +39,7 @@ #include "scene/2d/cpu_particles_2d.h" #include "scene/gui/menu_button.h" #include "scene/gui/separator.h" +#include "scene/resources/image_texture.h" #include "scene/resources/particle_process_material.h" void GPUParticles2DEditorPlugin::edit(Object *p_object) { diff --git a/editor/plugins/gpu_particles_3d_editor_plugin.cpp b/editor/plugins/gpu_particles_3d_editor_plugin.cpp index 65f66c2661..f0b2e32c72 100644 --- a/editor/plugins/gpu_particles_3d_editor_plugin.cpp +++ b/editor/plugins/gpu_particles_3d_editor_plugin.cpp @@ -38,6 +38,7 @@ #include "scene/3d/cpu_particles_3d.h" #include "scene/3d/mesh_instance_3d.h" #include "scene/gui/menu_button.h" +#include "scene/resources/image_texture.h" #include "scene/resources/particle_process_material.h" bool GPUParticles3DEditorBase::_generate(Vector<Vector3> &points, Vector<Vector3> &normals) { diff --git a/editor/plugins/gradient_editor.cpp b/editor/plugins/gradient_editor.cpp index 000db06d48..59bd0f02fc 100644 --- a/editor/plugins/gradient_editor.cpp +++ b/editor/plugins/gradient_editor.cpp @@ -34,11 +34,12 @@ #include "editor/editor_node.h" #include "editor/editor_scale.h" #include "editor/editor_undo_redo_manager.h" +#include "scene/resources/gradient_texture.h" void GradientEditor::set_gradient(const Ref<Gradient> &p_gradient) { gradient = p_gradient; connect("ramp_changed", callable_mp(this, &GradientEditor::_ramp_changed)); - gradient->connect("changed", callable_mp(this, &GradientEditor::_gradient_changed)); + gradient->connect_changed(callable_mp(this, &GradientEditor::_gradient_changed)); set_points(gradient->get_points()); set_interpolation_mode(gradient->get_interpolation_mode()); set_interpolation_color_space(gradient->get_interpolation_color_space()); diff --git a/editor/plugins/gradient_editor.h b/editor/plugins/gradient_editor.h index b78b740f4f..9ff39b2213 100644 --- a/editor/plugins/gradient_editor.h +++ b/editor/plugins/gradient_editor.h @@ -35,6 +35,8 @@ #include "scene/gui/popup.h" #include "scene/resources/gradient.h" +class GradientTexture1D; + class GradientEditor : public Control { GDCLASS(GradientEditor, Control); diff --git a/editor/plugins/gradient_texture_2d_editor_plugin.cpp b/editor/plugins/gradient_texture_2d_editor_plugin.cpp index 08de48af18..c48de7c3dd 100644 --- a/editor/plugins/gradient_texture_2d_editor_plugin.cpp +++ b/editor/plugins/gradient_texture_2d_editor_plugin.cpp @@ -38,6 +38,7 @@ #include "scene/gui/button.h" #include "scene/gui/flow_container.h" #include "scene/gui/separator.h" +#include "scene/resources/gradient_texture.h" Point2 GradientTexture2DEdit::_get_handle_pos(const Handle p_handle) { // Get the handle's mouse position in pixels relative to offset. @@ -132,7 +133,7 @@ void GradientTexture2DEdit::gui_input(const Ref<InputEvent> &p_event) { void GradientTexture2DEdit::set_texture(Ref<GradientTexture2D> &p_texture) { texture = p_texture; - texture->connect("changed", callable_mp((CanvasItem *)this, &CanvasItem::queue_redraw)); + texture->connect_changed(callable_mp((CanvasItem *)this, &CanvasItem::queue_redraw)); } void GradientTexture2DEdit::set_snap_enabled(bool p_snap_enabled) { diff --git a/editor/plugins/gradient_texture_2d_editor_plugin.h b/editor/plugins/gradient_texture_2d_editor_plugin.h index 2816b11d74..33570593cc 100644 --- a/editor/plugins/gradient_texture_2d_editor_plugin.h +++ b/editor/plugins/gradient_texture_2d_editor_plugin.h @@ -36,6 +36,7 @@ class Button; class EditorSpinSlider; +class GradientTexture2D; class GradientTexture2DEdit : public Control { GDCLASS(GradientTexture2DEdit, Control); diff --git a/editor/plugins/input_event_editor_plugin.cpp b/editor/plugins/input_event_editor_plugin.cpp index be36447432..9a54a8c1a1 100644 --- a/editor/plugins/input_event_editor_plugin.cpp +++ b/editor/plugins/input_event_editor_plugin.cpp @@ -77,7 +77,7 @@ void InputEventConfigContainer::set_event(const Ref<InputEvent> &p_event) { input_event = p_event; _event_changed(); - input_event->connect("changed", callable_mp(this, &InputEventConfigContainer::_event_changed)); + input_event->connect_changed(callable_mp(this, &InputEventConfigContainer::_event_changed)); } InputEventConfigContainer::InputEventConfigContainer() { diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp index d026916668..68d3661d10 100644 --- a/editor/plugins/node_3d_editor_plugin.cpp +++ b/editor/plugins/node_3d_editor_plugin.cpp @@ -4929,7 +4929,7 @@ void Node3DEditorViewport::register_shortcut_action(const String &p_path, const Ref<Shortcut> sc = ED_SHORTCUT(p_path, p_name, p_keycode, p_physical); shortcut_changed_callback(sc, p_path); // Connect to the change event on the shortcut so the input binding can be updated. - sc->connect("changed", callable_mp(this, &Node3DEditorViewport::shortcut_changed_callback).bind(sc, p_path)); + sc->connect_changed(callable_mp(this, &Node3DEditorViewport::shortcut_changed_callback).bind(sc, p_path)); } // Update the action in the InputMap to the provided shortcut events. diff --git a/editor/plugins/polygon_3d_editor_plugin.cpp b/editor/plugins/polygon_3d_editor_plugin.cpp index efbb2b0d2b..ceff9cb5f3 100644 --- a/editor/plugins/polygon_3d_editor_plugin.cpp +++ b/editor/plugins/polygon_3d_editor_plugin.cpp @@ -30,7 +30,6 @@ #include "polygon_3d_editor_plugin.h" -#include "core/core_string_names.h" #include "core/input/input.h" #include "core/io/file_access.h" #include "core/math/geometry_2d.h" @@ -497,7 +496,7 @@ void Polygon3DEditor::edit(Node *p_node) { node_resource = node->call("_get_editable_3d_polygon_resource"); if (node_resource.is_valid()) { - node_resource->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &Polygon3DEditor::_polygon_draw)); + node_resource->connect_changed(callable_mp(this, &Polygon3DEditor::_polygon_draw)); } //Enable the pencil tool if the polygon is empty if (_get_polygon().is_empty()) { @@ -518,7 +517,7 @@ void Polygon3DEditor::edit(Node *p_node) { } else { node = nullptr; if (node_resource.is_valid()) { - node_resource->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(this, &Polygon3DEditor::_polygon_draw)); + node_resource->disconnect_changed(callable_mp(this, &Polygon3DEditor::_polygon_draw)); } node_resource.unref(); diff --git a/editor/plugins/script_editor_plugin.cpp b/editor/plugins/script_editor_plugin.cpp index 791937dbf6..78ba26503d 100644 --- a/editor/plugins/script_editor_plugin.cpp +++ b/editor/plugins/script_editor_plugin.cpp @@ -2435,6 +2435,18 @@ bool ScriptEditor::edit(const Ref<Resource> &p_resource, int p_line, int p_col, return true; } +PackedStringArray ScriptEditor::get_unsaved_scripts() const { + PackedStringArray unsaved_list; + + for (int i = 0; i < tab_container->get_tab_count(); i++) { + ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i)); + if (se && se->is_unsaved()) { + unsaved_list.append(se->get_name()); + } + } + return unsaved_list; +} + void ScriptEditor::save_current_script() { ScriptEditorBase *current = _get_current_editor(); if (!current || _test_script_times_on_disk()) { @@ -4207,8 +4219,53 @@ void ScriptEditorPlugin::selected_notify() { _focus_another_editor(); } +String ScriptEditorPlugin::get_unsaved_status(const String &p_for_scene) const { + const PackedStringArray unsaved_scripts = script_editor->get_unsaved_scripts(); + if (unsaved_scripts.is_empty()) { + return String(); + } + + PackedStringArray message; + if (!p_for_scene.is_empty()) { + PackedStringArray unsaved_built_in_scripts; + + const String scene_file = p_for_scene.get_file(); + for (const String &E : unsaved_scripts) { + if (!E.is_resource_file() && E.contains(scene_file)) { + unsaved_built_in_scripts.append(E); + } + } + + if (unsaved_built_in_scripts.is_empty()) { + return String(); + } else { + message.resize(unsaved_built_in_scripts.size() + 1); + message.write[0] = TTR("There are unsaved changes in the following built-in script(s):"); + + int i = 1; + for (const String &E : unsaved_built_in_scripts) { + message.write[i] = E.trim_suffix("(*)"); + i++; + } + return String("\n").join(message); + } + } + + message.resize(unsaved_scripts.size() + 1); + message.write[0] = TTR("Save changes to the following script(s) before quitting?"); + + int i = 1; + for (const String &E : unsaved_scripts) { + message.write[i] = E.trim_suffix("(*)"); + i++; + } + return String("\n").join(message); +} + void ScriptEditorPlugin::save_external_data() { - script_editor->save_all_scripts(); + if (!EditorNode::get_singleton()->is_exiting()) { + script_editor->save_all_scripts(); + } } void ScriptEditorPlugin::apply_changes() { diff --git a/editor/plugins/script_editor_plugin.h b/editor/plugins/script_editor_plugin.h index e879920e41..198aaa6c4e 100644 --- a/editor/plugins/script_editor_plugin.h +++ b/editor/plugins/script_editor_plugin.h @@ -512,6 +512,7 @@ public: void get_breakpoints(List<String> *p_breakpoints); + PackedStringArray get_unsaved_scripts() const; void save_current_script(); void save_all_scripts(); @@ -572,6 +573,7 @@ public: virtual void make_visible(bool p_visible) override; virtual void selected_notify() override; + virtual String get_unsaved_status(const String &p_for_scene) const override; virtual void save_external_data() override; virtual void apply_changes() override; diff --git a/editor/plugins/script_text_editor.cpp b/editor/plugins/script_text_editor.cpp index 5fdfb21176..321c0f34b5 100644 --- a/editor/plugins/script_text_editor.cpp +++ b/editor/plugins/script_text_editor.cpp @@ -282,6 +282,22 @@ void ScriptTextEditor::_error_clicked(Variant p_line) { if (p_line.get_type() == Variant::INT) { code_editor->get_text_editor()->remove_secondary_carets(); code_editor->get_text_editor()->set_caret_line(p_line.operator int64_t()); + } else if (p_line.get_type() == Variant::DICTIONARY) { + Dictionary meta = p_line.operator Dictionary(); + const String path = meta["path"].operator String(); + const int line = meta["line"].operator int64_t(); + const int column = meta["column"].operator int64_t(); + if (path.is_empty()) { + code_editor->get_text_editor()->remove_secondary_carets(); + code_editor->get_text_editor()->set_caret_line(line); + } else { + Ref<Resource> scr = ResourceLoader::load(path); + if (!scr.is_valid()) { + EditorNode::get_singleton()->show_warning(TTR("Could not load file at:") + "\n\n" + path, TTR("Error!")); + } else { + ScriptEditor::get_singleton()->edit(scr, line, column); + } + } } } @@ -458,9 +474,17 @@ void ScriptTextEditor::_validate_script() { warnings.clear(); errors.clear(); + depended_errors.clear(); safe_lines.clear(); if (!script->get_language()->validate(text, script->get_path(), &fnc, &errors, &warnings, &safe_lines)) { + for (List<ScriptLanguage::ScriptError>::Element *E = errors.front(); E; E = E->next()) { + if (E->get().path.is_empty() || E->get().path != script->get_path()) { + depended_errors[E->get().path].push_back(E->get()); + E->erase(); + } + } + // TRANSLATORS: Script error pointing to a line and column number. String error_text = vformat(TTR("Error at (%d, %d):"), errors[0].line, errors[0].column) + " " + errors[0].message; code_editor->set_error(error_text); @@ -560,6 +584,10 @@ void ScriptTextEditor::_update_errors() { errors_panel->clear(); errors_panel->push_table(2); for (const ScriptLanguage::ScriptError &err : errors) { + Dictionary click_meta; + click_meta["line"] = err.line; + click_meta["column"] = err.column; + errors_panel->push_cell(); errors_panel->push_meta(err.line - 1); errors_panel->push_color(warnings_panel->get_theme_color(SNAME("error_color"), SNAME("Editor"))); @@ -575,6 +603,41 @@ void ScriptTextEditor::_update_errors() { } errors_panel->pop(); // Table + for (const KeyValue<String, List<ScriptLanguage::ScriptError>> &KV : depended_errors) { + Dictionary click_meta; + click_meta["path"] = KV.key; + click_meta["line"] = 1; + + errors_panel->add_newline(); + errors_panel->add_newline(); + errors_panel->push_meta(click_meta); + errors_panel->add_text(vformat(R"(%s:)", KV.key)); + errors_panel->pop(); // Meta goto. + errors_panel->add_newline(); + + errors_panel->push_indent(1); + errors_panel->push_table(2); + String filename = KV.key.get_file(); + for (const ScriptLanguage::ScriptError &err : KV.value) { + click_meta["line"] = err.line; + click_meta["column"] = err.column; + + errors_panel->push_cell(); + errors_panel->push_meta(click_meta); + errors_panel->push_color(errors_panel->get_theme_color(SNAME("error_color"), SNAME("Editor"))); + errors_panel->add_text(TTR("Line") + " " + itos(err.line) + ":"); + errors_panel->pop(); // Color. + errors_panel->pop(); // Meta goto. + errors_panel->pop(); // Cell. + + errors_panel->push_cell(); + errors_panel->add_text(err.message); + errors_panel->pop(); // Cell. + } + errors_panel->pop(); // Table + errors_panel->pop(); // Indent. + } + CodeEdit *te = code_editor->get_text_editor(); bool highlight_safe = EDITOR_GET("text_editor/appearance/gutters/highlight_type_safe_lines"); bool last_is_safe = false; @@ -811,6 +874,8 @@ void ScriptTextEditor::_lookup_symbol(const String &p_symbol, int p_row, int p_c } ScriptLanguage::LookupResult result; + String code_text = code_editor->get_text_editor()->get_text_with_cursor_char(p_row, p_column); + Error lc_error = script->get_language()->lookup_code(code_text, p_symbol, script->get_path(), base, result); if (ScriptServer::is_global_class(p_symbol)) { EditorNode::get_singleton()->load_resource(ScriptServer::get_global_class_path(p_symbol)); } else if (p_symbol.is_resource_file()) { @@ -823,7 +888,7 @@ void ScriptTextEditor::_lookup_symbol(const String &p_symbol, int p_row, int p_c EditorNode::get_singleton()->load_resource(p_symbol); } - } else if (script->get_language()->lookup_code(code_editor->get_text_editor()->get_text_for_symbol_lookup(), p_symbol, script->get_path(), base, result) == OK) { + } else if (lc_error == OK) { _goto_line(p_row); switch (result.type) { @@ -944,7 +1009,10 @@ void ScriptTextEditor::_validate_symbol(const String &p_symbol) { } ScriptLanguage::LookupResult result; - if (ScriptServer::is_global_class(p_symbol) || p_symbol.is_resource_file() || script->get_language()->lookup_code(code_editor->get_text_editor()->get_text_for_symbol_lookup(), p_symbol, script->get_path(), base, result) == OK || (ProjectSettings::get_singleton()->has_autoload(p_symbol) && ProjectSettings::get_singleton()->get_autoload(p_symbol).is_singleton)) { + String lc_text = code_editor->get_text_editor()->get_text_for_symbol_lookup(); + Error lc_error = script->get_language()->lookup_code(lc_text, p_symbol, script->get_path(), base, result); + bool is_singleton = ProjectSettings::get_singleton()->has_autoload(p_symbol) && ProjectSettings::get_singleton()->get_autoload(p_symbol).is_singleton; + if (ScriptServer::is_global_class(p_symbol) || p_symbol.is_resource_file() || lc_error == OK || is_singleton) { text_edit->set_symbol_lookup_word_as_valid(true); } else if (p_symbol.is_relative_path()) { String path = _get_absolute_path(p_symbol); @@ -1726,9 +1794,17 @@ void ScriptTextEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data String variable_name = String(node->get_name()).to_snake_case().validate_identifier(); if (use_type) { - text_to_drop += vformat("@onready var %s: %s = %s%s\n", variable_name, node->get_class_name(), is_unique ? "%" : "$", path); + StringName class_name = node->get_class_name(); + Ref<Script> node_script = node->get_script(); + if (node_script.is_valid()) { + StringName global_node_script_name = node_script->get_global_name(); + if (global_node_script_name != StringName()) { + class_name = global_node_script_name; + } + } + text_to_drop += vformat("@onready var %s: %s = %c%s\n", variable_name, class_name, is_unique ? '%' : '$', path); } else { - text_to_drop += vformat("@onready var %s = %s%s\n", variable_name, is_unique ? "%" : "$", path); + text_to_drop += vformat("@onready var %s = %c%s\n", variable_name, is_unique ? '%' : '$', path); } } } else { @@ -1841,7 +1917,7 @@ void ScriptTextEditor::_text_edit_gui_input(const Ref<InputEvent> &ev) { bool open_docs = false; bool goto_definition = false; - if (word_at_pos.is_resource_file()) { + if (ScriptServer::is_global_class(word_at_pos) || word_at_pos.is_resource_file()) { open_docs = true; } else { Node *base = get_tree()->get_edited_scene_root(); diff --git a/editor/plugins/script_text_editor.h b/editor/plugins/script_text_editor.h index 808a6417e4..d275013b91 100644 --- a/editor/plugins/script_text_editor.h +++ b/editor/plugins/script_text_editor.h @@ -68,6 +68,7 @@ class ScriptTextEditor : public ScriptEditorBase { Vector<String> functions; List<ScriptLanguage::Warning> warnings; List<ScriptLanguage::ScriptError> errors; + HashMap<String, List<ScriptLanguage::ScriptError>> depended_errors; HashSet<int> safe_lines; List<Connection> missing_connections; diff --git a/editor/plugins/shader_editor_plugin.cpp b/editor/plugins/shader_editor_plugin.cpp index 268828e8f5..247586fbfc 100644 --- a/editor/plugins/shader_editor_plugin.cpp +++ b/editor/plugins/shader_editor_plugin.cpp @@ -287,6 +287,27 @@ void ShaderEditorPlugin::get_window_layout(Ref<ConfigFile> p_layout) { p_layout->set_value("ShaderEditor", "selected_shader", selected_shader); } +String ShaderEditorPlugin::get_unsaved_status(const String &p_for_scene) const { + if (!p_for_scene.is_empty()) { + // TODO: handle built-in shaders. + return String(); + } + + // TODO: This should also include visual shaders and shader includes, but save_external_data() doesn't seem to save them... + PackedStringArray unsaved_shaders; + for (uint32_t i = 0; i < edited_shaders.size(); i++) { + if (edited_shaders[i].shader_editor) { + if (edited_shaders[i].shader_editor->is_unsaved()) { + if (unsaved_shaders.is_empty()) { + unsaved_shaders.append(TTR("Save changes to the following shaders(s) before quitting?")); + } + unsaved_shaders.append(edited_shaders[i].shader_editor->get_name()); + } + } + } + return String("\n").join(unsaved_shaders); +} + void ShaderEditorPlugin::save_external_data() { for (EditedShader &edited_shader : edited_shaders) { if (edited_shader.shader_editor) { diff --git a/editor/plugins/shader_editor_plugin.h b/editor/plugins/shader_editor_plugin.h index 45b48a2f91..fb7b283266 100644 --- a/editor/plugins/shader_editor_plugin.h +++ b/editor/plugins/shader_editor_plugin.h @@ -115,6 +115,7 @@ public: virtual void set_window_layout(Ref<ConfigFile> p_layout) override; virtual void get_window_layout(Ref<ConfigFile> p_layout) override; + virtual String get_unsaved_status(const String &p_for_scene) const override; virtual void save_external_data() override; virtual void apply_changes() override; diff --git a/editor/plugins/shader_file_editor_plugin.cpp b/editor/plugins/shader_file_editor_plugin.cpp index f9aa14dd09..b0e532b136 100644 --- a/editor/plugins/shader_file_editor_plugin.cpp +++ b/editor/plugins/shader_file_editor_plugin.cpp @@ -222,7 +222,7 @@ void ShaderFileEditor::_bind_methods() { void ShaderFileEditor::edit(const Ref<RDShaderFile> &p_shader) { if (p_shader.is_null()) { if (shader_file.is_valid()) { - shader_file->disconnect("changed", callable_mp(this, &ShaderFileEditor::_shader_changed)); + shader_file->disconnect_changed(callable_mp(this, &ShaderFileEditor::_shader_changed)); } return; } @@ -234,7 +234,7 @@ void ShaderFileEditor::edit(const Ref<RDShaderFile> &p_shader) { shader_file = p_shader; if (shader_file.is_valid()) { - shader_file->connect("changed", callable_mp(this, &ShaderFileEditor::_shader_changed)); + shader_file->connect_changed(callable_mp(this, &ShaderFileEditor::_shader_changed)); } _update_options(); diff --git a/editor/plugins/sprite_frames_editor_plugin.cpp b/editor/plugins/sprite_frames_editor_plugin.cpp index 563398e512..7b35351457 100644 --- a/editor/plugins/sprite_frames_editor_plugin.cpp +++ b/editor/plugins/sprite_frames_editor_plugin.cpp @@ -46,6 +46,7 @@ #include "scene/gui/option_button.h" #include "scene/gui/panel_container.h" #include "scene/gui/separator.h" +#include "scene/resources/atlas_texture.h" static void _draw_shadowed_line(Control *p_control, const Point2 &p_from, const Size2 &p_size, const Size2 &p_shadow_offset, Color p_color, Color p_shadow_color) { p_control->draw_line(p_from, p_from + p_size, p_color); @@ -948,13 +949,16 @@ void SpriteFramesEditor::_animation_name_edited() { String name = new_name; int counter = 0; while (frames->has_animation(name)) { + if (name == String(edited_anim)) { + edited->set_text(0, name); // The name didn't change, just updated the column text to name. + return; + } counter++; name = new_name + "_" + itos(counter); } EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Rename Animation"), UndoRedo::MERGE_DISABLE, EditorNode::get_singleton()->get_edited_scene()); - _rename_node_animation(undo_redo, false, edited_anim, "", ""); undo_redo->add_do_method(frames.ptr(), "rename_animation", edited_anim, name); undo_redo->add_undo_method(frames.ptr(), "rename_animation", name, edited_anim); _rename_node_animation(undo_redo, false, edited_anim, name, name); @@ -1208,30 +1212,29 @@ void SpriteFramesEditor::_update_library(bool p_skip_selector) { bool searching = anim_search_box->get_text().size(); String searched_string = searching ? anim_search_box->get_text().to_lower() : String(); + TreeItem *selected = nullptr; for (const StringName &E : anim_names) { String name = E; - if (searching && name.to_lower().find(searched_string) < 0) { continue; } - TreeItem *it = animations->create_item(anim_root); - it->set_metadata(0, name); - it->set_text(0, name); it->set_editable(0, true); - if (animated_sprite) { if (name == String(animated_sprite->call("get_autoplay"))) { it->set_icon(0, autoplay_icon); } } - if (E == edited_anim) { it->select(0); + selected = it; } } + if (selected) { + animations->scroll_to_item(selected); + } } if (animated_sprite) { diff --git a/editor/plugins/sprite_frames_editor_plugin.h b/editor/plugins/sprite_frames_editor_plugin.h index a53f8f73d9..ed75be9061 100644 --- a/editor/plugins/sprite_frames_editor_plugin.h +++ b/editor/plugins/sprite_frames_editor_plugin.h @@ -44,6 +44,7 @@ #include "scene/gui/split_container.h" #include "scene/gui/texture_rect.h" #include "scene/gui/tree.h" +#include "scene/resources/image_texture.h" class OptionButton; class EditorFileDialog; diff --git a/editor/plugins/style_box_editor_plugin.cpp b/editor/plugins/style_box_editor_plugin.cpp index c126aec008..1d14f5e60b 100644 --- a/editor/plugins/style_box_editor_plugin.cpp +++ b/editor/plugins/style_box_editor_plugin.cpp @@ -32,6 +32,7 @@ #include "editor/editor_scale.h" #include "scene/gui/button.h" +#include "scene/resources/style_box_texture.h" bool StyleBoxPreview::grid_preview_enabled = true; @@ -42,11 +43,11 @@ void StyleBoxPreview::_grid_preview_toggled(bool p_active) { void StyleBoxPreview::edit(const Ref<StyleBox> &p_stylebox) { if (stylebox.is_valid()) { - stylebox->disconnect("changed", callable_mp((CanvasItem *)this, &CanvasItem::queue_redraw)); + stylebox->disconnect_changed(callable_mp((CanvasItem *)this, &CanvasItem::queue_redraw)); } stylebox = p_stylebox; if (stylebox.is_valid()) { - stylebox->connect("changed", callable_mp((CanvasItem *)this, &CanvasItem::queue_redraw)); + stylebox->connect_changed(callable_mp((CanvasItem *)this, &CanvasItem::queue_redraw)); } Ref<StyleBoxTexture> sbt = stylebox; grid_preview->set_visible(sbt.is_valid()); diff --git a/editor/plugins/text_shader_editor.cpp b/editor/plugins/text_shader_editor.cpp index 4838661fa8..ed98c7f85c 100644 --- a/editor/plugins/text_shader_editor.cpp +++ b/editor/plugins/text_shader_editor.cpp @@ -122,7 +122,7 @@ void ShaderTextEditor::set_edited_shader(const Ref<Shader> &p_shader, const Stri return; } if (shader.is_valid()) { - shader->disconnect(SNAME("changed"), callable_mp(this, &ShaderTextEditor::_shader_changed)); + shader->disconnect_changed(callable_mp(this, &ShaderTextEditor::_shader_changed)); } shader = p_shader; shader_inc = Ref<ShaderInclude>(); @@ -130,7 +130,7 @@ void ShaderTextEditor::set_edited_shader(const Ref<Shader> &p_shader, const Stri set_edited_code(p_code); if (shader.is_valid()) { - shader->connect(SNAME("changed"), callable_mp(this, &ShaderTextEditor::_shader_changed)); + shader->connect_changed(callable_mp(this, &ShaderTextEditor::_shader_changed)); } } @@ -152,7 +152,7 @@ void ShaderTextEditor::set_edited_shader_include(const Ref<ShaderInclude> &p_sha return; } if (shader_inc.is_valid()) { - shader_inc->disconnect(SNAME("changed"), callable_mp(this, &ShaderTextEditor::_shader_changed)); + shader_inc->disconnect_changed(callable_mp(this, &ShaderTextEditor::_shader_changed)); } shader_inc = p_shader_inc; shader = Ref<Shader>(); @@ -160,7 +160,7 @@ void ShaderTextEditor::set_edited_shader_include(const Ref<ShaderInclude> &p_sha set_edited_code(p_code); if (shader_inc.is_valid()) { - shader_inc->connect(SNAME("changed"), callable_mp(this, &ShaderTextEditor::_shader_changed)); + shader_inc->connect_changed(callable_mp(this, &ShaderTextEditor::_shader_changed)); } } diff --git a/editor/plugins/texture_3d_editor_plugin.cpp b/editor/plugins/texture_3d_editor_plugin.cpp index 9904b888f2..2702e94188 100644 --- a/editor/plugins/texture_3d_editor_plugin.cpp +++ b/editor/plugins/texture_3d_editor_plugin.cpp @@ -115,7 +115,7 @@ void Texture3DEditor::_texture_rect_update_area() { void Texture3DEditor::edit(Ref<Texture3D> p_texture) { if (!texture.is_null()) { - texture->disconnect("changed", callable_mp(this, &Texture3DEditor::_texture_changed)); + texture->disconnect_changed(callable_mp(this, &Texture3DEditor::_texture_changed)); } texture = p_texture; @@ -125,7 +125,7 @@ void Texture3DEditor::edit(Ref<Texture3D> p_texture) { _make_shaders(); } - texture->connect("changed", callable_mp(this, &Texture3DEditor::_texture_changed)); + texture->connect_changed(callable_mp(this, &Texture3DEditor::_texture_changed)); queue_redraw(); texture_rect->set_material(material); setting = true; @@ -176,7 +176,7 @@ Texture3DEditor::Texture3DEditor() { Texture3DEditor::~Texture3DEditor() { if (!texture.is_null()) { - texture->disconnect("changed", callable_mp(this, &Texture3DEditor::_texture_changed)); + texture->disconnect_changed(callable_mp(this, &Texture3DEditor::_texture_changed)); } } diff --git a/editor/plugins/texture_editor_plugin.cpp b/editor/plugins/texture_editor_plugin.cpp index 1a9e09f3b1..87b207ebcd 100644 --- a/editor/plugins/texture_editor_plugin.cpp +++ b/editor/plugins/texture_editor_plugin.cpp @@ -29,9 +29,14 @@ /**************************************************************************/ #include "texture_editor_plugin.h" + #include "editor/editor_scale.h" #include "scene/gui/label.h" #include "scene/gui/texture_rect.h" +#include "scene/resources/animated_texture.h" +#include "scene/resources/atlas_texture.h" +#include "scene/resources/compressed_texture.h" +#include "scene/resources/image_texture.h" TextureRect *TexturePreview::get_texture_display() { return texture_display; @@ -135,7 +140,7 @@ TexturePreview::TexturePreview(Ref<Texture2D> p_texture, bool p_show_metadata) { metadata_label = memnew(Label); _update_metadata_label_text(); - p_texture->connect("changed", callable_mp(this, &TexturePreview::_update_metadata_label_text)); + p_texture->connect_changed(callable_mp(this, &TexturePreview::_update_metadata_label_text)); // It's okay that these colors are static since the grid color is static too. metadata_label->add_theme_color_override("font_color", Color::named("white")); diff --git a/editor/plugins/texture_layered_editor_plugin.cpp b/editor/plugins/texture_layered_editor_plugin.cpp index 816d081617..a0188b08e5 100644 --- a/editor/plugins/texture_layered_editor_plugin.cpp +++ b/editor/plugins/texture_layered_editor_plugin.cpp @@ -181,7 +181,7 @@ void TextureLayeredEditor::_texture_rect_update_area() { void TextureLayeredEditor::edit(Ref<TextureLayered> p_texture) { if (!texture.is_null()) { - texture->disconnect("changed", callable_mp(this, &TextureLayeredEditor::_texture_changed)); + texture->disconnect_changed(callable_mp(this, &TextureLayeredEditor::_texture_changed)); } texture = p_texture; @@ -191,7 +191,7 @@ void TextureLayeredEditor::edit(Ref<TextureLayered> p_texture) { _make_shaders(); } - texture->connect("changed", callable_mp(this, &TextureLayeredEditor::_texture_changed)); + texture->connect_changed(callable_mp(this, &TextureLayeredEditor::_texture_changed)); queue_redraw(); texture_rect->set_material(materials[texture->get_layered_type()]); setting = true; diff --git a/editor/plugins/texture_region_editor_plugin.cpp b/editor/plugins/texture_region_editor_plugin.cpp index f2e650a604..19df31a0b3 100644 --- a/editor/plugins/texture_region_editor_plugin.cpp +++ b/editor/plugins/texture_region_editor_plugin.cpp @@ -30,7 +30,6 @@ #include "texture_region_editor_plugin.h" -#include "core/core_string_names.h" #include "core/input/input.h" #include "core/os/keyboard.h" #include "editor/editor_node.h" @@ -42,7 +41,7 @@ #include "scene/gui/separator.h" #include "scene/gui/spin_box.h" #include "scene/gui/view_panner.h" -#include "scene/resources/texture.h" +#include "scene/resources/atlas_texture.h" void draw_margin_line(Control *edit_draw, Vector2 from, Vector2 to) { Vector2 line = (to - from).normalized() * 10; @@ -433,7 +432,7 @@ void TextureRegionEditor::_region_input(const Ref<InputEvent> &p_input) { } else if (obj_styleBox.is_valid()) { undo_redo->add_do_method(obj_styleBox.ptr(), "set_texture_margin", side[edited_margin], obj_styleBox->get_texture_margin(side[edited_margin])); undo_redo->add_undo_method(obj_styleBox.ptr(), "set_texture_margin", side[edited_margin], prev_margin); - obj_styleBox->emit_signal(CoreStringNames::get_singleton()->changed); + obj_styleBox->emit_changed(); } edited_margin = -1; } else { @@ -913,10 +912,10 @@ void TextureRegionEditor::edit(Object *p_obj) { node_ninepatch->disconnect("texture_changed", callable_mp(this, &TextureRegionEditor::_texture_changed)); } if (obj_styleBox.is_valid()) { - obj_styleBox->disconnect("changed", callable_mp(this, &TextureRegionEditor::_texture_changed)); + obj_styleBox->disconnect_changed(callable_mp(this, &TextureRegionEditor::_texture_changed)); } if (atlas_tex.is_valid()) { - atlas_tex->disconnect("changed", callable_mp(this, &TextureRegionEditor::_texture_changed)); + atlas_tex->disconnect_changed(callable_mp(this, &TextureRegionEditor::_texture_changed)); } node_sprite_2d = nullptr; @@ -941,7 +940,7 @@ void TextureRegionEditor::edit(Object *p_obj) { } if (is_resource) { - p_obj->connect("changed", callable_mp(this, &TextureRegionEditor::_texture_changed)); + Object::cast_to<Resource>(p_obj)->connect_changed(callable_mp(this, &TextureRegionEditor::_texture_changed)); } else { p_obj->connect("texture_changed", callable_mp(this, &TextureRegionEditor::_texture_changed)); } diff --git a/editor/plugins/texture_region_editor_plugin.h b/editor/plugins/texture_region_editor_plugin.h index c303cec3f5..6b7a198246 100644 --- a/editor/plugins/texture_region_editor_plugin.h +++ b/editor/plugins/texture_region_editor_plugin.h @@ -38,11 +38,11 @@ #include "scene/3d/sprite_3d.h" #include "scene/gui/dialogs.h" #include "scene/gui/nine_patch_rect.h" -#include "scene/resources/style_box.h" -#include "scene/resources/texture.h" +#include "scene/resources/style_box_texture.h" -class ViewPanner; +class AtlasTexture; class OptionButton; +class ViewPanner; class TextureRegionEditor : public AcceptDialog { GDCLASS(TextureRegionEditor, AcceptDialog); diff --git a/editor/plugins/theme_editor_plugin.cpp b/editor/plugins/theme_editor_plugin.cpp index 09053db122..a1ddfc4b85 100644 --- a/editor/plugins/theme_editor_plugin.cpp +++ b/editor/plugins/theme_editor_plugin.cpp @@ -3151,7 +3151,7 @@ void ThemeTypeEditor::_stylebox_item_changed(Ref<StyleBox> p_value, String p_ite void ThemeTypeEditor::_change_pinned_stylebox() { if (leading_stylebox.pinned) { if (leading_stylebox.stylebox.is_valid()) { - leading_stylebox.stylebox->disconnect("changed", callable_mp(this, &ThemeTypeEditor::_update_stylebox_from_leading)); + leading_stylebox.stylebox->disconnect_changed(callable_mp(this, &ThemeTypeEditor::_update_stylebox_from_leading)); } Ref<StyleBox> new_stylebox = edited_theme->get_stylebox(leading_stylebox.item_name, edited_type); @@ -3159,10 +3159,10 @@ void ThemeTypeEditor::_change_pinned_stylebox() { leading_stylebox.ref_stylebox = (new_stylebox.is_valid() ? new_stylebox->duplicate() : Ref<Resource>()); if (leading_stylebox.stylebox.is_valid()) { - new_stylebox->connect("changed", callable_mp(this, &ThemeTypeEditor::_update_stylebox_from_leading)); + new_stylebox->connect_changed(callable_mp(this, &ThemeTypeEditor::_update_stylebox_from_leading)); } } else if (leading_stylebox.stylebox.is_valid()) { - leading_stylebox.stylebox->disconnect("changed", callable_mp(this, &ThemeTypeEditor::_update_stylebox_from_leading)); + leading_stylebox.stylebox->disconnect_changed(callable_mp(this, &ThemeTypeEditor::_update_stylebox_from_leading)); } } @@ -3187,7 +3187,7 @@ void ThemeTypeEditor::_on_pin_leader_button_pressed(Control *p_editor, String p_ void ThemeTypeEditor::_pin_leading_stylebox(String p_item_name, Ref<StyleBox> p_stylebox) { if (leading_stylebox.stylebox.is_valid()) { - leading_stylebox.stylebox->disconnect("changed", callable_mp(this, &ThemeTypeEditor::_update_stylebox_from_leading)); + leading_stylebox.stylebox->disconnect_changed(callable_mp(this, &ThemeTypeEditor::_update_stylebox_from_leading)); } LeadingStylebox leader; @@ -3198,7 +3198,7 @@ void ThemeTypeEditor::_pin_leading_stylebox(String p_item_name, Ref<StyleBox> p_ leading_stylebox = leader; if (p_stylebox.is_valid()) { - p_stylebox->connect("changed", callable_mp(this, &ThemeTypeEditor::_update_stylebox_from_leading)); + p_stylebox->connect_changed(callable_mp(this, &ThemeTypeEditor::_update_stylebox_from_leading)); } _update_type_items(); @@ -3214,7 +3214,7 @@ void ThemeTypeEditor::_on_unpin_leader_button_pressed() { void ThemeTypeEditor::_unpin_leading_stylebox() { if (leading_stylebox.stylebox.is_valid()) { - leading_stylebox.stylebox->disconnect("changed", callable_mp(this, &ThemeTypeEditor::_update_stylebox_from_leading)); + leading_stylebox.stylebox->disconnect_changed(callable_mp(this, &ThemeTypeEditor::_update_stylebox_from_leading)); } LeadingStylebox leader; @@ -3337,12 +3337,12 @@ void ThemeTypeEditor::_bind_methods() { void ThemeTypeEditor::set_edited_theme(const Ref<Theme> &p_theme) { if (edited_theme.is_valid()) { - edited_theme->disconnect("changed", callable_mp(this, &ThemeTypeEditor::_update_type_list_debounced)); + edited_theme->disconnect_changed(callable_mp(this, &ThemeTypeEditor::_update_type_list_debounced)); } edited_theme = p_theme; if (edited_theme.is_valid()) { - edited_theme->connect("changed", callable_mp(this, &ThemeTypeEditor::_update_type_list_debounced)); + edited_theme->connect_changed(callable_mp(this, &ThemeTypeEditor::_update_type_list_debounced)); _update_type_list(); } diff --git a/editor/plugins/theme_editor_preview.cpp b/editor/plugins/theme_editor_preview.cpp index f4a6c4af2d..fb8cb57d4d 100644 --- a/editor/plugins/theme_editor_preview.cpp +++ b/editor/plugins/theme_editor_preview.cpp @@ -321,7 +321,7 @@ DefaultThemeEditorPreview::DefaultThemeEditorPreview() { first_vb->add_child(bt); Button *tb = memnew(Button); tb->set_flat(true); - tb->set_text("Button"); + tb->set_text("Flat Button"); first_vb->add_child(tb); CheckButton *cb = memnew(CheckButton); diff --git a/editor/plugins/tiles/atlas_merging_dialog.cpp b/editor/plugins/tiles/atlas_merging_dialog.cpp index 7ed9c9d61b..937480eb50 100644 --- a/editor/plugins/tiles/atlas_merging_dialog.cpp +++ b/editor/plugins/tiles/atlas_merging_dialog.cpp @@ -36,6 +36,7 @@ #include "editor/gui/editor_file_dialog.h" #include "scene/gui/control.h" #include "scene/gui/split_container.h" +#include "scene/resources/image_texture.h" void AtlasMergingDialog::_property_changed(const StringName &p_property, const Variant &p_value, const String &p_field, bool p_changing) { _set(p_property, p_value); diff --git a/editor/plugins/tiles/tile_data_editors.cpp b/editor/plugins/tiles/tile_data_editors.cpp index 7767831ea3..24e61d7dc9 100644 --- a/editor/plugins/tiles/tile_data_editors.cpp +++ b/editor/plugins/tiles/tile_data_editors.cpp @@ -88,11 +88,11 @@ void TileDataEditor::_bind_methods() { void TileDataEditor::set_tile_set(Ref<TileSet> p_tile_set) { if (tile_set.is_valid()) { - tile_set->disconnect("changed", callable_mp(this, &TileDataEditor::_tile_set_changed_plan_update)); + tile_set->disconnect_changed(callable_mp(this, &TileDataEditor::_tile_set_changed_plan_update)); } tile_set = p_tile_set; if (tile_set.is_valid()) { - tile_set->connect("changed", callable_mp(this, &TileDataEditor::_tile_set_changed_plan_update)); + tile_set->connect_changed(callable_mp(this, &TileDataEditor::_tile_set_changed_plan_update)); } _tile_set_changed_plan_update(); } diff --git a/editor/plugins/tiles/tile_map_editor.cpp b/editor/plugins/tiles/tile_map_editor.cpp index aac7fb3b84..700e2abf95 100644 --- a/editor/plugins/tiles/tile_map_editor.cpp +++ b/editor/plugins/tiles/tile_map_editor.cpp @@ -766,9 +766,7 @@ void TileMapEditorTilesPlugin::forward_canvas_draw_over_viewport(Control *p_over for (int y = rect.position.y; y < rect.get_end().y; y++) { Vector2i coords = Vector2i(x, y); if (tile_map->get_cell_source_id(tile_map_layer, coords) != TileSet::INVALID_SOURCE) { - Transform2D tile_xform; - tile_xform.set_origin(tile_map->map_to_local(coords)); - tile_xform.set_scale(tile_shape_size); + Transform2D tile_xform(0, tile_shape_size, 0, tile_map->map_to_local(coords)); tile_set->draw_tile_shape(p_overlay, xform * tile_xform, Color(1.0, 1.0, 1.0), false); } } @@ -784,6 +782,8 @@ void TileMapEditorTilesPlugin::forward_canvas_draw_over_viewport(Control *p_over if (tile_map->get_cell_source_id(tile_map_layer, coords) != TileSet::INVALID_SOURCE) { to_draw.insert(coords); } + Transform2D tile_xform(0, tile_shape_size, 0, tile_map->map_to_local(coords)); + tile_set->draw_tile_shape(p_overlay, xform * tile_xform, Color(1.0, 1.0, 1.0, 0.2), true); } } tile_map->draw_cells_outline(p_overlay, to_draw, Color(1.0, 1.0, 1.0), xform); diff --git a/editor/plugins/tiles/tile_set_atlas_source_editor.cpp b/editor/plugins/tiles/tile_set_atlas_source_editor.cpp index 4e8c28e997..e0df73596e 100644 --- a/editor/plugins/tiles/tile_set_atlas_source_editor.cpp +++ b/editor/plugins/tiles/tile_set_atlas_source_editor.cpp @@ -730,6 +730,8 @@ void TileSetAtlasSourceEditor::_update_tile_data_editors() { tile_data_editors["probability"] = tile_data_probability_editor; } + Color disabled_color = get_theme_color("disabled_font_color", "Editor"); + // --- Physics --- ADD_TILE_DATA_EDITOR_GROUP(TTR("Physics")); for (int i = 0; i < tile_set->get_physics_layers_count(); i++) { @@ -748,6 +750,16 @@ void TileSetAtlasSourceEditor::_update_tile_data_editors() { tile_data_editors.erase(vformat("physics_layer_%d", i)); } + if (tile_set->get_physics_layers_count() == 0) { + item = tile_data_editors_tree->create_item(group); + item->set_icon(0, get_theme_icon("Info", "EditorIcons")); + item->set_icon_modulate(0, disabled_color); + item->set_text(0, TTR("No physics layers")); + item->set_tooltip_text(0, TTR("Create and customize physics layers in the inspector of the TileSet resource.")); + item->set_selectable(0, false); + item->set_custom_color(0, disabled_color); + } + // --- Navigation --- ADD_TILE_DATA_EDITOR_GROUP(TTR("Navigation")); for (int i = 0; i < tile_set->get_navigation_layers_count(); i++) { @@ -766,6 +778,16 @@ void TileSetAtlasSourceEditor::_update_tile_data_editors() { tile_data_editors.erase(vformat("navigation_layer_%d", i)); } + if (tile_set->get_navigation_layers_count() == 0) { + item = tile_data_editors_tree->create_item(group); + item->set_icon(0, get_theme_icon("Info", "EditorIcons")); + item->set_icon_modulate(0, disabled_color); + item->set_text(0, TTR("No navigation layers")); + item->set_tooltip_text(0, TTR("Create and customize navigation layers in the inspector of the TileSet resource.")); + item->set_selectable(0, false); + item->set_custom_color(0, disabled_color); + } + // --- Custom Data --- ADD_TILE_DATA_EDITOR_GROUP(TTR("Custom Data")); for (int i = 0; i < tile_set->get_custom_data_layers_count(); i++) { @@ -799,6 +821,16 @@ void TileSetAtlasSourceEditor::_update_tile_data_editors() { tile_data_editors.erase(vformat("custom_data_%d", i)); } + if (tile_set->get_custom_data_layers_count() == 0) { + item = tile_data_editors_tree->create_item(group); + item->set_icon(0, get_theme_icon("Info", "EditorIcons")); + item->set_icon_modulate(0, disabled_color); + item->set_text(0, TTR("No custom data layers")); + item->set_tooltip_text(0, TTR("Create and customize custom data layers in the inspector of the TileSet resource.")); + item->set_selectable(0, false); + item->set_custom_color(0, disabled_color); + } + #undef ADD_TILE_DATA_EDITOR_GROUP #undef ADD_TILE_DATA_EDITOR @@ -1533,66 +1565,6 @@ void TileSetAtlasSourceEditor::_end_dragging() { // Change mouse accordingly. } -Control::CursorShape TileSetAtlasSourceEditor::get_cursor_shape(const Point2 &p_pos) const { - Control::CursorShape cursor_shape = get_default_cursor_shape(); - if (drag_type == DRAG_TYPE_NONE) { - if (selection.size() == 1) { - // Change the cursor depending on the hovered thing. - TileSelection selected = selection.front()->get(); - if (selected.tile != TileSetSource::INVALID_ATLAS_COORDS && selected.alternative == 0) { - Transform2D xform = tile_atlas_control->get_global_transform().affine_inverse() * get_global_transform(); - Vector2 mouse_local_pos = xform.xform(p_pos); - Vector2i size_in_atlas = tile_set_atlas_source->get_tile_size_in_atlas(selected.tile); - Rect2 region = tile_set_atlas_source->get_tile_texture_region(selected.tile); - Size2 zoomed_size = resize_handle->get_size() / tile_atlas_view->get_zoom(); - Rect2 rect = region.grow_individual(zoomed_size.x, zoomed_size.y, 0, 0); - const Vector2i coords[] = { Vector2i(0, 0), Vector2i(1, 0), Vector2i(1, 1), Vector2i(0, 1) }; - const Vector2i directions[] = { Vector2i(0, -1), Vector2i(1, 0), Vector2i(0, 1), Vector2i(-1, 0) }; - bool can_grow[4]; - for (int i = 0; i < 4; i++) { - can_grow[i] = tile_set_atlas_source->has_room_for_tile(selected.tile + directions[i], tile_set_atlas_source->get_tile_size_in_atlas(selected.tile), tile_set_atlas_source->get_tile_animation_columns(selected.tile), tile_set_atlas_source->get_tile_animation_separation(selected.tile), tile_set_atlas_source->get_tile_animation_frames_count(selected.tile), selected.tile); - can_grow[i] |= (i % 2 == 0) ? size_in_atlas.y > 1 : size_in_atlas.x > 1; - } - for (int i = 0; i < 4; i++) { - Vector2 pos = rect.position + rect.size * coords[i]; - if (can_grow[i] && can_grow[(i + 3) % 4] && Rect2(pos, zoomed_size).has_point(mouse_local_pos)) { - cursor_shape = (i % 2) ? CURSOR_BDIAGSIZE : CURSOR_FDIAGSIZE; - } - Vector2 next_pos = rect.position + rect.size * coords[(i + 1) % 4]; - if (can_grow[i] && Rect2((pos + next_pos) / 2.0, zoomed_size).has_point(mouse_local_pos)) { - cursor_shape = (i % 2) ? CURSOR_HSIZE : CURSOR_VSIZE; - } - } - } - } - } else { - switch (drag_type) { - case DRAG_TYPE_RESIZE_TOP_LEFT: - case DRAG_TYPE_RESIZE_BOTTOM_RIGHT: - cursor_shape = CURSOR_FDIAGSIZE; - break; - case DRAG_TYPE_RESIZE_TOP: - case DRAG_TYPE_RESIZE_BOTTOM: - cursor_shape = CURSOR_VSIZE; - break; - case DRAG_TYPE_RESIZE_TOP_RIGHT: - case DRAG_TYPE_RESIZE_BOTTOM_LEFT: - cursor_shape = CURSOR_BDIAGSIZE; - break; - case DRAG_TYPE_RESIZE_LEFT: - case DRAG_TYPE_RESIZE_RIGHT: - cursor_shape = CURSOR_HSIZE; - break; - case DRAG_TYPE_MOVE_TILE: - cursor_shape = CURSOR_MOVE; - break; - default: - break; - } - } - return cursor_shape; -} - HashMap<Vector2i, List<const PropertyInfo *>> TileSetAtlasSourceEditor::_group_properties_per_tiles(const List<PropertyInfo> &r_list, const TileSetAtlasSource *p_atlas) { // Group properties per tile. HashMap<Vector2i, List<const PropertyInfo *>> per_tile; @@ -2104,7 +2076,7 @@ void TileSetAtlasSourceEditor::_tile_alternatives_control_unscaled_draw() { void TileSetAtlasSourceEditor::_tile_set_changed() { if (tile_set->get_source_count() == 0) { // No sources, so nothing to do here anymore. - tile_set->disconnect("changed", callable_mp(this, &TileSetAtlasSourceEditor::_tile_set_changed)); + tile_set->disconnect_changed(callable_mp(this, &TileSetAtlasSourceEditor::_tile_set_changed)); tile_set = Ref<TileSet>(); return; } @@ -2166,7 +2138,7 @@ void TileSetAtlasSourceEditor::_undo_redo_inspector_callback(Object *p_undo_redo Ref<TileSetAtlasSource> atlas_source = atlas_source_proxy->get_edited(); ERR_FAIL_COND(!atlas_source.is_valid()); - UndoRedo *internal_undo_redo = undo_redo_man->get_history_for_object(atlas_source.ptr()).undo_redo; + UndoRedo *internal_undo_redo = undo_redo_man->get_history_for_object(atlas_source_proxy).undo_redo; internal_undo_redo->start_force_keep_in_merge_ends(); PackedVector2Array arr; @@ -2190,7 +2162,7 @@ void TileSetAtlasSourceEditor::_undo_redo_inspector_callback(Object *p_undo_redo String prefix = vformat("%d:%d/", coords.x, coords.y); for (PropertyInfo pi : properties) { if (pi.name.begins_with(prefix)) { - ADD_UNDO(atlas_source.ptr(), pi.name); + ADD_UNDO(atlas_source_proxy, pi.name); } } } @@ -2218,7 +2190,7 @@ void TileSetAtlasSourceEditor::edit(Ref<TileSet> p_tile_set, TileSetAtlasSource // Remove listener for old objects. if (tile_set.is_valid()) { - tile_set->disconnect("changed", callable_mp(this, &TileSetAtlasSourceEditor::_tile_set_changed)); + tile_set->disconnect_changed(callable_mp(this, &TileSetAtlasSourceEditor::_tile_set_changed)); } // Clear the selection. @@ -2233,7 +2205,7 @@ void TileSetAtlasSourceEditor::edit(Ref<TileSet> p_tile_set, TileSetAtlasSource read_only = new_read_only_state; if (tile_set.is_valid()) { - tile_set->connect("changed", callable_mp(this, &TileSetAtlasSourceEditor::_tile_set_changed)); + tile_set->connect_changed(callable_mp(this, &TileSetAtlasSourceEditor::_tile_set_changed)); } if (read_only && tools_button_group->get_pressed_button() == tool_paint_button) { @@ -2499,7 +2471,8 @@ TileSetAtlasSourceEditor::TileSetAtlasSourceEditor() { tile_inspector_no_tile_selected_label = memnew(Label); tile_inspector_no_tile_selected_label->set_v_size_flags(SIZE_EXPAND | SIZE_SHRINK_CENTER); tile_inspector_no_tile_selected_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER); - tile_inspector_no_tile_selected_label->set_text(TTR("No tiles selected.")); + tile_inspector_no_tile_selected_label->set_autowrap_mode(TextServer::AUTOWRAP_WORD_SMART); + tile_inspector_no_tile_selected_label->set_text(TTR("No tiles selected.\nSelect one or more tiles from the palette to edit its properties.")); middle_vbox_container->add_child(tile_inspector_no_tile_selected_label); // Property values palette. @@ -2610,7 +2583,7 @@ TileSetAtlasSourceEditor::TileSetAtlasSourceEditor() { empty_base_tile_popup_menu->connect("id_pressed", callable_mp(this, &TileSetAtlasSourceEditor::_menu_option)); tile_atlas_view->add_child(empty_base_tile_popup_menu); - tile_atlas_control = memnew(Control); + tile_atlas_control = memnew(TileAtlasControl(this)); tile_atlas_control->connect("draw", callable_mp(this, &TileSetAtlasSourceEditor::_tile_atlas_control_draw)); tile_atlas_control->connect("mouse_exited", callable_mp(this, &TileSetAtlasSourceEditor::_tile_atlas_control_mouse_exited)); tile_atlas_control->connect("gui_input", callable_mp(this, &TileSetAtlasSourceEditor::_tile_atlas_control_gui_input)); @@ -2838,3 +2811,63 @@ bool EditorInspectorPluginTileData::parse_property(Object *p_object, const Varia } return false; } + +Control::CursorShape TileSetAtlasSourceEditor::TileAtlasControl::get_cursor_shape(const Point2 &p_pos) const { + Control::CursorShape cursor_shape = get_default_cursor_shape(); + if (editor->drag_type == DRAG_TYPE_NONE) { + if (editor->selection.size() == 1) { + // Change the cursor depending on the hovered thing. + TileSelection selected = editor->selection.front()->get(); + if (selected.tile != TileSetSource::INVALID_ATLAS_COORDS && selected.alternative == 0) { + Transform2D xform = editor->tile_atlas_control->get_global_transform().affine_inverse() * get_global_transform(); + Vector2 mouse_local_pos = xform.xform(p_pos); + Vector2i size_in_atlas = editor->tile_set_atlas_source->get_tile_size_in_atlas(selected.tile); + Rect2 region = editor->tile_set_atlas_source->get_tile_texture_region(selected.tile); + Size2 zoomed_size = editor->resize_handle->get_size() / editor->tile_atlas_view->get_zoom(); + Rect2 rect = region.grow_individual(zoomed_size.x, zoomed_size.y, 0, 0); + const Vector2i coords[] = { Vector2i(0, 0), Vector2i(1, 0), Vector2i(1, 1), Vector2i(0, 1) }; + const Vector2i directions[] = { Vector2i(0, -1), Vector2i(1, 0), Vector2i(0, 1), Vector2i(-1, 0) }; + bool can_grow[4]; + for (int i = 0; i < 4; i++) { + can_grow[i] = editor->tile_set_atlas_source->has_room_for_tile(selected.tile + directions[i], editor->tile_set_atlas_source->get_tile_size_in_atlas(selected.tile), editor->tile_set_atlas_source->get_tile_animation_columns(selected.tile), editor->tile_set_atlas_source->get_tile_animation_separation(selected.tile), editor->tile_set_atlas_source->get_tile_animation_frames_count(selected.tile), selected.tile); + can_grow[i] |= (i % 2 == 0) ? size_in_atlas.y > 1 : size_in_atlas.x > 1; + } + for (int i = 0; i < 4; i++) { + Vector2 pos = rect.position + rect.size * coords[i]; + if (can_grow[i] && can_grow[(i + 3) % 4] && Rect2(pos, zoomed_size).has_point(mouse_local_pos)) { + cursor_shape = (i % 2) ? CURSOR_BDIAGSIZE : CURSOR_FDIAGSIZE; + } + Vector2 next_pos = rect.position + rect.size * coords[(i + 1) % 4]; + if (can_grow[i] && Rect2((pos + next_pos) / 2.0, zoomed_size).has_point(mouse_local_pos)) { + cursor_shape = (i % 2) ? CURSOR_HSIZE : CURSOR_VSIZE; + } + } + } + } + } else { + switch (editor->drag_type) { + case DRAG_TYPE_RESIZE_TOP_LEFT: + case DRAG_TYPE_RESIZE_BOTTOM_RIGHT: + cursor_shape = CURSOR_FDIAGSIZE; + break; + case DRAG_TYPE_RESIZE_TOP: + case DRAG_TYPE_RESIZE_BOTTOM: + cursor_shape = CURSOR_VSIZE; + break; + case DRAG_TYPE_RESIZE_TOP_RIGHT: + case DRAG_TYPE_RESIZE_BOTTOM_LEFT: + cursor_shape = CURSOR_BDIAGSIZE; + break; + case DRAG_TYPE_RESIZE_LEFT: + case DRAG_TYPE_RESIZE_RIGHT: + cursor_shape = CURSOR_HSIZE; + break; + case DRAG_TYPE_MOVE_TILE: + cursor_shape = CURSOR_MOVE; + break; + default: + break; + } + } + return cursor_shape; +} diff --git a/editor/plugins/tiles/tile_set_atlas_source_editor.h b/editor/plugins/tiles/tile_set_atlas_source_editor.h index 65a2ba33f6..6bbede520f 100644 --- a/editor/plugins/tiles/tile_set_atlas_source_editor.h +++ b/editor/plugins/tiles/tile_set_atlas_source_editor.h @@ -112,6 +112,15 @@ public: } }; + class TileAtlasControl : public Control { + TileSetAtlasSourceEditor *editor = nullptr; + + public: + virtual CursorShape get_cursor_shape(const Point2 &p_pos) const override; + TileAtlasControl(TileSetAtlasSourceEditor *p_editor) { editor = p_editor; } + }; + friend class TileAtlasControl; + private: bool read_only = false; @@ -279,8 +288,6 @@ public: void edit(Ref<TileSet> p_tile_set, TileSetAtlasSource *p_tile_set_source, int p_source_id); void init_source(); - virtual CursorShape get_cursor_shape(const Point2 &p_pos) const override; - TileSetAtlasSourceEditor(); ~TileSetAtlasSourceEditor(); }; diff --git a/editor/plugins/tiles/tile_set_editor.cpp b/editor/plugins/tiles/tile_set_editor.cpp index 9821588f63..9725ee72bb 100644 --- a/editor/plugins/tiles/tile_set_editor.cpp +++ b/editor/plugins/tiles/tile_set_editor.cpp @@ -723,7 +723,7 @@ void TileSetEditor::edit(Ref<TileSet> p_tile_set) { // Remove listener. if (tile_set.is_valid()) { - tile_set->disconnect("changed", callable_mp(this, &TileSetEditor::_tile_set_changed)); + tile_set->disconnect_changed(callable_mp(this, &TileSetEditor::_tile_set_changed)); } // Change the edited object. @@ -738,7 +738,7 @@ void TileSetEditor::edit(Ref<TileSet> p_tile_set) { sources_advanced_menu_button->set_disabled(read_only); source_sort_button->set_disabled(read_only); - tile_set->connect("changed", callable_mp(this, &TileSetEditor::_tile_set_changed)); + tile_set->connect_changed(callable_mp(this, &TileSetEditor::_tile_set_changed)); if (first_edit) { first_edit = false; _set_source_sort(EditorSettings::get_singleton()->get_project_metadata("editor_metadata", "tile_source_sort", 0)); diff --git a/editor/plugins/tiles/tile_set_scenes_collection_source_editor.cpp b/editor/plugins/tiles/tile_set_scenes_collection_source_editor.cpp index 6908dd7c3b..5ffa7b4bd4 100644 --- a/editor/plugins/tiles/tile_set_scenes_collection_source_editor.cpp +++ b/editor/plugins/tiles/tile_set_scenes_collection_source_editor.cpp @@ -384,7 +384,7 @@ void TileSetScenesCollectionSourceEditor::edit(Ref<TileSet> p_tile_set, TileSetS // Remove listener for old objects. if (tile_set_scenes_collection_source) { - tile_set_scenes_collection_source->disconnect("changed", callable_mp(this, &TileSetScenesCollectionSourceEditor::_tile_set_scenes_collection_source_changed)); + tile_set_scenes_collection_source->disconnect_changed(callable_mp(this, &TileSetScenesCollectionSourceEditor::_tile_set_scenes_collection_source_changed)); } // Change the edited object. @@ -404,7 +404,7 @@ void TileSetScenesCollectionSourceEditor::edit(Ref<TileSet> p_tile_set, TileSetS // Add the listener again. if (tile_set_scenes_collection_source) { - tile_set_scenes_collection_source->connect("changed", callable_mp(this, &TileSetScenesCollectionSourceEditor::_tile_set_scenes_collection_source_changed)); + tile_set_scenes_collection_source->connect_changed(callable_mp(this, &TileSetScenesCollectionSourceEditor::_tile_set_scenes_collection_source_changed)); } // Update everything. diff --git a/editor/plugins/tiles/tiles_editor_plugin.cpp b/editor/plugins/tiles/tiles_editor_plugin.cpp index b2ee3103ce..74668bd3be 100644 --- a/editor/plugins/tiles/tiles_editor_plugin.cpp +++ b/editor/plugins/tiles/tiles_editor_plugin.cpp @@ -45,6 +45,7 @@ #include "scene/gui/button.h" #include "scene/gui/control.h" #include "scene/gui/separator.h" +#include "scene/resources/image_texture.h" #include "scene/resources/tile_set.h" TilesEditorPlugin *TilesEditorPlugin::singleton = nullptr; diff --git a/editor/plugins/visual_shader_editor_plugin.cpp b/editor/plugins/visual_shader_editor_plugin.cpp index c9651e634f..3062059001 100644 --- a/editor/plugins/visual_shader_editor_plugin.cpp +++ b/editor/plugins/visual_shader_editor_plugin.cpp @@ -31,7 +31,6 @@ #include "visual_shader_editor_plugin.h" #include "core/config/project_settings.h" -#include "core/core_string_names.h" #include "core/io/resource_loader.h" #include "core/math/math_defs.h" #include "core/os/keyboard.h" @@ -57,6 +56,9 @@ #include "scene/gui/tree.h" #include "scene/gui/view_panner.h" #include "scene/main/window.h" +#include "scene/resources/curve_texture.h" +#include "scene/resources/image_texture.h" +#include "scene/resources/style_box_flat.h" #include "scene/resources/visual_shader_nodes.h" #include "scene/resources/visual_shader_particle_nodes.h" #include "servers/display_server.h" @@ -239,7 +241,7 @@ void VisualShaderGraphPlugin::update_curve(int p_node_id) { if (tex->get_texture().is_valid()) { links[p_node_id].curve_editors[0]->set_curve(tex->get_texture()->get_curve()); } - tex->emit_signal(CoreStringNames::get_singleton()->changed); + tex->emit_changed(); } } @@ -253,7 +255,7 @@ void VisualShaderGraphPlugin::update_curve_xyz(int p_node_id) { links[p_node_id].curve_editors[1]->set_curve(tex->get_texture()->get_curve_y()); links[p_node_id].curve_editors[2]->set_curve(tex->get_texture()->get_curve_z()); } - tex->emit_signal(CoreStringNames::get_singleton()->changed); + tex->emit_changed(); } } @@ -426,7 +428,8 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id, bool Ref<VisualShaderNodeGroupBase> group_node = Object::cast_to<VisualShaderNodeGroupBase>(vsnode.ptr()); bool is_group = !group_node.is_null(); - bool is_comment = false; + Ref<VisualShaderNodeComment> comment_node = Object::cast_to<VisualShaderNodeComment>(vsnode.ptr()); + bool is_comment = comment_node.is_valid(); Ref<VisualShaderNodeExpression> expression_node = Object::cast_to<VisualShaderNodeExpression>(group_node.ptr()); bool is_expression = !expression_node.is_null(); @@ -465,6 +468,10 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id, bool expression = expression_node->get_expression(); } + if (is_comment) { + node->set_visible(false); + } + node->set_position_offset(visual_shader->get_node_position(p_type, p_id)); node->set_title(vsnode->get_caption()); node->set_name(itos(p_id)); @@ -488,17 +495,6 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id, bool } if (is_resizable) { - Ref<VisualShaderNodeComment> comment_node = Object::cast_to<VisualShaderNodeComment>(vsnode.ptr()); - if (comment_node.is_valid()) { - is_comment = true; - node->set_comment(true); - - Label *comment_label = memnew(Label); - node->add_child(comment_label); - comment_label->set_h_size_flags(Control::SIZE_EXPAND_FILL); - comment_label->set_v_size_flags(Control::SIZE_EXPAND_FILL); - comment_label->set_text(comment_node->get_description()); - } editor->call_deferred(SNAME("_set_node_size"), (int)p_type, p_id, size); } @@ -560,9 +556,8 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id, bool if (curve.is_valid()) { custom_editor->set_h_size_flags(Control::SIZE_EXPAND_FILL); - Callable ce = callable_mp(graph_plugin, &VisualShaderGraphPlugin::update_curve); - if (curve->get_texture().is_valid() && !curve->get_texture()->is_connected("changed", ce)) { - curve->get_texture()->connect("changed", ce.bind(p_id)); + if (curve->get_texture().is_valid()) { + curve->get_texture()->connect_changed(callable_mp(graph_plugin, &VisualShaderGraphPlugin::update_curve).bind(p_id)); } CurveEditor *curve_editor = memnew(CurveEditor); @@ -578,9 +573,8 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id, bool if (curve_xyz.is_valid()) { custom_editor->set_h_size_flags(Control::SIZE_EXPAND_FILL); - Callable ce = callable_mp(graph_plugin, &VisualShaderGraphPlugin::update_curve_xyz); - if (curve_xyz->get_texture().is_valid() && !curve_xyz->get_texture()->is_connected("changed", ce)) { - curve_xyz->get_texture()->connect("changed", ce.bind(p_id)); + if (curve_xyz->get_texture().is_valid()) { + curve_xyz->get_texture()->connect_changed(callable_mp(graph_plugin, &VisualShaderGraphPlugin::update_curve_xyz).bind(p_id)); } CurveEditor *curve_editor_x = memnew(CurveEditor); @@ -1159,20 +1153,14 @@ void VisualShaderEditor::edit(VisualShader *p_visual_shader) { visual_shader = Ref<VisualShader>(p_visual_shader); graph_plugin->register_shader(visual_shader.ptr()); - Callable ce = callable_mp(this, &VisualShaderEditor::_update_preview); - if (!visual_shader->is_connected("changed", ce)) { - visual_shader->connect("changed", ce); - } - visual_shader->set_graph_offset(graph->get_scroll_ofs() / EDSCALE); + visual_shader->connect_changed(callable_mp(this, &VisualShaderEditor::_update_preview)); + visual_shader->set_graph_offset(graph->get_scroll_offset() / EDSCALE); _set_mode(visual_shader->get_mode()); _update_nodes(); } else { if (visual_shader.is_valid()) { - Callable ce = callable_mp(this, &VisualShaderEditor::_update_preview); - if (visual_shader->is_connected("changed", ce)) { - visual_shader->disconnect("changed", ce); - } + visual_shader->disconnect_changed(callable_mp(this, &VisualShaderEditor::_update_preview)); } visual_shader.unref(); } @@ -2031,7 +2019,7 @@ void VisualShaderEditor::_update_graph() { return; } - graph->set_scroll_ofs(visual_shader->get_graph_offset() * EDSCALE); + graph->set_scroll_offset(visual_shader->get_graph_offset() * EDSCALE); VisualShader::Type type = get_current_shader_type(); @@ -3089,6 +3077,9 @@ void VisualShaderEditor::_add_node(int p_idx, const Vector<Variant> &p_ops, Stri if (!is_native) { vsnode->set_script(add_options[p_idx].script); } + VisualShaderNodeCustom *custom_node = Object::cast_to<VisualShaderNodeCustom>(vsn); + ERR_FAIL_COND(!custom_node); + custom_node->update_ports(); } bool is_texture2d = (Object::cast_to<VisualShaderNodeTexture>(vsnode.ptr()) != nullptr); @@ -3099,7 +3090,7 @@ void VisualShaderEditor::_add_node(int p_idx, const Vector<Variant> &p_ops, Stri bool is_curve_xyz = (Object::cast_to<VisualShaderNodeCurveXYZTexture>(vsnode.ptr()) != nullptr); bool is_parameter = (Object::cast_to<VisualShaderNodeParameter>(vsnode.ptr()) != nullptr); - Point2 position = graph->get_scroll_ofs(); + Point2 position = graph->get_scroll_offset(); if (saved_node_pos_dirty) { position += saved_node_pos; @@ -3217,16 +3208,26 @@ void VisualShaderEditor::_add_node(int p_idx, const Vector<Variant> &p_ops, Stri undo_redo->add_undo_method(graph_plugin.ptr(), "disconnect_nodes", type, from_node, from_slot, _to_node, _to_slot); undo_redo->add_do_method(graph_plugin.ptr(), "connect_nodes", type, from_node, from_slot, _to_node, _to_slot); } else { - // Attempting to connect to the first correct port. + int _to_slot = -1; + + // Attempting to connect to the default input port or to the first correct port (if it's not found). for (int i = 0; i < vsnode->get_input_port_count(); i++) { if (visual_shader->is_port_types_compatible(output_port_type, vsnode->get_input_port_type(i))) { - undo_redo->add_undo_method(visual_shader.ptr(), "disconnect_nodes", type, from_node, from_slot, _to_node, i); - undo_redo->add_do_method(visual_shader.ptr(), "connect_nodes", type, from_node, from_slot, _to_node, i); - undo_redo->add_undo_method(graph_plugin.ptr(), "disconnect_nodes", type, from_node, from_slot, _to_node, i); - undo_redo->add_do_method(graph_plugin.ptr(), "connect_nodes", type, from_node, from_slot, _to_node, i); - break; + if (i == vsnode->get_default_input_port(output_port_type)) { + _to_slot = i; + break; + } else if (_to_slot == -1) { + _to_slot = i; + } } } + + if (_to_slot >= 0) { + undo_redo->add_undo_method(visual_shader.ptr(), "disconnect_nodes", type, from_node, from_slot, _to_node, _to_slot); + undo_redo->add_do_method(visual_shader.ptr(), "connect_nodes", type, from_node, from_slot, _to_node, _to_slot); + undo_redo->add_undo_method(graph_plugin.ptr(), "disconnect_nodes", type, from_node, from_slot, _to_node, _to_slot); + undo_redo->add_do_method(graph_plugin.ptr(), "connect_nodes", type, from_node, from_slot, _to_node, _to_slot); + } } if (output_port_type == VisualShaderNode::PORT_TYPE_SAMPLER) { @@ -4386,7 +4387,7 @@ void VisualShaderEditor::_paste_nodes(bool p_use_custom_position, const Vector2 mpos = graph->get_local_mouse_position(); } - _dup_paste_nodes(type, copy_items_buffer, copy_connections_buffer, graph->get_scroll_ofs() / scale + mpos / scale - selection_center, false); + _dup_paste_nodes(type, copy_items_buffer, copy_connections_buffer, graph->get_scroll_offset() / scale + mpos / scale - selection_center, false); } void VisualShaderEditor::_mode_selected(int p_id) { @@ -5089,7 +5090,7 @@ VisualShaderEditor::VisualShaderEditor() { FileSystemDock::get_singleton()->connect("resource_removed", callable_mp(this, &VisualShaderEditor::_resource_removed)); graph = memnew(GraphEdit); - graph->get_zoom_hbox()->set_h_size_flags(SIZE_EXPAND_FILL); + graph->get_menu_hbox()->set_h_size_flags(SIZE_EXPAND_FILL); graph->set_v_size_flags(SIZE_EXPAND_FILL); graph->set_h_size_flags(SIZE_EXPAND_FILL); graph->set_show_zoom_label(true); @@ -5182,8 +5183,8 @@ VisualShaderEditor::VisualShaderEditor() { graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_SAMPLER, VisualShaderNode::PORT_TYPE_SAMPLER); VSeparator *vs = memnew(VSeparator); - graph->get_zoom_hbox()->add_child(vs); - graph->get_zoom_hbox()->move_child(vs, 0); + graph->get_menu_hbox()->add_child(vs); + graph->get_menu_hbox()->move_child(vs, 0); custom_mode_box = memnew(CheckBox); custom_mode_box->set_text(TTR("Custom")); @@ -5217,28 +5218,28 @@ VisualShaderEditor::VisualShaderEditor() { edit_type = edit_type_standard; - graph->get_zoom_hbox()->add_child(custom_mode_box); - graph->get_zoom_hbox()->move_child(custom_mode_box, 0); - graph->get_zoom_hbox()->add_child(edit_type_standard); - graph->get_zoom_hbox()->move_child(edit_type_standard, 0); - graph->get_zoom_hbox()->add_child(edit_type_particles); - graph->get_zoom_hbox()->move_child(edit_type_particles, 0); - graph->get_zoom_hbox()->add_child(edit_type_sky); - graph->get_zoom_hbox()->move_child(edit_type_sky, 0); - graph->get_zoom_hbox()->add_child(edit_type_fog); - graph->get_zoom_hbox()->move_child(edit_type_fog, 0); + graph->get_menu_hbox()->add_child(custom_mode_box); + graph->get_menu_hbox()->move_child(custom_mode_box, 0); + graph->get_menu_hbox()->add_child(edit_type_standard); + graph->get_menu_hbox()->move_child(edit_type_standard, 0); + graph->get_menu_hbox()->add_child(edit_type_particles); + graph->get_menu_hbox()->move_child(edit_type_particles, 0); + graph->get_menu_hbox()->add_child(edit_type_sky); + graph->get_menu_hbox()->move_child(edit_type_sky, 0); + graph->get_menu_hbox()->add_child(edit_type_fog); + graph->get_menu_hbox()->move_child(edit_type_fog, 0); add_node = memnew(Button); add_node->set_flat(true); add_node->set_text(TTR("Add Node...")); - graph->get_zoom_hbox()->add_child(add_node); - graph->get_zoom_hbox()->move_child(add_node, 0); + graph->get_menu_hbox()->add_child(add_node); + graph->get_menu_hbox()->move_child(add_node, 0); add_node->connect("pressed", callable_mp(this, &VisualShaderEditor::_show_members_dialog).bind(false, VisualShaderNode::PORT_TYPE_MAX, VisualShaderNode::PORT_TYPE_MAX)); varying_button = memnew(MenuButton); varying_button->set_text(TTR("Manage Varyings")); varying_button->set_switch_on_hover(true); - graph->get_zoom_hbox()->add_child(varying_button); + graph->get_menu_hbox()->add_child(varying_button); PopupMenu *varying_menu = varying_button->get_popup(); varying_menu->add_item(TTR("Add Varying"), int(VaryingMenuOptions::ADD)); @@ -5249,7 +5250,7 @@ VisualShaderEditor::VisualShaderEditor() { preview_shader->set_flat(true); preview_shader->set_toggle_mode(true); preview_shader->set_tooltip_text(TTR("Show generated shader code.")); - graph->get_zoom_hbox()->add_child(preview_shader); + graph->get_menu_hbox()->add_child(preview_shader); preview_shader->connect("pressed", callable_mp(this, &VisualShaderEditor::_show_preview_text)); /////////////////////////////////////// @@ -5874,6 +5875,10 @@ VisualShaderEditor::VisualShaderEditor() { add_options.push_back(AddOption("CurveXYZTexture", "Textures/Functions", "VisualShaderNodeCurveXYZTexture", TTR("Perform the three components curve texture lookup."), {}, VisualShaderNode::PORT_TYPE_VECTOR_3D)); add_options.push_back(AddOption("LinearSceneDepth", "Textures/Functions", "VisualShaderNodeLinearSceneDepth", TTR("Returns the depth value obtained from the depth prepass in a linear space."), {}, VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_FRAGMENT, Shader::MODE_SPATIAL)); texture2d_node_option_idx = add_options.size(); + add_options.push_back(AddOption("WorldPositionFromDepth", "Textures/Functions", "VisualShaderNodeWorldPositionFromDepth", TTR("Reconstructs the World Position of the Node from the depth texture."), {}, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_FRAGMENT, Shader::MODE_SPATIAL)); + texture2d_node_option_idx = add_options.size(); + add_options.push_back(AddOption("ScreenNormalWorldSpace", "Textures/Functions", "VisualShaderNodeScreenNormalWorldSpace", TTR("Unpacks the Screen Normal Texture in World Space"), {}, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_FRAGMENT, Shader::MODE_SPATIAL)); + texture2d_node_option_idx = add_options.size(); add_options.push_back(AddOption("Texture2D", "Textures/Functions", "VisualShaderNodeTexture", TTR("Perform the 2D texture lookup."), {}, VisualShaderNode::PORT_TYPE_VECTOR_4D)); texture2d_array_node_option_idx = add_options.size(); add_options.push_back(AddOption("Texture2DArray", "Textures/Functions", "VisualShaderNodeTexture2DArray", TTR("Perform the 2D-array texture lookup."), {}, VisualShaderNode::PORT_TYPE_VECTOR_4D)); @@ -5918,6 +5923,8 @@ VisualShaderEditor::VisualShaderEditor() { add_options.push_back(AddOption("ProximityFade", "Utility", "VisualShaderNodeProximityFade", TTR("The proximity fade effect fades out each pixel based on its distance to another object."), {}, VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_FRAGMENT, Shader::MODE_SPATIAL)); add_options.push_back(AddOption("RandomRange", "Utility", "VisualShaderNodeRandomRange", TTR("Returns a random value between the minimum and maximum input values."), {}, VisualShaderNode::PORT_TYPE_SCALAR)); add_options.push_back(AddOption("Remap", "Utility", "VisualShaderNodeRemap", TTR("Remaps a given input from the input range to the output range."), {}, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("RotationByAxis", "Utility", "VisualShaderNodeRotationByAxis", TTR("Rotates an input vector by a given angle."), {}, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_FRAGMENT, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("RotationByAxis", "Utility", "VisualShaderNodeRotationByAxis", TTR("Rotates an input vector by a given angle."), {}, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_VERTEX, Shader::MODE_SPATIAL)); // VECTOR @@ -6120,7 +6127,6 @@ VisualShaderEditor::VisualShaderEditor() { // SPECIAL - add_options.push_back(AddOption("Comment", "Special", "VisualShaderNodeComment", TTR("A rectangular area with a description string for better graph organization."))); add_options.push_back(AddOption("Expression", "Special", "VisualShaderNodeExpression", TTR("Custom Godot Shader Language expression, with custom amount of input and output ports. This is a direct injection of code into the vertex/fragment/light function, do not use it to write the function declarations inside."))); add_options.push_back(AddOption("GlobalExpression", "Special", "VisualShaderNodeGlobalExpression", TTR("Custom Godot Shader Language expression, which is placed on top of the resulted shader. You can place various function definitions inside and call it later in the Expressions. You can also declare varyings, parameters and constants."))); add_options.push_back(AddOption("ParameterRef", "Special", "VisualShaderNodeParameterRef", TTR("A reference to an existing parameter."))); @@ -6447,7 +6453,7 @@ public: properties[i]->update_property(); properties[i]->set_name_split_ratio(0); } - node->connect("changed", callable_mp(this, &VisualShaderNodePluginDefaultEditor::_node_changed)); + node->connect_changed(callable_mp(this, &VisualShaderNodePluginDefaultEditor::_node_changed)); } static void _bind_methods() { @@ -6713,7 +6719,7 @@ void VisualShaderNodePortPreview::_shader_changed() { void VisualShaderNodePortPreview::setup(const Ref<VisualShader> &p_shader, VisualShader::Type p_type, int p_node, int p_port, bool p_is_valid) { shader = p_shader; - shader->connect("changed", callable_mp(this, &VisualShaderNodePortPreview::_shader_changed), CONNECT_DEFERRED); + shader->connect_changed(callable_mp(this, &VisualShaderNodePortPreview::_shader_changed), CONNECT_DEFERRED); type = p_type; port = p_port; node = p_node; diff --git a/editor/project_manager.cpp b/editor/project_manager.cpp index 6df6973a7d..691adcdb7a 100644 --- a/editor/project_manager.cpp +++ b/editor/project_manager.cpp @@ -59,6 +59,7 @@ #include "scene/gui/separator.h" #include "scene/gui/texture_rect.h" #include "scene/main/window.h" +#include "scene/resources/image_texture.h" #include "servers/display_server.h" #include "servers/navigation_server_3d.h" #include "servers/physics_server_2d.h" diff --git a/editor/shader_create_dialog.cpp b/editor/shader_create_dialog.cpp index 3d8a6e368d..607b53718b 100644 --- a/editor/shader_create_dialog.cpp +++ b/editor/shader_create_dialog.cpp @@ -164,37 +164,64 @@ void ShaderCreateDialog::_create_new() { code += vformat("shader_type %s;\n", mode_menu->get_text().to_snake_case()); if (current_template == 0) { // Default template. - code += "\n"; switch (current_mode) { case Shader::MODE_SPATIAL: - code += "void fragment() {\n"; - code += "\t// Place fragment code here.\n"; - code += "}\n"; + code += R"( +void vertex() { + // Called for every vertex the material is visible on. +} + +void fragment() { + // Called for every pixel the material is visible on. +} + +void light() { + // Called for every pixel for every light affecting the material. +} +)"; break; case Shader::MODE_CANVAS_ITEM: - code += "void fragment() {\n"; - code += "\t// Place fragment code here.\n"; - code += "}\n"; + code += R"( +void vertex() { + // Called for every vertex the material is visible on. +} + +void fragment() { + // Called for every pixel the material is visible on. +} + +void light() { + // Called for every pixel for every light affecting the CanvasItem. +} +)"; break; case Shader::MODE_PARTICLES: - code += "void start() {\n"; - code += "\t// Place start code here.\n"; - code += "}\n"; - code += "\n"; - code += "void process() {\n"; - code += "\t// Place process code here.\n"; - code += "}\n"; + code += R"( +void start() { + // Called when a particle is spawned. +} + +void process() { + // Called every frame on existing particles (according to the Fixed FPS property). +} +)"; break; case Shader::MODE_SKY: - code += "void sky() {\n"; - code += "\t// Place sky code here.\n"; - code += "}\n"; + code += R"( +void sky() { + // Called for every visible pixel in the sky background, as well as all pixels + // in the radiance cubemap. +} +)"; break; case Shader::MODE_FOG: - code += "void fog() {\n"; - code += "\t// Place fog code here.\n"; - code += "}\n"; - break; + code += R"( +void fog() { + // Called once for every froxel that is touched by an axis-aligned bounding box + // of the associated FogVolume. This means that froxels that just barely touch + // a given FogVolume will still be used. +} +)"; } } text_shader->set_code(code.as_string()); diff --git a/editor/window_wrapper.cpp b/editor/window_wrapper.cpp index 3a8dbf017f..91d5aa8860 100644 --- a/editor/window_wrapper.cpp +++ b/editor/window_wrapper.cpp @@ -313,7 +313,7 @@ void WindowWrapper::set_margins_enabled(bool p_enabled) { } WindowWrapper::WindowWrapper() { - if (SceneTree::get_singleton()->get_root()->is_embedding_subwindows() || !EDITOR_GET("interface/multi_window/enable")) { + if (SceneTree::get_singleton()->get_root()->is_embedding_subwindows() || EDITOR_GET("interface/editor/single_window_mode") || !EDITOR_GET("interface/multi_window/enable")) { return; } diff --git a/main/main.cpp b/main/main.cpp index 989b9bc59d..7e2741648d 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -2710,6 +2710,9 @@ bool Main::start() { } uint64_t minimum_time_msec = GLOBAL_DEF(PropertyInfo(Variant::INT, "application/boot_splash/minimum_display_time", PROPERTY_HINT_RANGE, "0,100,1,or_greater,suffix:ms"), 0); + if (Engine::get_singleton()->is_editor_hint()) { + minimum_time_msec = 0; + } #ifdef TOOLS_ENABLED #ifdef MODULE_GDSCRIPT_ENABLED diff --git a/modules/csg/csg_shape.cpp b/modules/csg/csg_shape.cpp index c241f1cabd..4c217dac28 100644 --- a/modules/csg/csg_shape.cpp +++ b/modules/csg/csg_shape.cpp @@ -953,12 +953,12 @@ void CSGMesh3D::set_mesh(const Ref<Mesh> &p_mesh) { return; } if (mesh.is_valid()) { - mesh->disconnect("changed", callable_mp(this, &CSGMesh3D::_mesh_changed)); + mesh->disconnect_changed(callable_mp(this, &CSGMesh3D::_mesh_changed)); } mesh = p_mesh; if (mesh.is_valid()) { - mesh->connect("changed", callable_mp(this, &CSGMesh3D::_mesh_changed)); + mesh->connect_changed(callable_mp(this, &CSGMesh3D::_mesh_changed)); } _mesh_changed(); diff --git a/modules/dds/texture_loader_dds.cpp b/modules/dds/texture_loader_dds.cpp index e6523e3d09..8a3a36e84b 100644 --- a/modules/dds/texture_loader_dds.cpp +++ b/modules/dds/texture_loader_dds.cpp @@ -31,6 +31,7 @@ #include "texture_loader_dds.h" #include "core/io/file_access.h" +#include "scene/resources/image_texture.h" #define PF_FOURCC(s) ((uint32_t)(((s)[3] << 24U) | ((s)[2] << 16U) | ((s)[1] << 8U) | ((s)[0]))) diff --git a/modules/dds/texture_loader_dds.h b/modules/dds/texture_loader_dds.h index dc3df1fcee..3763700ff1 100644 --- a/modules/dds/texture_loader_dds.h +++ b/modules/dds/texture_loader_dds.h @@ -32,7 +32,6 @@ #define TEXTURE_LOADER_DDS_H #include "core/io/resource_loader.h" -#include "scene/resources/texture.h" class ResourceFormatDDS : public ResourceFormatLoader { public: diff --git a/modules/enet/enet_packet_peer.cpp b/modules/enet/enet_packet_peer.cpp index f64704c67d..a131841a07 100644 --- a/modules/enet/enet_packet_peer.cpp +++ b/modules/enet/enet_packet_peer.cpp @@ -76,7 +76,7 @@ void ENetPacketPeer::throttle_configure(int p_interval, int p_acceleration, int void ENetPacketPeer::set_timeout(int p_timeout, int p_timeout_min, int p_timeout_max) { ERR_FAIL_COND_MSG(peer == nullptr, "Peer not connected"); - ERR_FAIL_COND_MSG(p_timeout > p_timeout_min || p_timeout_min > p_timeout_max, "Timeout limit must be less than minimum timeout, which itself must be less then maximum timeout"); + ERR_FAIL_COND_MSG(p_timeout > p_timeout_min || p_timeout_min > p_timeout_max, "Timeout limit must be less than minimum timeout, which itself must be less than maximum timeout"); enet_peer_timeout(peer, p_timeout, p_timeout_min, p_timeout_max); } diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp index 3d6d133579..0bf9f72a2c 100644 --- a/modules/gdscript/gdscript.cpp +++ b/modules/gdscript/gdscript.cpp @@ -787,11 +787,11 @@ Error GDScript::reload(bool p_keep_state) { err = compiler.compile(&parser, this, p_keep_state); if (err) { + _err_print_error("GDScript::reload", path.is_empty() ? "built-in" : (const char *)path.utf8().get_data(), compiler.get_error_line(), ("Compile Error: " + compiler.get_error()).utf8().get_data(), false, ERR_HANDLER_SCRIPT); if (can_run) { if (EngineDebugger::is_active()) { GDScriptLanguage::get_singleton()->debug_break_parse(_get_debug_path(), compiler.get_error_line(), "Parser Error: " + compiler.get_error()); } - _err_print_error("GDScript::reload", path.is_empty() ? "built-in" : (const char *)path.utf8().get_data(), compiler.get_error_line(), ("Compile Error: " + compiler.get_error()).utf8().get_data(), false, ERR_HANDLER_SCRIPT); reloading = false; return ERR_COMPILATION_FAILED; } else { @@ -2094,10 +2094,7 @@ String GDScriptLanguage::get_extension() const { } void GDScriptLanguage::finish() { - if (_call_stack) { - memdelete_arr(_call_stack); - _call_stack = nullptr; - } + _call_stack.free(); // Clear the cache before parsing the script_list GDScriptCache::clear(); @@ -2140,12 +2137,12 @@ void GDScriptLanguage::profiling_start() { SelfList<GDScriptFunction> *elem = function_list.first(); while (elem) { - elem->self()->profile.call_count = 0; - elem->self()->profile.self_time = 0; - elem->self()->profile.total_time = 0; - elem->self()->profile.frame_call_count = 0; - elem->self()->profile.frame_self_time = 0; - elem->self()->profile.frame_total_time = 0; + elem->self()->profile.call_count.set(0); + elem->self()->profile.self_time.set(0); + elem->self()->profile.total_time.set(0); + elem->self()->profile.frame_call_count.set(0); + elem->self()->profile.frame_self_time.set(0); + elem->self()->profile.frame_total_time.set(0); elem->self()->profile.last_frame_call_count = 0; elem->self()->profile.last_frame_self_time = 0; elem->self()->profile.last_frame_total_time = 0; @@ -2175,9 +2172,9 @@ int GDScriptLanguage::profiling_get_accumulated_data(ProfilingInfo *p_info_arr, if (current >= p_info_max) { break; } - p_info_arr[current].call_count = elem->self()->profile.call_count; - p_info_arr[current].self_time = elem->self()->profile.self_time; - p_info_arr[current].total_time = elem->self()->profile.total_time; + p_info_arr[current].call_count = elem->self()->profile.call_count.get(); + p_info_arr[current].self_time = elem->self()->profile.self_time.get(); + p_info_arr[current].total_time = elem->self()->profile.total_time.get(); p_info_arr[current].signature = elem->self()->profile.signature; elem = elem->next(); current++; @@ -2395,12 +2392,12 @@ void GDScriptLanguage::frame() { SelfList<GDScriptFunction> *elem = function_list.first(); while (elem) { - elem->self()->profile.last_frame_call_count = elem->self()->profile.frame_call_count; - elem->self()->profile.last_frame_self_time = elem->self()->profile.frame_self_time; - elem->self()->profile.last_frame_total_time = elem->self()->profile.frame_total_time; - elem->self()->profile.frame_call_count = 0; - elem->self()->profile.frame_self_time = 0; - elem->self()->profile.frame_total_time = 0; + elem->self()->profile.last_frame_call_count = elem->self()->profile.frame_call_count.get(); + elem->self()->profile.last_frame_self_time = elem->self()->profile.frame_self_time.get(); + elem->self()->profile.last_frame_total_time = elem->self()->profile.frame_total_time.get(); + elem->self()->profile.frame_call_count.set(0); + elem->self()->profile.frame_self_time.set(0); + elem->self()->profile.frame_total_time.set(0); elem = elem->next(); } } @@ -2607,6 +2604,8 @@ String GDScriptLanguage::get_global_class_name(const String &p_path, String *r_b return c->identifier != nullptr ? String(c->identifier->name) : String(); } +thread_local GDScriptLanguage::CallStack GDScriptLanguage::_call_stack; + GDScriptLanguage::GDScriptLanguage() { calls = 0; ERR_FAIL_COND(singleton); @@ -2626,18 +2625,14 @@ GDScriptLanguage::GDScriptLanguage() { profiling = false; script_frame_time = 0; - _debug_call_stack_pos = 0; int dmcs = GLOBAL_DEF(PropertyInfo(Variant::INT, "debug/settings/gdscript/max_call_stack", PROPERTY_HINT_RANGE, "512," + itos(GDScriptFunction::MAX_CALL_DEPTH - 1) + ",1"), 1024); if (EngineDebugger::is_active()) { //debugging enabled! _debug_max_call_stack = dmcs; - _call_stack = memnew_arr(CallLevel, _debug_max_call_stack + 1); - } else { _debug_max_call_stack = 0; - _call_stack = nullptr; } #ifdef DEBUG_ENABLED diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h index d131ec6ab1..c41b1a0def 100644 --- a/modules/gdscript/gdscript.h +++ b/modules/gdscript/gdscript.h @@ -364,12 +364,26 @@ class GDScriptLanguage : public ScriptLanguage { int *line = nullptr; }; - int _debug_parse_err_line; - String _debug_parse_err_file; - String _debug_error; - int _debug_call_stack_pos; - int _debug_max_call_stack; - CallLevel *_call_stack = nullptr; + static thread_local int _debug_parse_err_line; + static thread_local String _debug_parse_err_file; + static thread_local String _debug_error; + struct CallStack { + CallLevel *levels = nullptr; + int stack_pos = 0; + + void free() { + if (levels) { + memdelete(levels); + levels = nullptr; + } + } + ~CallStack() { + free(); + } + }; + + static thread_local CallStack _call_stack; + int _debug_max_call_stack = 0; void _add_global(const StringName &p_name, const Variant &p_value); @@ -395,59 +409,51 @@ public: bool debug_break_parse(const String &p_file, int p_line, const String &p_error); _FORCE_INLINE_ void enter_function(GDScriptInstance *p_instance, GDScriptFunction *p_function, Variant *p_stack, int *p_ip, int *p_line) { - if (Thread::get_main_id() != Thread::get_caller_id()) { - return; //no support for other threads than main for now + if (unlikely(_call_stack.levels == nullptr)) { + _call_stack.levels = memnew_arr(CallLevel, _debug_max_call_stack + 1); } if (EngineDebugger::get_script_debugger()->get_lines_left() > 0 && EngineDebugger::get_script_debugger()->get_depth() >= 0) { EngineDebugger::get_script_debugger()->set_depth(EngineDebugger::get_script_debugger()->get_depth() + 1); } - if (_debug_call_stack_pos >= _debug_max_call_stack) { + if (_call_stack.stack_pos >= _debug_max_call_stack) { //stack overflow _debug_error = vformat("Stack overflow (stack size: %s). Check for infinite recursion in your script.", _debug_max_call_stack); EngineDebugger::get_script_debugger()->debug(this); return; } - _call_stack[_debug_call_stack_pos].stack = p_stack; - _call_stack[_debug_call_stack_pos].instance = p_instance; - _call_stack[_debug_call_stack_pos].function = p_function; - _call_stack[_debug_call_stack_pos].ip = p_ip; - _call_stack[_debug_call_stack_pos].line = p_line; - _debug_call_stack_pos++; + _call_stack.levels[_call_stack.stack_pos].stack = p_stack; + _call_stack.levels[_call_stack.stack_pos].instance = p_instance; + _call_stack.levels[_call_stack.stack_pos].function = p_function; + _call_stack.levels[_call_stack.stack_pos].ip = p_ip; + _call_stack.levels[_call_stack.stack_pos].line = p_line; + _call_stack.stack_pos++; } _FORCE_INLINE_ void exit_function() { - if (Thread::get_main_id() != Thread::get_caller_id()) { - return; //no support for other threads than main for now - } - if (EngineDebugger::get_script_debugger()->get_lines_left() > 0 && EngineDebugger::get_script_debugger()->get_depth() >= 0) { EngineDebugger::get_script_debugger()->set_depth(EngineDebugger::get_script_debugger()->get_depth() - 1); } - if (_debug_call_stack_pos == 0) { + if (_call_stack.stack_pos == 0) { _debug_error = "Stack Underflow (Engine Bug)"; EngineDebugger::get_script_debugger()->debug(this); return; } - _debug_call_stack_pos--; + _call_stack.stack_pos--; } virtual Vector<StackInfo> debug_get_current_stack_info() override { - if (Thread::get_main_id() != Thread::get_caller_id()) { - return Vector<StackInfo>(); - } - Vector<StackInfo> csi; - csi.resize(_debug_call_stack_pos); - for (int i = 0; i < _debug_call_stack_pos; i++) { - csi.write[_debug_call_stack_pos - i - 1].line = _call_stack[i].line ? *_call_stack[i].line : 0; - if (_call_stack[i].function) { - csi.write[_debug_call_stack_pos - i - 1].func = _call_stack[i].function->get_name(); - csi.write[_debug_call_stack_pos - i - 1].file = _call_stack[i].function->get_script()->get_script_path(); + csi.resize(_call_stack.stack_pos); + for (int i = 0; i < _call_stack.stack_pos; i++) { + csi.write[_call_stack.stack_pos - i - 1].line = _call_stack.levels[i].line ? *_call_stack.levels[i].line : 0; + if (_call_stack.levels[i].function) { + csi.write[_call_stack.stack_pos - i - 1].func = _call_stack.levels[i].function->get_name(); + csi.write[_call_stack.stack_pos - i - 1].file = _call_stack.levels[i].function->get_script()->get_script_path(); } } return csi; diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index d3445b8cc0..01e94ebb22 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -1553,7 +1553,7 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode * } parser->push_warning(p_function->parameters[i]->identifier, GDScriptWarning::UNUSED_PARAMETER, visible_name, p_function->parameters[i]->identifier->name); } - is_shadowing(p_function->parameters[i]->identifier, "function parameter"); + is_shadowing(p_function->parameters[i]->identifier, "function parameter", true); #endif // DEBUG_ENABLED #ifdef TOOLS_ENABLED if (p_function->parameters[i]->initializer) { @@ -1874,9 +1874,8 @@ void GDScriptAnalyzer::resolve_variable(GDScriptParser::VariableNode *p_variable } else if (p_variable->assignments == 0) { parser->push_warning(p_variable, GDScriptWarning::UNASSIGNED_VARIABLE, p_variable->identifier->name); } - - is_shadowing(p_variable->identifier, kind); } + is_shadowing(p_variable->identifier, kind, p_is_local); #endif } @@ -1889,9 +1888,8 @@ void GDScriptAnalyzer::resolve_constant(GDScriptParser::ConstantNode *p_constant if (p_constant->usages == 0) { parser->push_warning(p_constant, GDScriptWarning::UNUSED_LOCAL_CONSTANT, p_constant->identifier->name); } - - is_shadowing(p_constant->identifier, kind); } + is_shadowing(p_constant->identifier, kind, p_is_local); #endif } @@ -2052,7 +2050,7 @@ void GDScriptAnalyzer::resolve_for(GDScriptParser::ForNode *p_for) { p_for->set_datatype(p_for->loop->get_datatype()); #ifdef DEBUG_ENABLED if (p_for->variable) { - is_shadowing(p_for->variable, R"("for" iterator variable)"); + is_shadowing(p_for->variable, R"("for" iterator variable)", true); } #endif } @@ -2148,7 +2146,7 @@ void GDScriptAnalyzer::resolve_match_pattern(GDScriptParser::PatternNode *p_matc } p_match_pattern->bind->set_datatype(result); #ifdef DEBUG_ENABLED - is_shadowing(p_match_pattern->bind, "pattern bind"); + is_shadowing(p_match_pattern->bind, "pattern bind", true); if (p_match_pattern->bind->usages == 0 && !String(p_match_pattern->bind->name).begins_with("_")) { parser->push_warning(p_match_pattern->bind, GDScriptWarning::UNUSED_VARIABLE, p_match_pattern->bind->name); } @@ -4517,6 +4515,10 @@ Variant GDScriptAnalyzer::make_variable_default_value(GDScriptParser::VariableNo return result; } +const HashMap<String, Ref<GDScriptParserRef>> &GDScriptAnalyzer::get_depended_parsers() { + return depended_parsers; +} + GDScriptParser::DataType GDScriptAnalyzer::type_from_variant(const Variant &p_value, const GDScriptParser::Node *p_source) { GDScriptParser::DataType result; result.is_constant = true; @@ -4890,8 +4892,8 @@ void GDScriptAnalyzer::validate_call_arg(const List<GDScriptParser::DataType> &p } #ifdef DEBUG_ENABLED -void GDScriptAnalyzer::is_shadowing(GDScriptParser::IdentifierNode *p_local, const String &p_context) { - const StringName &name = p_local->name; +void GDScriptAnalyzer::is_shadowing(GDScriptParser::IdentifierNode *p_identifier, const String &p_context, const bool p_in_local_scope) { + const StringName &name = p_identifier->name; GDScriptParser::DataType base = parser->current_class->get_datatype(); GDScriptParser::ClassNode *base_class = base.class_type; @@ -4901,29 +4903,30 @@ void GDScriptAnalyzer::is_shadowing(GDScriptParser::IdentifierNode *p_local, con for (MethodInfo &info : gdscript_funcs) { if (info.name == name) { - parser->push_warning(p_local, GDScriptWarning::SHADOWED_GLOBAL_IDENTIFIER, p_context, name, "built-in function"); + parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_GLOBAL_IDENTIFIER, p_context, name, "built-in function"); return; } } - if (Variant::has_utility_function(name)) { - parser->push_warning(p_local, GDScriptWarning::SHADOWED_GLOBAL_IDENTIFIER, p_context, name, "built-in function"); + parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_GLOBAL_IDENTIFIER, p_context, name, "built-in function"); return; } else if (ClassDB::class_exists(name)) { - parser->push_warning(p_local, GDScriptWarning::SHADOWED_GLOBAL_IDENTIFIER, p_context, name, "global class"); + parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_GLOBAL_IDENTIFIER, p_context, name, "global class"); return; } else if (GDScriptParser::get_builtin_type(name) != Variant::VARIANT_MAX) { - parser->push_warning(p_local, GDScriptWarning::SHADOWED_GLOBAL_IDENTIFIER, p_context, name, "built-in type"); + parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_GLOBAL_IDENTIFIER, p_context, name, "built-in type"); return; } } - while (base_class != nullptr) { - if (base_class->has_member(name)) { - parser->push_warning(p_local, GDScriptWarning::SHADOWED_VARIABLE, p_context, p_local->name, base_class->get_member(name).get_type_name(), itos(base_class->get_member(name).get_line())); - return; + if (p_in_local_scope) { + while (base_class != nullptr) { + if (base_class->has_member(name)) { + parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE, p_context, p_identifier->name, base_class->get_member(name).get_type_name(), itos(base_class->get_member(name).get_line())); + return; + } + base_class = base_class->base_type.class_type; } - base_class = base_class->base_type.class_type; } StringName parent = base.native_type; @@ -4931,19 +4934,19 @@ void GDScriptAnalyzer::is_shadowing(GDScriptParser::IdentifierNode *p_local, con ERR_FAIL_COND_MSG(!class_exists(parent), "Non-existent native base class."); if (ClassDB::has_method(parent, name, true)) { - parser->push_warning(p_local, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_local->name, "method", parent); + parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_identifier->name, "method", parent); return; } else if (ClassDB::has_signal(parent, name, true)) { - parser->push_warning(p_local, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_local->name, "signal", parent); + parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_identifier->name, "signal", parent); return; } else if (ClassDB::has_property(parent, name, true)) { - parser->push_warning(p_local, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_local->name, "property", parent); + parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_identifier->name, "property", parent); return; } else if (ClassDB::has_integer_constant(parent, name, true)) { - parser->push_warning(p_local, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_local->name, "constant", parent); + parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_identifier->name, "constant", parent); return; } else if (ClassDB::has_enum(parent, name, true)) { - parser->push_warning(p_local, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_local->name, "enum", parent); + parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_identifier->name, "enum", parent); return; } parent = ClassDB::get_parent_class(parent); diff --git a/modules/gdscript/gdscript_analyzer.h b/modules/gdscript/gdscript_analyzer.h index 195e23b503..5bc2c89a87 100644 --- a/modules/gdscript/gdscript_analyzer.h +++ b/modules/gdscript/gdscript_analyzer.h @@ -133,7 +133,7 @@ class GDScriptAnalyzer { Ref<GDScriptParserRef> get_parser_for(const String &p_path); void reduce_identifier_from_base_set_class(GDScriptParser::IdentifierNode *p_identifier, GDScriptParser::DataType p_identifier_datatype); #ifdef DEBUG_ENABLED - void is_shadowing(GDScriptParser::IdentifierNode *p_local, const String &p_context); + void is_shadowing(GDScriptParser::IdentifierNode *p_identifier, const String &p_context, const bool p_in_local_scope); #endif public: @@ -144,6 +144,7 @@ public: Error analyze(); Variant make_variable_default_value(GDScriptParser::VariableNode *p_variable); + const HashMap<String, Ref<GDScriptParserRef>> &get_depended_parsers(); GDScriptAnalyzer(GDScriptParser *p_parser); }; diff --git a/modules/gdscript/gdscript_cache.cpp b/modules/gdscript/gdscript_cache.cpp index e5bb93e3c8..d191bd0224 100644 --- a/modules/gdscript/gdscript_cache.cpp +++ b/modules/gdscript/gdscript_cache.cpp @@ -294,8 +294,12 @@ Ref<GDScript> GDScriptCache::get_full_script(const String &p_path, Error &r_erro if (p_update_from_disk) { r_error = script->load_source_code(p_path); + if (r_error) { + return script; + } } + r_error = script->reload(true); if (r_error) { return script; } @@ -303,7 +307,6 @@ Ref<GDScript> GDScriptCache::get_full_script(const String &p_path, Error &r_erro singleton->full_gdscript_cache[p_path] = script; singleton->shallow_gdscript_cache.erase(p_path); - script->reload(true); return script; } diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp index 004af80a91..3f571602e8 100644 --- a/modules/gdscript/gdscript_compiler.cpp +++ b/modules/gdscript/gdscript_compiler.cpp @@ -2579,9 +2579,9 @@ Error GDScriptCompiler::_populate_class_members(GDScript *p_script, const GDScri } } else if (!base->is_valid()) { Error err = OK; - Ref<GDScript> base_root = GDScriptCache::get_full_script(base->path, err, p_script->path); + Ref<GDScript> base_root = GDScriptCache::get_shallow_script(base->path, err, p_script->path); if (err) { - _set_error(vformat(R"(Could not compile base class "%s" from "%s": %s)", base->fully_qualified_name, base->path, error_names[err]), nullptr); + _set_error(vformat(R"(Could not parse base class "%s" from "%s": %s)", base->fully_qualified_name, base->path, error_names[err]), nullptr); return err; } if (base_root.is_valid()) { @@ -2591,7 +2591,12 @@ Error GDScriptCompiler::_populate_class_members(GDScript *p_script, const GDScri _set_error(vformat(R"(Could not find class "%s" in "%s".)", base->fully_qualified_name, base->path), nullptr); return ERR_COMPILATION_FAILED; } - ERR_FAIL_COND_V(!base->is_valid() && !base->reloading, ERR_BUG); + + err = _populate_class_members(base.ptr(), p_class->base_type.class_type, p_keep_state); + if (err) { + _set_error(vformat(R"(Could not populate class members of base class "%s" in "%s".)", base->fully_qualified_name, base->path), nullptr); + return err; + } } p_script->base = base; @@ -2968,7 +2973,7 @@ Error GDScriptCompiler::compile(const GDScriptParser *p_parser, GDScript *p_scri GDScriptCache::add_static_script(p_script); } - return GDScriptCache::finish_compiling(main_script->get_path()); + return GDScriptCache::finish_compiling(main_script->path); } String GDScriptCompiler::get_error() const { diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index cd34feb8b3..47ceee3943 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -143,14 +143,26 @@ bool GDScriptLanguage::validate(const String &p_script, const String &p_path, Li #endif if (err) { if (r_errors) { - for (const GDScriptParser::ParserError &E : parser.get_errors()) { - const GDScriptParser::ParserError &pe = E; + for (const GDScriptParser::ParserError &pe : parser.get_errors()) { ScriptLanguage::ScriptError e; + e.path = p_path; e.line = pe.line; e.column = pe.column; e.message = pe.message; r_errors->push_back(e); } + + for (KeyValue<String, Ref<GDScriptParserRef>> E : analyzer.get_depended_parsers()) { + GDScriptParser *depended_parser = E.value->get_parser(); + for (const GDScriptParser::ParserError &pe : depended_parser->get_errors()) { + ScriptLanguage::ScriptError e; + e.path = E.key; + e.line = pe.line; + e.column = pe.column; + e.message = pe.message; + r_errors->push_back(e); + } + } } return false; } else { @@ -221,6 +233,10 @@ Script *GDScriptLanguage::create_script() const { /* DEBUGGER FUNCTIONS */ +thread_local int GDScriptLanguage::_debug_parse_err_line = -1; +thread_local String GDScriptLanguage::_debug_parse_err_file; +thread_local String GDScriptLanguage::_debug_error; + bool GDScriptLanguage::debug_break_parse(const String &p_file, int p_line, const String &p_error) { // break because of parse error @@ -229,6 +245,9 @@ bool GDScriptLanguage::debug_break_parse(const String &p_file, int p_line, const _debug_parse_err_file = p_file; _debug_error = p_error; EngineDebugger::get_script_debugger()->debug(this, false, true); + // Because this is thread local, clear the memory afterwards. + _debug_parse_err_file = String(); + _debug_error = String(); return true; } else { return false; @@ -236,12 +255,15 @@ bool GDScriptLanguage::debug_break_parse(const String &p_file, int p_line, const } bool GDScriptLanguage::debug_break(const String &p_error, bool p_allow_continue) { - if (EngineDebugger::is_active() && Thread::get_caller_id() == Thread::get_main_id()) { + if (EngineDebugger::is_active()) { _debug_parse_err_line = -1; _debug_parse_err_file = ""; _debug_error = p_error; bool is_error_breakpoint = p_error != "Breakpoint"; EngineDebugger::get_script_debugger()->debug(this, p_allow_continue, is_error_breakpoint); + // Because this is thread local, clear the memory afterwards. + _debug_parse_err_file = String(); + _debug_error = String(); return true; } else { return false; @@ -257,7 +279,7 @@ int GDScriptLanguage::debug_get_stack_level_count() const { return 1; } - return _debug_call_stack_pos; + return _call_stack.stack_pos; } int GDScriptLanguage::debug_get_stack_level_line(int p_level) const { @@ -265,11 +287,11 @@ int GDScriptLanguage::debug_get_stack_level_line(int p_level) const { return _debug_parse_err_line; } - ERR_FAIL_INDEX_V(p_level, _debug_call_stack_pos, -1); + ERR_FAIL_INDEX_V(p_level, _call_stack.stack_pos, -1); - int l = _debug_call_stack_pos - p_level - 1; + int l = _call_stack.stack_pos - p_level - 1; - return *(_call_stack[l].line); + return *(_call_stack.levels[l].line); } String GDScriptLanguage::debug_get_stack_level_function(int p_level) const { @@ -277,9 +299,9 @@ String GDScriptLanguage::debug_get_stack_level_function(int p_level) const { return ""; } - ERR_FAIL_INDEX_V(p_level, _debug_call_stack_pos, ""); - int l = _debug_call_stack_pos - p_level - 1; - return _call_stack[l].function->get_name(); + ERR_FAIL_INDEX_V(p_level, _call_stack.stack_pos, ""); + int l = _call_stack.stack_pos - p_level - 1; + return _call_stack.levels[l].function->get_name(); } String GDScriptLanguage::debug_get_stack_level_source(int p_level) const { @@ -287,9 +309,9 @@ String GDScriptLanguage::debug_get_stack_level_source(int p_level) const { return _debug_parse_err_file; } - ERR_FAIL_INDEX_V(p_level, _debug_call_stack_pos, ""); - int l = _debug_call_stack_pos - p_level - 1; - return _call_stack[l].function->get_source(); + ERR_FAIL_INDEX_V(p_level, _call_stack.stack_pos, ""); + int l = _call_stack.stack_pos - p_level - 1; + return _call_stack.levels[l].function->get_source(); } void GDScriptLanguage::debug_get_stack_level_locals(int p_level, List<String> *p_locals, List<Variant> *p_values, int p_max_subitems, int p_max_depth) { @@ -297,17 +319,17 @@ void GDScriptLanguage::debug_get_stack_level_locals(int p_level, List<String> *p return; } - ERR_FAIL_INDEX(p_level, _debug_call_stack_pos); - int l = _debug_call_stack_pos - p_level - 1; + ERR_FAIL_INDEX(p_level, _call_stack.stack_pos); + int l = _call_stack.stack_pos - p_level - 1; - GDScriptFunction *f = _call_stack[l].function; + GDScriptFunction *f = _call_stack.levels[l].function; List<Pair<StringName, int>> locals; - f->debug_get_stack_member_state(*_call_stack[l].line, &locals); + f->debug_get_stack_member_state(*_call_stack.levels[l].line, &locals); for (const Pair<StringName, int> &E : locals) { p_locals->push_back(E.first); - p_values->push_back(_call_stack[l].stack[E.second]); + p_values->push_back(_call_stack.levels[l].stack[E.second]); } } @@ -316,10 +338,10 @@ void GDScriptLanguage::debug_get_stack_level_members(int p_level, List<String> * return; } - ERR_FAIL_INDEX(p_level, _debug_call_stack_pos); - int l = _debug_call_stack_pos - p_level - 1; + ERR_FAIL_INDEX(p_level, _call_stack.stack_pos); + int l = _call_stack.stack_pos - p_level - 1; - GDScriptInstance *instance = _call_stack[l].instance; + GDScriptInstance *instance = _call_stack.levels[l].instance; if (!instance) { return; @@ -341,10 +363,10 @@ ScriptInstance *GDScriptLanguage::debug_get_stack_level_instance(int p_level) { return nullptr; } - ERR_FAIL_INDEX_V(p_level, _debug_call_stack_pos, nullptr); + ERR_FAIL_INDEX_V(p_level, _call_stack.stack_pos, nullptr); - int l = _debug_call_stack_pos - p_level - 1; - ScriptInstance *instance = _call_stack[l].instance; + int l = _call_stack.stack_pos - p_level - 1; + ScriptInstance *instance = _call_stack.levels[l].instance; return instance; } @@ -1435,11 +1457,11 @@ static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context, } break; case GDScriptParser::Node::SELF: { if (p_context.current_class) { - r_type.type.kind = GDScriptParser::DataType::CLASS; - r_type.type.type_source = GDScriptParser::DataType::INFERRED; - r_type.type.is_constant = true; - r_type.type.class_type = p_context.current_class; - r_type.value = p_context.base; + if (p_context.type != GDScriptParser::COMPLETION_SUPER_METHOD) { + r_type.type = p_context.current_class->get_datatype(); + } else { + r_type.type = p_context.current_class->base_type; + } found = true; } } break; diff --git a/modules/gdscript/gdscript_function.h b/modules/gdscript/gdscript_function.h index 9bbfb14f31..dfe66e6688 100644 --- a/modules/gdscript/gdscript_function.h +++ b/modules/gdscript/gdscript_function.h @@ -539,12 +539,12 @@ private: struct Profile { StringName signature; - uint64_t call_count = 0; - uint64_t self_time = 0; - uint64_t total_time = 0; - uint64_t frame_call_count = 0; - uint64_t frame_self_time = 0; - uint64_t frame_total_time = 0; + SafeNumeric<uint64_t> call_count; + SafeNumeric<uint64_t> self_time; + SafeNumeric<uint64_t> total_time; + SafeNumeric<uint64_t> frame_call_count; + SafeNumeric<uint64_t> frame_self_time; + SafeNumeric<uint64_t> frame_total_time; uint64_t last_frame_call_count = 0; uint64_t last_frame_self_time = 0; uint64_t last_frame_total_time = 0; diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index 5c2d4a060c..2b91ba8f86 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -783,20 +783,6 @@ void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)(b if (member->identifier != nullptr) { if (!((String)member->identifier->name).is_empty()) { // Enums may be unnamed. - -#ifdef DEBUG_ENABLED - List<MethodInfo> gdscript_funcs; - GDScriptLanguage::get_singleton()->get_public_functions(&gdscript_funcs); - for (MethodInfo &info : gdscript_funcs) { - if (info.name == member->identifier->name) { - push_warning(member->identifier, GDScriptWarning::SHADOWED_GLOBAL_IDENTIFIER, p_member_kind, member->identifier->name, "built-in function"); - } - } - if (Variant::has_utility_function(member->identifier->name)) { - push_warning(member->identifier, GDScriptWarning::SHADOWED_GLOBAL_IDENTIFIER, p_member_kind, member->identifier->name, "built-in function"); - } -#endif - if (current_class->members_indices.has(member->identifier->name)) { push_error(vformat(R"(%s "%s" has the same name as a previously declared %s.)", p_member_kind.capitalize(), member->identifier->name, current_class->get_member(member->identifier->name).get_type_name()), member->identifier); } else { diff --git a/modules/gdscript/gdscript_vm.cpp b/modules/gdscript/gdscript_vm.cpp index a8c9cfa579..44c4cb0fc3 100644 --- a/modules/gdscript/gdscript_vm.cpp +++ b/modules/gdscript/gdscript_vm.cpp @@ -663,8 +663,8 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a if (GDScriptLanguage::get_singleton()->profiling) { function_start_time = OS::get_singleton()->get_ticks_usec(); function_call_time = 0; - profile.call_count++; - profile.frame_call_count++; + profile.call_count.increment(); + profile.frame_call_count.increment(); } bool exit_ok = false; bool awaited = false; @@ -3550,7 +3550,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a // line bool do_break = false; - if (EngineDebugger::get_script_debugger()->get_lines_left() > 0) { + if (unlikely(EngineDebugger::get_script_debugger()->get_lines_left() > 0)) { if (EngineDebugger::get_script_debugger()->get_depth() <= 0) { EngineDebugger::get_script_debugger()->set_lines_left(EngineDebugger::get_script_debugger()->get_lines_left() - 1); } @@ -3563,7 +3563,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a do_break = true; } - if (do_break) { + if (unlikely(do_break)) { GDScriptLanguage::get_singleton()->debug_break("Breakpoint", true); } @@ -3630,11 +3630,13 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a #ifdef DEBUG_ENABLED if (GDScriptLanguage::get_singleton()->profiling) { uint64_t time_taken = OS::get_singleton()->get_ticks_usec() - function_start_time; - profile.total_time += time_taken; - profile.self_time += time_taken - function_call_time; - profile.frame_total_time += time_taken; - profile.frame_self_time += time_taken - function_call_time; - GDScriptLanguage::get_singleton()->script_frame_time += time_taken - function_call_time; + profile.total_time.add(time_taken); + profile.self_time.add(time_taken - function_call_time); + profile.frame_total_time.add(time_taken); + profile.frame_self_time.add(time_taken - function_call_time); + if (Thread::get_caller_id() == Thread::get_main_id()) { + GDScriptLanguage::get_singleton()->script_frame_time += time_taken - function_call_time; + } } // Check if this is not the last time it was interrupted by `await` or if it's the first time executing. diff --git a/modules/gdscript/register_types.cpp b/modules/gdscript/register_types.cpp index e23bd50b8b..605e82be6e 100644 --- a/modules/gdscript/register_types.cpp +++ b/modules/gdscript/register_types.cpp @@ -98,7 +98,7 @@ public: return; } - virtual String _get_name() const override { return "GDScript"; } + virtual String get_name() const override { return "GDScript"; } }; static void _editor_init() { diff --git a/modules/gdscript/tests/scripts/analyzer/errors/abstract_class_instantiate.gd b/modules/gdscript/tests/scripts/analyzer/errors/abstract_class_instantiate.gd index 38c2faa859..8709b89b54 100644 --- a/modules/gdscript/tests/scripts/analyzer/errors/abstract_class_instantiate.gd +++ b/modules/gdscript/tests/scripts/analyzer/errors/abstract_class_instantiate.gd @@ -1,2 +1,2 @@ func test(): - CanvasItem.new() + InstancePlaceholder.new() diff --git a/modules/gdscript/tests/scripts/analyzer/errors/abstract_class_instantiate.out b/modules/gdscript/tests/scripts/analyzer/errors/abstract_class_instantiate.out index 9eff912b59..36224c6b6f 100644 --- a/modules/gdscript/tests/scripts/analyzer/errors/abstract_class_instantiate.out +++ b/modules/gdscript/tests/scripts/analyzer/errors/abstract_class_instantiate.out @@ -1,2 +1,2 @@ GDTEST_ANALYZER_ERROR -Native class "CanvasItem" cannot be constructed as it is abstract. +Native class "InstancePlaceholder" cannot be constructed as it is abstract. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/abstract_script_instantiate.gd b/modules/gdscript/tests/scripts/analyzer/errors/abstract_script_instantiate.gd index 118e7e8a45..be67182efb 100644 --- a/modules/gdscript/tests/scripts/analyzer/errors/abstract_script_instantiate.gd +++ b/modules/gdscript/tests/scripts/analyzer/errors/abstract_script_instantiate.gd @@ -1,4 +1,4 @@ -class A extends CanvasItem: +class A extends InstancePlaceholder: func _init(): print('no') diff --git a/modules/gdscript/tests/scripts/analyzer/errors/abstract_script_instantiate.out b/modules/gdscript/tests/scripts/analyzer/errors/abstract_script_instantiate.out index 8b956f5974..260f062555 100644 --- a/modules/gdscript/tests/scripts/analyzer/errors/abstract_script_instantiate.out +++ b/modules/gdscript/tests/scripts/analyzer/errors/abstract_script_instantiate.out @@ -1,2 +1,2 @@ GDTEST_ANALYZER_ERROR -Class "abstract_script_instantiate.gd::B" cannot be constructed as it is based on abstract native class "CanvasItem". +Class "abstract_script_instantiate.gd::B" cannot be constructed as it is based on abstract native class "InstancePlaceholder". diff --git a/modules/gdscript/tests/scripts/analyzer/features/allow_get_node_with_onready.gd b/modules/gdscript/tests/scripts/analyzer/features/allow_get_node_with_onready.gd index a9004a346b..3e647407cd 100644 --- a/modules/gdscript/tests/scripts/analyzer/features/allow_get_node_with_onready.gd +++ b/modules/gdscript/tests/scripts/analyzer/features/allow_get_node_with_onready.gd @@ -1,7 +1,7 @@ extends Node @onready var shorthand = $Node -@onready var call = get_node(^"Node") +@onready var call_no_cast = get_node(^"Node") @onready var shorthand_with_cast = $Node as Node @onready var call_with_cast = get_node(^"Node") as Node @@ -13,6 +13,6 @@ func _init(): func test(): # Those are expected to be `null` since `_ready()` is never called on tests. prints("shorthand", shorthand) - prints("call", call) + prints("call_no_cast", call_no_cast) prints("shorthand_with_cast", shorthand_with_cast) prints("call_with_cast", call_with_cast) diff --git a/modules/gdscript/tests/scripts/analyzer/features/allow_get_node_with_onready.out b/modules/gdscript/tests/scripts/analyzer/features/allow_get_node_with_onready.out index eddc2deec0..78b830aad0 100644 --- a/modules/gdscript/tests/scripts/analyzer/features/allow_get_node_with_onready.out +++ b/modules/gdscript/tests/scripts/analyzer/features/allow_get_node_with_onready.out @@ -1,5 +1,5 @@ GDTEST_OK shorthand <null> -call <null> +call_no_cast <null> shorthand_with_cast <null> call_with_cast <null> diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/shadowning.gd b/modules/gdscript/tests/scripts/analyzer/warnings/shadowning.gd index 61945c9c8f..29239a19da 100644 --- a/modules/gdscript/tests/scripts/analyzer/warnings/shadowning.gd +++ b/modules/gdscript/tests/scripts/analyzer/warnings/shadowning.gd @@ -1,5 +1,9 @@ var member: int = 0 +var print_debug := 'print_debug' +@warning_ignore("shadowed_global_identifier") +var print := 'print' + @warning_ignore("unused_variable") func test(): var Array := 'Array' diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/shadowning.out b/modules/gdscript/tests/scripts/analyzer/warnings/shadowning.out index 8467697a96..accc791d8a 100644 --- a/modules/gdscript/tests/scripts/analyzer/warnings/shadowning.out +++ b/modules/gdscript/tests/scripts/analyzer/warnings/shadowning.out @@ -1,26 +1,30 @@ GDTEST_OK >> WARNING ->> Line: 5 +>> Line: 3 +>> SHADOWED_GLOBAL_IDENTIFIER +>> The variable "print_debug" has the same name as a built-in function. +>> WARNING +>> Line: 9 >> SHADOWED_GLOBAL_IDENTIFIER >> The variable "Array" has the same name as a built-in type. >> WARNING ->> Line: 6 +>> Line: 10 >> SHADOWED_GLOBAL_IDENTIFIER >> The variable "Node" has the same name as a global class. >> WARNING ->> Line: 7 +>> Line: 11 >> SHADOWED_GLOBAL_IDENTIFIER >> The variable "is_same" has the same name as a built-in function. >> WARNING ->> Line: 8 +>> Line: 12 >> SHADOWED_GLOBAL_IDENTIFIER >> The variable "sqrt" has the same name as a built-in function. >> WARNING ->> Line: 9 +>> Line: 13 >> SHADOWED_VARIABLE >> The local variable "member" is shadowing an already-declared variable at line 1. >> WARNING ->> Line: 10 +>> Line: 14 >> SHADOWED_VARIABLE_BASE_CLASS >> The local variable "reference" is shadowing an already-declared method at the base class "RefCounted". warn diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp index 0bbc2bbf74..d828363e03 100644 --- a/modules/gltf/gltf_document.cpp +++ b/modules/gltf/gltf_document.cpp @@ -49,6 +49,8 @@ #include "scene/3d/light_3d.h" #include "scene/3d/mesh_instance_3d.h" #include "scene/3d/multimesh_instance_3d.h" +#include "scene/resources/image_texture.h" +#include "scene/resources/portable_compressed_texture.h" #include "scene/resources/skin.h" #include "scene/resources/surface_tool.h" @@ -3769,6 +3771,12 @@ Error GLTFDocument::_serialize_materials(Ref<GLTFState> p_state) { extensions["KHR_materials_unlit"] = mat_unlit; p_state->add_used_extension("KHR_materials_unlit"); } + if (base_material->get_feature(BaseMaterial3D::FEATURE_EMISSION) && !Math::is_equal_approx(base_material->get_emission_energy_multiplier(), 1.0f)) { + Dictionary mat_emissive_strength; + mat_emissive_strength["emissiveStrength"] = base_material->get_emission_energy_multiplier(); + extensions["KHR_materials_emissive_strength"] = mat_emissive_strength; + p_state->add_used_extension("KHR_materials_emissive_strength"); + } d["extensions"] = extensions; materials.push_back(d); @@ -3789,35 +3797,35 @@ Error GLTFDocument::_parse_materials(Ref<GLTFState> p_state) { const Array &materials = p_state->json["materials"]; for (GLTFMaterialIndex i = 0; i < materials.size(); i++) { - const Dictionary &d = materials[i]; + const Dictionary &material_dict = materials[i]; Ref<StandardMaterial3D> material; material.instantiate(); - if (d.has("name") && !String(d["name"]).is_empty()) { - material->set_name(d["name"]); + if (material_dict.has("name") && !String(material_dict["name"]).is_empty()) { + material->set_name(material_dict["name"]); } else { material->set_name(vformat("material_%s", itos(i))); } material->set_flag(BaseMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true); - Dictionary pbr_spec_gloss_extensions; - if (d.has("extensions")) { - pbr_spec_gloss_extensions = d["extensions"]; + Dictionary material_extensions; + if (material_dict.has("extensions")) { + material_extensions = material_dict["extensions"]; } - if (pbr_spec_gloss_extensions.has("KHR_materials_unlit")) { + if (material_extensions.has("KHR_materials_unlit")) { material->set_shading_mode(BaseMaterial3D::SHADING_MODE_UNSHADED); } - if (pbr_spec_gloss_extensions.has("KHR_materials_emissive_strength")) { - Dictionary emissive_strength = pbr_spec_gloss_extensions["KHR_materials_emissive_strength"]; + if (material_extensions.has("KHR_materials_emissive_strength")) { + Dictionary emissive_strength = material_extensions["KHR_materials_emissive_strength"]; if (emissive_strength.has("emissiveStrength")) { material->set_emission_energy_multiplier(emissive_strength["emissiveStrength"]); } } - if (pbr_spec_gloss_extensions.has("KHR_materials_pbrSpecularGlossiness")) { + if (material_extensions.has("KHR_materials_pbrSpecularGlossiness")) { WARN_PRINT("Material uses a specular and glossiness workflow. Textures will be converted to roughness and metallic workflow, which may not be 100% accurate."); - Dictionary sgm = pbr_spec_gloss_extensions["KHR_materials_pbrSpecularGlossiness"]; + Dictionary sgm = material_extensions["KHR_materials_pbrSpecularGlossiness"]; Ref<GLTFSpecGloss> spec_gloss; spec_gloss.instantiate(); @@ -3865,8 +3873,8 @@ Error GLTFDocument::_parse_materials(Ref<GLTFState> p_state) { } spec_gloss_to_rough_metal(spec_gloss, material); - } else if (d.has("pbrMetallicRoughness")) { - const Dictionary &mr = d["pbrMetallicRoughness"]; + } else if (material_dict.has("pbrMetallicRoughness")) { + const Dictionary &mr = material_dict["pbrMetallicRoughness"]; if (mr.has("baseColorFactor")) { const Array &arr = mr["baseColorFactor"]; ERR_FAIL_COND_V(arr.size() != 4, ERR_PARSE_ERROR); @@ -3918,8 +3926,8 @@ Error GLTFDocument::_parse_materials(Ref<GLTFState> p_state) { } } - if (d.has("normalTexture")) { - const Dictionary &bct = d["normalTexture"]; + if (material_dict.has("normalTexture")) { + const Dictionary &bct = material_dict["normalTexture"]; if (bct.has("index")) { material->set_texture(BaseMaterial3D::TEXTURE_NORMAL, _get_texture(p_state, bct["index"], TEXTURE_TYPE_NORMAL)); material->set_feature(BaseMaterial3D::FEATURE_NORMAL_MAPPING, true); @@ -3928,8 +3936,8 @@ Error GLTFDocument::_parse_materials(Ref<GLTFState> p_state) { material->set_normal_scale(bct["scale"]); } } - if (d.has("occlusionTexture")) { - const Dictionary &bct = d["occlusionTexture"]; + if (material_dict.has("occlusionTexture")) { + const Dictionary &bct = material_dict["occlusionTexture"]; if (bct.has("index")) { material->set_texture(BaseMaterial3D::TEXTURE_AMBIENT_OCCLUSION, _get_texture(p_state, bct["index"], TEXTURE_TYPE_GENERIC)); material->set_ao_texture_channel(BaseMaterial3D::TEXTURE_CHANNEL_RED); @@ -3937,8 +3945,8 @@ Error GLTFDocument::_parse_materials(Ref<GLTFState> p_state) { } } - if (d.has("emissiveFactor")) { - const Array &arr = d["emissiveFactor"]; + if (material_dict.has("emissiveFactor")) { + const Array &arr = material_dict["emissiveFactor"]; ERR_FAIL_COND_V(arr.size() != 3, ERR_PARSE_ERROR); const Color c = Color(arr[0], arr[1], arr[2]).linear_to_srgb(); material->set_feature(BaseMaterial3D::FEATURE_EMISSION, true); @@ -3946,8 +3954,8 @@ Error GLTFDocument::_parse_materials(Ref<GLTFState> p_state) { material->set_emission(c); } - if (d.has("emissiveTexture")) { - const Dictionary &bct = d["emissiveTexture"]; + if (material_dict.has("emissiveTexture")) { + const Dictionary &bct = material_dict["emissiveTexture"]; if (bct.has("index")) { material->set_texture(BaseMaterial3D::TEXTURE_EMISSION, _get_texture(p_state, bct["index"], TEXTURE_TYPE_GENERIC)); material->set_feature(BaseMaterial3D::FEATURE_EMISSION, true); @@ -3955,20 +3963,20 @@ Error GLTFDocument::_parse_materials(Ref<GLTFState> p_state) { } } - if (d.has("doubleSided")) { - const bool ds = d["doubleSided"]; + if (material_dict.has("doubleSided")) { + const bool ds = material_dict["doubleSided"]; if (ds) { material->set_cull_mode(BaseMaterial3D::CULL_DISABLED); } } - if (d.has("alphaMode")) { - const String &am = d["alphaMode"]; + if (material_dict.has("alphaMode")) { + const String &am = material_dict["alphaMode"]; if (am == "BLEND") { material->set_transparency(BaseMaterial3D::TRANSPARENCY_ALPHA_DEPTH_PRE_PASS); } else if (am == "MASK") { material->set_transparency(BaseMaterial3D::TRANSPARENCY_ALPHA_SCISSOR); - if (d.has("alphaCutoff")) { - material->set_alpha_scissor_threshold(d["alphaCutoff"]); + if (material_dict.has("alphaCutoff")) { + material->set_alpha_scissor_threshold(material_dict["alphaCutoff"]); } else { material->set_alpha_scissor_threshold(0.5f); } diff --git a/modules/gltf/register_types.cpp b/modules/gltf/register_types.cpp index 87149fa11d..1788ffac3a 100644 --- a/modules/gltf/register_types.cpp +++ b/modules/gltf/register_types.cpp @@ -70,9 +70,9 @@ static void _editor_init() { if (blend_enabled) { Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); if (blender3_path.is_empty()) { - WARN_PRINT("Blend file import is enabled in the project settings, but no Blender path is configured in the editor settings. Blend files will not be imported."); + WARN_PRINT(TTR("Blend file import is enabled in the project settings, but no Blender path is configured in the editor settings. Blend files will not be imported.")); } else if (!da->dir_exists(blender3_path)) { - WARN_PRINT("Blend file import is enabled, but the Blender path doesn't point to an accessible directory. Blend files will not be imported."); + WARN_PRINT(TTR("Blend file import is enabled, but the Blender path doesn't point to an accessible directory. Blend files will not be imported.")); } else { Ref<EditorSceneFormatImporterBlend> importer; importer.instantiate(); diff --git a/modules/gridmap/doc_classes/GridMap.xml b/modules/gridmap/doc_classes/GridMap.xml index 6973bd3cd8..f9c3ca476a 100644 --- a/modules/gridmap/doc_classes/GridMap.xml +++ b/modules/gridmap/doc_classes/GridMap.xml @@ -138,11 +138,11 @@ Returns the position of a grid cell in the GridMap's local coordinate space. To convert the returned value into global coordinates, use [method Node3D.to_global]. See also [method map_to_local]. </description> </method> - <method name="resource_changed"> + <method name="resource_changed" is_deprecated="true"> <return type="void" /> <param index="0" name="resource" type="Resource" /> <description> - Notifies the [GridMap] about changed resource and recreates octant data. + [i]Obsoleted.[/i] Use [signal Resource.changed] instead. </description> </method> <method name="set_cell_item"> diff --git a/modules/gridmap/grid_map.cpp b/modules/gridmap/grid_map.cpp index c77fa98be2..f1e2218434 100644 --- a/modules/gridmap/grid_map.cpp +++ b/modules/gridmap/grid_map.cpp @@ -258,11 +258,11 @@ RID GridMap::get_navigation_map() const { void GridMap::set_mesh_library(const Ref<MeshLibrary> &p_mesh_library) { if (!mesh_library.is_null()) { - mesh_library->unregister_owner(this); + mesh_library->disconnect_changed(callable_mp(this, &GridMap::_recreate_octant_data)); } mesh_library = p_mesh_library; if (!mesh_library.is_null()) { - mesh_library->register_owner(this); + mesh_library->connect_changed(callable_mp(this, &GridMap::_recreate_octant_data)); } _recreate_octant_data(); @@ -1005,9 +1005,10 @@ void GridMap::clear() { clear_baked_meshes(); } +#ifndef DISABLE_DEPRECATED void GridMap::resource_changed(const Ref<Resource> &p_res) { - _recreate_octant_data(); } +#endif void GridMap::_update_octants_callback() { if (!awaiting_update) { @@ -1079,7 +1080,9 @@ void GridMap::_bind_methods() { ClassDB::bind_method(D_METHOD("map_to_local", "map_position"), &GridMap::map_to_local); ClassDB::bind_method(D_METHOD("_update_octants_callback"), &GridMap::_update_octants_callback); +#ifndef DISABLE_DEPRECATED ClassDB::bind_method(D_METHOD("resource_changed", "resource"), &GridMap::resource_changed); +#endif ClassDB::bind_method(D_METHOD("set_center_x", "enable"), &GridMap::set_center_x); ClassDB::bind_method(D_METHOD("get_center_x"), &GridMap::get_center_x); @@ -1336,10 +1339,6 @@ void GridMap::_navigation_map_changed(RID p_map) { #endif // DEBUG_ENABLED GridMap::~GridMap() { - if (!mesh_library.is_null()) { - mesh_library->unregister_owner(this); - } - clear(); #ifdef DEBUG_ENABLED NavigationServer3D::get_singleton()->disconnect("map_changed", callable_mp(this, &GridMap::_navigation_map_changed)); diff --git a/modules/gridmap/grid_map.h b/modules/gridmap/grid_map.h index 18c3f90269..e05979efbc 100644 --- a/modules/gridmap/grid_map.h +++ b/modules/gridmap/grid_map.h @@ -203,7 +203,9 @@ class GridMap : public Node3D { void _queue_octants_dirty(); void _update_octants_callback(); +#ifndef DISABLE_DEPRECATED void resource_changed(const Ref<Resource> &p_res); +#endif void _clear_internal(); diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs index 8be1151142..72614dd7e0 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs @@ -384,5 +384,65 @@ namespace Godot.SourceGenerators typeArgumentSyntax.GetLocation(), typeArgumentSyntax.SyntaxTree.FilePath)); } + + public static readonly DiagnosticDescriptor GlobalClassMustDeriveFromGodotObjectRule = + new DiagnosticDescriptor(id: "GD0401", + title: "The class must derive from GodotObject or a derived class", + messageFormat: "The class '{0}' must derive from GodotObject or a derived class.", + category: "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + "The class must derive from GodotObject or a derived class. Change the base class or remove the '[GlobalClass]' attribute."); + + public static void ReportGlobalClassMustDeriveFromGodotObject( + SyntaxNodeAnalysisContext context, + SyntaxNode classSyntax, + ISymbol typeSymbol) + { + string message = $"The class '{typeSymbol.ToDisplayString()}' must derive from GodotObject or a derived class"; + + string description = $"{message}. Change the base class or remove the '[GlobalClass]' attribute."; + + context.ReportDiagnostic(Diagnostic.Create( + new DiagnosticDescriptor(id: "GD0401", + title: message, + messageFormat: message, + category: "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description), + classSyntax.GetLocation(), + classSyntax.SyntaxTree.FilePath)); + } + + public static readonly DiagnosticDescriptor GlobalClassMustNotBeGenericRule = + new DiagnosticDescriptor(id: "GD0402", + title: "The class must not contain generic arguments", + messageFormat: "The class '{0}' must not contain generic arguments", + category: "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + "The class must be a non-generic type. Remove the generic arguments or the '[GlobalClass]' attribute."); + + public static void ReportGlobalClassMustNotBeGeneric( + SyntaxNodeAnalysisContext context, + SyntaxNode classSyntax, + ISymbol typeSymbol) + { + string message = $"The class '{typeSymbol.ToDisplayString()}' must not contain generic arguments"; + + string description = $"{message}. Remove the generic arguments or the '[GlobalClass]' attribute."; + + context.ReportDiagnostic(Diagnostic.Create( + new DiagnosticDescriptor(id: "GD0402", + title: message, + messageFormat: message, + category: "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description), + classSyntax.GetLocation(), + classSyntax.SyntaxTree.FilePath)); + } } } diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs index b2a3c046e5..b6ea4b8e88 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs @@ -81,7 +81,7 @@ namespace Godot.SourceGenerators return godotClassName ?? nativeType.Name; } - private static bool IsGodotScriptClass( + private static bool TryGetGodotScriptClass( this ClassDeclarationSyntax cds, Compilation compilation, out INamedTypeSymbol? symbol ) @@ -108,7 +108,7 @@ namespace Godot.SourceGenerators { foreach (var cds in source) { - if (cds.IsGodotScriptClass(compilation, out var symbol)) + if (cds.TryGetGodotScriptClass(compilation, out var symbol)) yield return (cds, symbol!); } } diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GlobalClassAnalyzer.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GlobalClassAnalyzer.cs new file mode 100644 index 0000000000..bcb35dae8a --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GlobalClassAnalyzer.cs @@ -0,0 +1,42 @@ +using System.Collections.Immutable; +using System.Linq; + +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace Godot.SourceGenerators +{ + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public class GlobalClassAnalyzer : DiagnosticAnalyzer + { + public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics + => ImmutableArray.Create( + Common.GlobalClassMustDeriveFromGodotObjectRule, + Common.GlobalClassMustNotBeGenericRule); + + public override void Initialize(AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.EnableConcurrentExecution(); + context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.ClassDeclaration); + } + + private void AnalyzeNode(SyntaxNodeAnalysisContext context) + { + var typeClassDecl = (ClassDeclarationSyntax)context.Node; + + // Return if not a type symbol or the type is not a global class. + if (context.ContainingSymbol is not INamedTypeSymbol typeSymbol || + !typeSymbol.GetAttributes().Any(a => a.AttributeClass?.IsGodotGlobalClassAttribute() ?? false)) + return; + + if (typeSymbol.IsGenericType) + Common.ReportGlobalClassMustNotBeGeneric(context, typeClassDecl, typeSymbol); + + if (!typeSymbol.InheritsFrom("GodotSharp", GodotClasses.GodotObject)) + Common.ReportGlobalClassMustDeriveFromGodotObject(context, typeClassDecl, typeSymbol); + } + } +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptMethodsGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptMethodsGenerator.cs index 1affe692d0..5ea0ca53c3 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptMethodsGenerator.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptMethodsGenerator.cs @@ -135,6 +135,10 @@ namespace Godot.SourceGenerators source.Append("#pragma warning disable CS0109 // Disable warning about redundant 'new' keyword\n"); + source.Append(" /// <summary>\n") + .Append(" /// Cached StringNames for the methods contained in this class, for fast lookup.\n") + .Append(" /// </summary>\n"); + source.Append( $" public new class MethodName : {symbol.BaseType.FullQualifiedNameIncludeGlobal()}.MethodName {{\n"); @@ -147,6 +151,12 @@ namespace Godot.SourceGenerators foreach (string methodName in distinctMethodNames) { + source.Append(" /// <summary>\n") + .Append(" /// Cached name for the '") + .Append(methodName) + .Append("' method.\n") + .Append(" /// </summary>\n"); + source.Append(" public new static readonly global::Godot.StringName "); source.Append(methodName); source.Append(" = \""); @@ -162,6 +172,14 @@ namespace Godot.SourceGenerators { const string listType = "global::System.Collections.Generic.List<global::Godot.Bridge.MethodInfo>"; + source.Append(" /// <summary>\n") + .Append(" /// Get the method information for all the methods declared in this class.\n") + .Append(" /// This method is used by Godot to register the available methods in the editor.\n") + .Append(" /// Do not call this method.\n") + .Append(" /// </summary>\n"); + + source.Append(" [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]\n"); + source.Append(" internal new static ") .Append(listType) .Append(" GetGodotMethodList()\n {\n"); @@ -188,6 +206,8 @@ namespace Godot.SourceGenerators if (godotClassMethods.Length > 0) { + source.Append(" /// <inheritdoc/>\n"); + source.Append(" [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]\n"); source.Append(" protected override bool InvokeGodotClassMethod(in godot_string_name method, "); source.Append("NativeVariantPtrArgs args, out godot_variant ret)\n {\n"); @@ -205,6 +225,8 @@ namespace Godot.SourceGenerators if (distinctMethodNames.Length > 0) { + source.Append(" /// <inheritdoc/>\n"); + source.Append(" [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]\n"); source.Append(" protected override bool HasGodotClassMethod(in godot_string_name method)\n {\n"); bool isFirstEntry = true; diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs index 3e6858485d..94d8696717 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs @@ -124,6 +124,10 @@ namespace Godot.SourceGenerators source.Append("#pragma warning disable CS0109 // Disable warning about redundant 'new' keyword\n"); + source.Append(" /// <summary>\n") + .Append(" /// Cached StringNames for the properties and fields contained in this class, for fast lookup.\n") + .Append(" /// </summary>\n"); + source.Append( $" public new class PropertyName : {symbol.BaseType.FullQualifiedNameIncludeGlobal()}.PropertyName {{\n"); @@ -132,6 +136,13 @@ namespace Godot.SourceGenerators foreach (var property in godotClassProperties) { string propertyName = property.PropertySymbol.Name; + + source.Append(" /// <summary>\n") + .Append(" /// Cached name for the '") + .Append(propertyName) + .Append("' property.\n") + .Append(" /// </summary>\n"); + source.Append(" public new static readonly global::Godot.StringName "); source.Append(propertyName); source.Append(" = \""); @@ -142,6 +153,13 @@ namespace Godot.SourceGenerators foreach (var field in godotClassFields) { string fieldName = field.FieldSymbol.Name; + + source.Append(" /// <summary>\n") + .Append(" /// Cached name for the '") + .Append(fieldName) + .Append("' field.\n") + .Append(" /// </summary>\n"); + source.Append(" public new static readonly global::Godot.StringName "); source.Append(fieldName); source.Append(" = \""); @@ -162,6 +180,8 @@ namespace Godot.SourceGenerators if (!allPropertiesAreReadOnly) { + source.Append(" /// <inheritdoc/>\n"); + source.Append(" [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]\n"); source.Append(" protected override bool SetGodotClassPropertyValue(in godot_string_name name, "); source.Append("in godot_variant value)\n {\n"); @@ -193,6 +213,8 @@ namespace Godot.SourceGenerators // Generate GetGodotClassPropertyValue + source.Append(" /// <inheritdoc/>\n"); + source.Append(" [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]\n"); source.Append(" protected override bool GetGodotClassPropertyValue(in godot_string_name name, "); source.Append("out godot_variant value)\n {\n"); @@ -217,7 +239,15 @@ namespace Godot.SourceGenerators // Generate GetGodotPropertyList - string dictionaryType = "global::System.Collections.Generic.List<global::Godot.Bridge.PropertyInfo>"; + const string dictionaryType = "global::System.Collections.Generic.List<global::Godot.Bridge.PropertyInfo>"; + + source.Append(" /// <summary>\n") + .Append(" /// Get the property information for all the properties declared in this class.\n") + .Append(" /// This method is used by Godot to register the available properties in the editor.\n") + .Append(" /// Do not call this method.\n") + .Append(" /// </summary>\n"); + + source.Append(" [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]\n"); source.Append(" internal new static ") .Append(dictionaryType) @@ -620,7 +650,7 @@ namespace Godot.SourceGenerators bool isPresetHint = false; - if (elementVariantType == VariantType.String) + if (elementVariantType == VariantType.String || elementVariantType == VariantType.StringName) isPresetHint = GetStringArrayEnumHint(elementVariantType, exportAttr, out hintString); if (!isPresetHint) diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertyDefValGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertyDefValGenerator.cs index ac908a6de3..4df16d05f0 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertyDefValGenerator.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertyDefValGenerator.cs @@ -285,10 +285,20 @@ namespace Godot.SourceGenerators { source.Append("#pragma warning disable CS0109 // Disable warning about redundant 'new' keyword\n"); - string dictionaryType = + const string dictionaryType = "global::System.Collections.Generic.Dictionary<global::Godot.StringName, global::Godot.Variant>"; source.Append("#if TOOLS\n"); + + source.Append(" /// <summary>\n") + .Append(" /// Get the default values for all properties declared in this class.\n") + .Append(" /// This method is used by Godot to determine the value that will be\n") + .Append(" /// used by the inspector when resetting properties.\n") + .Append(" /// Do not call this method.\n") + .Append(" /// </summary>\n"); + + source.Append(" [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]\n"); + source.Append(" internal new static "); source.Append(dictionaryType); source.Append(" GetGodotPropertyDefaultValues()\n {\n"); @@ -320,7 +330,8 @@ namespace Godot.SourceGenerators source.Append(" return values;\n"); source.Append(" }\n"); - source.Append("#endif\n"); + + source.Append("#endif // TOOLS\n"); source.Append("#pragma warning restore CS0109\n"); } diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSerializationGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSerializationGenerator.cs index 97771b721d..231a7be021 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSerializationGenerator.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSerializationGenerator.cs @@ -149,6 +149,8 @@ namespace Godot.SourceGenerators godotSignalDelegates.Add(new(signalName, signalDelegateSymbol, invokeMethodData.Value)); } + source.Append(" /// <inheritdoc/>\n"); + source.Append(" [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]\n"); source.Append( " protected override void SaveGodotObjectData(global::Godot.Bridge.GodotSerializationInfo info)\n {\n"); source.Append(" base.SaveGodotObjectData(info);\n"); @@ -196,6 +198,8 @@ namespace Godot.SourceGenerators source.Append(" }\n"); + source.Append(" /// <inheritdoc/>\n"); + source.Append(" [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]\n"); source.Append( " protected override void RestoreGodotObjectData(global::Godot.Bridge.GodotSerializationInfo info)\n {\n"); source.Append(" base.RestoreGodotObjectData(info);\n"); diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs index 7e3323f588..8f2774d5ae 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs @@ -176,6 +176,10 @@ namespace Godot.SourceGenerators source.Append("#pragma warning disable CS0109 // Disable warning about redundant 'new' keyword\n"); + source.Append(" /// <summary>\n") + .Append(" /// Cached StringNames for the signals contained in this class, for fast lookup.\n") + .Append(" /// </summary>\n"); + source.Append( $" public new class SignalName : {symbol.BaseType.FullQualifiedNameIncludeGlobal()}.SignalName {{\n"); @@ -184,6 +188,13 @@ namespace Godot.SourceGenerators foreach (var signalDelegate in godotSignalDelegates) { string signalName = signalDelegate.Name; + + source.Append(" /// <summary>\n") + .Append(" /// Cached name for the '") + .Append(signalName) + .Append("' signal.\n") + .Append(" /// </summary>\n"); + source.Append(" public new static readonly global::Godot.StringName "); source.Append(signalName); source.Append(" = \""); @@ -199,6 +210,14 @@ namespace Godot.SourceGenerators { const string listType = "global::System.Collections.Generic.List<global::Godot.Bridge.MethodInfo>"; + source.Append(" /// <summary>\n") + .Append(" /// Get the signal information for all the signals declared in this class.\n") + .Append(" /// This method is used by Godot to register the available signals in the editor.\n") + .Append(" /// Do not call this method.\n") + .Append(" /// </summary>\n"); + + source.Append(" [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]\n"); + source.Append(" internal new static ") .Append(listType) .Append(" GetGodotSignalList()\n {\n"); @@ -258,6 +277,8 @@ namespace Godot.SourceGenerators if (godotSignalDelegates.Count > 0) { + source.Append(" /// <inheritdoc/>\n"); + source.Append(" [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]\n"); source.Append( " protected override void RaiseGodotClassSignalCallbacks(in godot_string_name signal, "); source.Append("NativeVariantPtrArgs args)\n {\n"); @@ -276,6 +297,8 @@ namespace Godot.SourceGenerators if (godotSignalDelegates.Count > 0) { + source.Append(" /// <inheritdoc/>\n"); + source.Append(" [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]\n"); source.Append( " protected override bool HasGodotClassSignal(in godot_string_name signal)\n {\n"); diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp index 15bb574f4d..cff41a57f3 100644 --- a/modules/mono/editor/bindings_generator.cpp +++ b/modules/mono/editor/bindings_generator.cpp @@ -1616,7 +1616,16 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str // Generate InvokeGodotClassMethod - output << MEMBER_BEGIN "protected internal " << (is_derived_type ? "override" : "virtual") + output << MEMBER_BEGIN "/// <summary>\n" + << INDENT1 "/// Invokes the method with the given name, using the given arguments.\n" + << INDENT1 "/// This method is used by Godot to invoke methods from the engine side.\n" + << INDENT1 "/// Do not call or override this method.\n" + << INDENT1 "/// </summary>\n" + << INDENT1 "/// <param name=\"method\">Name of the method to invoke.</param>\n" + << INDENT1 "/// <param name=\"args\">Arguments to use with the invoked method.</param>\n" + << INDENT1 "/// <param name=\"ret\">Value returned by the invoked method.</param>\n"; + + output << INDENT1 "protected internal " << (is_derived_type ? "override" : "virtual") << " bool " CS_METHOD_INVOKE_GODOT_CLASS_METHOD "(in godot_string_name method, " << "NativeVariantPtrArgs args, out godot_variant ret)\n" << INDENT1 "{\n"; @@ -1696,6 +1705,13 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str // Generate HasGodotClassMethod + output << MEMBER_BEGIN "/// <summary>\n" + << INDENT1 "/// Check if the type contains a method with the given name.\n" + << INDENT1 "/// This method is used by Godot to check if a method exists before invoking it.\n" + << INDENT1 "/// Do not call or override this method.\n" + << INDENT1 "/// </summary>\n" + << INDENT1 "/// <param name=\"method\">Name of the method to check for.</param>\n"; + output << MEMBER_BEGIN "protected internal " << (is_derived_type ? "override" : "virtual") << " bool " CS_METHOD_HAS_GODOT_CLASS_METHOD "(in godot_string_name method)\n" << INDENT1 "{\n"; @@ -1728,6 +1744,13 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str // Generate HasGodotClassSignal + output << MEMBER_BEGIN "/// <summary>\n" + << INDENT1 "/// Check if the type contains a signal with the given name.\n" + << INDENT1 "/// This method is used by Godot to check if a signal exists before raising it.\n" + << INDENT1 "/// Do not call or override this method.\n" + << INDENT1 "/// </summary>\n" + << INDENT1 "/// <param name=\"method\">Name of the method to check for.</param>\n"; + output << MEMBER_BEGIN "protected internal " << (is_derived_type ? "override" : "virtual") << " bool " CS_METHOD_HAS_GODOT_CLASS_SIGNAL "(in godot_string_name signal)\n" << INDENT1 "{\n"; @@ -1758,39 +1781,57 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str //Generate StringName for all class members bool is_inherit = !itype.is_singleton && obj_types.has(itype.base_name); //PropertyName + output << MEMBER_BEGIN "/// <summary>\n" + << INDENT1 "/// Cached StringNames for the properties and fields contained in this class, for fast lookup.\n" + << INDENT1 "/// </summary>\n"; if (is_inherit) { - output << MEMBER_BEGIN "public new class PropertyName : " << obj_types[itype.base_name].proxy_name << ".PropertyName"; + output << INDENT1 "public new class PropertyName : " << obj_types[itype.base_name].proxy_name << ".PropertyName"; } else { - output << MEMBER_BEGIN "public class PropertyName"; + output << INDENT1 "public class PropertyName"; } output << "\n" << INDENT1 "{\n"; for (const PropertyInterface &iprop : itype.properties) { - output << INDENT2 "public static readonly StringName " << iprop.proxy_name << " = \"" << iprop.cname << "\";\n"; + output << INDENT2 "/// <summary>\n" + << INDENT2 "/// Cached name for the '" << iprop.cname << "' property.\n" + << INDENT2 "/// </summary>\n" + << INDENT2 "public static readonly StringName " << iprop.proxy_name << " = \"" << iprop.cname << "\";\n"; } output << INDENT1 "}\n"; //MethodName + output << MEMBER_BEGIN "/// <summary>\n" + << INDENT1 "/// Cached StringNames for the methods contained in this class, for fast lookup.\n" + << INDENT1 "/// </summary>\n"; if (is_inherit) { - output << MEMBER_BEGIN "public new class MethodName : " << obj_types[itype.base_name].proxy_name << ".MethodName"; + output << INDENT1 "public new class MethodName : " << obj_types[itype.base_name].proxy_name << ".MethodName"; } else { - output << MEMBER_BEGIN "public class MethodName"; + output << INDENT1 "public class MethodName"; } output << "\n" << INDENT1 "{\n"; for (const MethodInterface &imethod : itype.methods) { - output << INDENT2 "public static readonly StringName " << imethod.proxy_name << " = \"" << imethod.cname << "\";\n"; + output << INDENT2 "/// <summary>\n" + << INDENT2 "/// Cached name for the '" << imethod.cname << "' method.\n" + << INDENT2 "/// </summary>\n" + << INDENT2 "public static readonly StringName " << imethod.proxy_name << " = \"" << imethod.cname << "\";\n"; } output << INDENT1 "}\n"; //SignalName + output << MEMBER_BEGIN "/// <summary>\n" + << INDENT1 "/// Cached StringNames for the signals contained in this class, for fast lookup.\n" + << INDENT1 "/// </summary>\n"; if (is_inherit) { - output << MEMBER_BEGIN "public new class SignalName : " << obj_types[itype.base_name].proxy_name << ".SignalName"; + output << INDENT1 "public new class SignalName : " << obj_types[itype.base_name].proxy_name << ".SignalName"; } else { - output << MEMBER_BEGIN "public class SignalName"; + output << INDENT1 "public class SignalName"; } output << "\n" << INDENT1 "{\n"; for (const SignalInterface &isignal : itype.signals_) { - output << INDENT2 "public static readonly StringName " << isignal.proxy_name << " = \"" << isignal.cname << "\";\n"; + output << INDENT2 "/// <summary>\n" + << INDENT2 "/// Cached name for the '" << isignal.cname << "' signal.\n" + << INDENT2 "/// </summary>\n" + << INDENT2 "public static readonly StringName " << isignal.proxy_name << " = \"" << isignal.cname << "\";\n"; } output << INDENT1 "}\n"; diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Aabb.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Aabb.cs index af83cc24bf..d25944dceb 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Aabb.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Aabb.cs @@ -564,7 +564,7 @@ namespace Godot /// <summary> /// Returns <see langword="true"/> if this <see cref="Aabb"/> is finite, by calling - /// <see cref="Mathf.IsFinite"/> on each component. + /// <see cref="Mathf.IsFinite(real_t)"/> on each component. /// </summary> /// <returns>Whether this vector is finite or not.</returns> public readonly bool IsFinite() @@ -683,7 +683,7 @@ namespace Godot /// <summary> /// Returns <see langword="true"/> if the AABB is exactly equal - /// to the given object (<see paramref="obj"/>). + /// to the given object (<paramref name="obj"/>). /// Note: Due to floating-point precision errors, consider using /// <see cref="IsEqualApprox"/> instead, which is more reliable. /// </summary> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs index 36f5d8e2ab..d53dd9a9af 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs @@ -1,5 +1,6 @@ using System; using System.Runtime.InteropServices; +using System.ComponentModel; namespace Godot { @@ -595,7 +596,7 @@ namespace Godot /// <summary> /// Returns <see langword="true"/> if this basis is finite, by calling - /// <see cref="Mathf.IsFinite"/> on each component. + /// <see cref="Mathf.IsFinite(real_t)"/> on each component. /// </summary> /// <returns>Whether this vector is finite or not.</returns> public readonly bool IsFinite() @@ -623,21 +624,31 @@ namespace Godot /// </summary> /// <param name="target">The position to look at.</param> /// <param name="up">The relative up direction.</param> + /// <param name="useModelFront"> + /// If true, then the model is oriented in reverse, + /// towards the model front axis (+Z, Vector3.ModelFront), + /// which is more useful for orienting 3D models. + /// </param> /// <returns>The resulting basis matrix.</returns> - public static Basis LookingAt(Vector3 target, Vector3 up) + public static Basis LookingAt(Vector3 target, Vector3? up = null, bool useModelFront = false) { + up ??= Vector3.Up; #if DEBUG if (target.IsZeroApprox()) { throw new ArgumentException("The vector can't be zero.", nameof(target)); } - if (up.IsZeroApprox()) + if (up.Value.IsZeroApprox()) { throw new ArgumentException("The vector can't be zero.", nameof(up)); } #endif - Vector3 column2 = -target.Normalized(); - Vector3 column0 = up.Cross(column2); + Vector3 column2 = target.Normalized(); + if (!useModelFront) + { + column2 = -column2; + } + Vector3 column0 = up.Value.Cross(column2); #if DEBUG if (column0.IsZeroApprox()) { @@ -649,6 +660,13 @@ namespace Godot return new Basis(column0, column1, column2); } + /// <inheritdoc cref="LookingAt(Vector3, Nullable{Vector3}, bool)"/> + [EditorBrowsable(EditorBrowsableState.Never)] + public static Basis LookingAt(Vector3 target, Vector3 up) + { + return LookingAt(target, up, false); + } + /// <summary> /// Returns the orthonormalized version of the basis matrix (useful to /// call occasionally to avoid rounding errors for orthogonal matrices). @@ -1065,7 +1083,7 @@ namespace Godot /// <summary> /// Returns <see langword="true"/> if the <see cref="Basis"/> is - /// exactly equal to the given object (<see paramref="obj"/>). + /// exactly equal to the given object (<paramref name="obj"/>). /// Note: Due to floating-point precision errors, consider using /// <see cref="IsEqualApprox"/> instead, which is more reliable. /// </summary> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotObject.base.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotObject.base.cs index 12e8a638d3..c6337e56ef 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotObject.base.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotObject.base.cs @@ -191,12 +191,30 @@ namespace Godot } // ReSharper disable once VirtualMemberNeverOverridden.Global + /// <summary> + /// Set the value of a property contained in this class. + /// This method is used by Godot to assign property values. + /// Do not call or override this method. + /// </summary> + /// <param name="name">Name of the property to set.</param> + /// <param name="value">Value to set the property to if it was found.</param> + /// <returns><see langword="true"/> if a property with the given name was found.</returns> + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] protected internal virtual bool SetGodotClassPropertyValue(in godot_string_name name, in godot_variant value) { return false; } // ReSharper disable once VirtualMemberNeverOverridden.Global + /// <summary> + /// Get the value of a property contained in this class. + /// This method is used by Godot to retrieve property values. + /// Do not call or override this method. + /// </summary> + /// <param name="name">Name of the property to get.</param> + /// <param name="value">Value of the property if it was found.</param> + /// <returns><see langword="true"/> if a property with the given name was found.</returns> + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] protected internal virtual bool GetGodotClassPropertyValue(in godot_string_name name, out godot_variant value) { value = default; @@ -204,6 +222,14 @@ namespace Godot } // ReSharper disable once VirtualMemberNeverOverridden.Global + /// <summary> + /// Raises the signal with the given name, using the given arguments. + /// This method is used by Godot to raise signals from the engine side.\n" + /// Do not call or override this method. + /// </summary> + /// <param name="signal">Name of the signal to raise.</param> + /// <param name="args">Arguments to use with the raised signal.</param> + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] protected internal virtual void RaiseGodotClassSignalCallbacks(in godot_string_name signal, NativeVariantPtrArgs args) { @@ -233,11 +259,25 @@ namespace Godot return nativeConstructor; } + /// <summary> + /// Saves this instance's state to be restored when reloading assemblies. + /// Do not call or override this method. + /// To add data to be saved and restored, implement <see cref="ISerializationListener"/>. + /// </summary> + /// <param name="info">Object used to save the data.</param> + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] protected internal virtual void SaveGodotObjectData(GodotSerializationInfo info) { } // TODO: Should this be a constructor overload? + /// <summary> + /// Restores this instance's state after reloading assemblies. + /// Do not call or override this method. + /// To add data to be saved and restored, implement <see cref="ISerializationListener"/>. + /// </summary> + /// <param name="info">Object that contains the previously saved data.</param> + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] protected internal virtual void RestoreGodotObjectData(GodotSerializationInfo info) { } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Interfaces/ISerializationListener.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Interfaces/ISerializationListener.cs index 90b4d1b8d3..3288705dab 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Interfaces/ISerializationListener.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Interfaces/ISerializationListener.cs @@ -1,11 +1,21 @@ namespace Godot { /// <summary> - /// An interface that requires methods for before and after serialization. + /// Allows a GodotObject to react to the serialization/deserialization + /// that occurs when Godot reloads assemblies. /// </summary> public interface ISerializationListener { + /// <summary> + /// Executed before serializing this instance's state when reloading assemblies. + /// Clear any data that should not be serialized. + /// </summary> void OnBeforeSerialize(); + + /// <summary> + /// Executed after deserializing this instance's state after reloading assemblies. + /// Restore any state that has been lost. + /// </summary> void OnAfterDeserialize(); } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Plane.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Plane.cs index 55b7a83fc2..3c7455a76c 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Plane.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Plane.cs @@ -204,7 +204,7 @@ namespace Godot /// <summary> /// Returns <see langword="true"/> if this plane is finite, by calling - /// <see cref="Mathf.IsFinite"/> on each component. + /// <see cref="Mathf.IsFinite(real_t)"/> on each component. /// </summary> /// <returns>Whether this vector is finite or not.</returns> public readonly bool IsFinite() diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Projection.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Projection.cs index 84fc73b87a..998a2786a7 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Projection.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Projection.cs @@ -976,7 +976,7 @@ namespace Godot /// <summary> /// Returns <see langword="true"/> if the projection is exactly equal - /// to the given object (<see paramref="obj"/>). + /// to the given object (<paramref name="obj"/>). /// </summary> /// <param name="obj">The object to compare with.</param> /// <returns>Whether or not the vector and the object are equal.</returns> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Quaternion.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Quaternion.cs index 9c2a6fc654..2e282447bd 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Quaternion.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Quaternion.cs @@ -103,7 +103,7 @@ namespace Godot /// /// Note: This method has an abnormally high amount /// of floating-point error, so methods such as - /// <see cref="Mathf.IsZeroApprox"/> will not work reliably. + /// <see cref="Mathf.IsZeroApprox(real_t)"/> will not work reliably. /// </summary> /// <param name="to">The other quaternion.</param> /// <returns>The angle between the quaternions.</returns> @@ -320,7 +320,7 @@ namespace Godot /// <summary> /// Returns <see langword="true"/> if this quaternion is finite, by calling - /// <see cref="Mathf.IsFinite"/> on each component. + /// <see cref="Mathf.IsFinite(real_t)"/> on each component. /// </summary> /// <returns>Whether this vector is finite or not.</returns> public readonly bool IsFinite() diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs index 69444f8035..458802f95d 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs @@ -102,7 +102,7 @@ namespace Godot /// <summary> /// Returns <see langword="true"/> if this <see cref="Rect2"/> is finite, by calling - /// <see cref="Mathf.IsFinite"/> on each component. + /// <see cref="Mathf.IsFinite(real_t)"/> on each component. /// </summary> /// <returns>Whether this vector is finite or not.</returns> public bool IsFinite() diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs index d7392dbda8..618c892681 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs @@ -232,7 +232,7 @@ namespace Godot /// <summary> /// Returns <see langword="true"/> if this transform is finite, by calling - /// <see cref="Mathf.IsFinite"/> on each component. + /// <see cref="Mathf.IsFinite(real_t)"/> on each component. /// </summary> /// <returns>Whether this vector is finite or not.</returns> public readonly bool IsFinite() @@ -586,7 +586,7 @@ namespace Godot /// <summary> /// Returns <see langword="true"/> if the transform is exactly equal - /// to the given object (<see paramref="obj"/>). + /// to the given object (<paramref name="obj"/>). /// Note: Due to floating-point precision errors, consider using /// <see cref="IsEqualApprox"/> instead, which is more reliable. /// </summary> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform3D.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform3D.cs index 1e2aaa299f..b16e6e592e 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform3D.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform3D.cs @@ -1,5 +1,6 @@ using System; using System.Runtime.InteropServices; +using System.ComponentModel; namespace Godot { @@ -155,7 +156,7 @@ namespace Godot /// <summary> /// Returns <see langword="true"/> if this transform is finite, by calling - /// <see cref="Mathf.IsFinite"/> on each component. + /// <see cref="Mathf.IsFinite(real_t)"/> on each component. /// </summary> /// <returns>Whether this vector is finite or not.</returns> public readonly bool IsFinite() @@ -175,14 +176,26 @@ namespace Godot /// </summary> /// <param name="target">The object to look at.</param> /// <param name="up">The relative up direction.</param> + /// <param name="useModelFront"> + /// If true, then the model is oriented in reverse, + /// towards the model front axis (+Z, Vector3.ModelFront), + /// which is more useful for orienting 3D models. + /// </param> /// <returns>The resulting transform.</returns> - public readonly Transform3D LookingAt(Vector3 target, Vector3 up) + public readonly Transform3D LookingAt(Vector3 target, Vector3? up = null, bool useModelFront = false) { Transform3D t = this; - t.SetLookAt(Origin, target, up); + t.SetLookAt(Origin, target, up ?? Vector3.Up, useModelFront); return t; } + /// <inheritdoc cref="LookingAt(Vector3, Nullable{Vector3}, bool)"/> + [EditorBrowsable(EditorBrowsableState.Never)] + public readonly Transform3D LookingAt(Vector3 target, Vector3 up) + { + return LookingAt(target, up, false); + } + /// <summary> /// Returns the transform with the basis orthogonal (90 degrees), /// and normalized axis vectors (scale of 1 or -1). @@ -247,9 +260,9 @@ namespace Godot return new Transform3D(Basis * tmpBasis, Origin); } - private void SetLookAt(Vector3 eye, Vector3 target, Vector3 up) + private void SetLookAt(Vector3 eye, Vector3 target, Vector3 up, bool useModelFront = false) { - Basis = Basis.LookingAt(target - eye, up); + Basis = Basis.LookingAt(target - eye, up, useModelFront); Origin = eye; } @@ -600,7 +613,7 @@ namespace Godot /// <summary> /// Returns <see langword="true"/> if the transform is exactly equal - /// to the given object (<see paramref="obj"/>). + /// to the given object (<paramref name="obj"/>). /// Note: Due to floating-point precision errors, consider using /// <see cref="IsEqualApprox"/> instead, which is more reliable. /// </summary> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs index 0bf8f25f06..642ef231f3 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs @@ -164,7 +164,7 @@ namespace Godot /// <summary> /// Returns a new vector with all components rounded up (towards positive infinity). /// </summary> - /// <returns>A vector with <see cref="Mathf.Ceil"/> called on each component.</returns> + /// <returns>A vector with <see cref="Mathf.Ceil(real_t)"/> called on each component.</returns> public readonly Vector2 Ceil() { return new Vector2(Mathf.Ceil(X), Mathf.Ceil(Y)); @@ -318,7 +318,7 @@ namespace Godot /// <summary> /// Returns a new vector with all components rounded down (towards negative infinity). /// </summary> - /// <returns>A vector with <see cref="Mathf.Floor"/> called on each component.</returns> + /// <returns>A vector with <see cref="Mathf.Floor(real_t)"/> called on each component.</returns> public readonly Vector2 Floor() { return new Vector2(Mathf.Floor(X), Mathf.Floor(Y)); @@ -335,7 +335,7 @@ namespace Godot /// <summary> /// Returns <see langword="true"/> if this vector is finite, by calling - /// <see cref="Mathf.IsFinite"/> on each component. + /// <see cref="Mathf.IsFinite(real_t)"/> on each component. /// </summary> /// <returns>Whether this vector is finite or not.</returns> public readonly bool IsFinite() @@ -948,7 +948,7 @@ namespace Godot /// <summary> /// Returns <see langword="true"/> if the vector is exactly equal - /// to the given object (<see paramref="obj"/>). + /// to the given object (<paramref name="obj"/>). /// Note: Due to floating-point precision errors, consider using /// <see cref="IsEqualApprox"/> instead, which is more reliable. /// </summary> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2I.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2I.cs index 0dac8205b6..231e791904 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2I.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2I.cs @@ -517,7 +517,7 @@ namespace Godot /// <summary> /// Returns <see langword="true"/> if the vector is equal - /// to the given object (<see paramref="obj"/>). + /// to the given object (<paramref name="obj"/>). /// </summary> /// <param name="obj">The object to compare with.</param> /// <returns>Whether or not the vector and the object are equal.</returns> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs index c773c0fda6..7d548f1d10 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs @@ -150,7 +150,7 @@ namespace Godot /// <summary> /// Returns a new vector with all components rounded up (towards positive infinity). /// </summary> - /// <returns>A vector with <see cref="Mathf.Ceil"/> called on each component.</returns> + /// <returns>A vector with <see cref="Mathf.Ceil(real_t)"/> called on each component.</returns> public readonly Vector3 Ceil() { return new Vector3(Mathf.Ceil(X), Mathf.Ceil(Y), Mathf.Ceil(Z)); @@ -315,7 +315,7 @@ namespace Godot /// <summary> /// Returns a new vector with all components rounded down (towards negative infinity). /// </summary> - /// <returns>A vector with <see cref="Mathf.Floor"/> called on each component.</returns> + /// <returns>A vector with <see cref="Mathf.Floor(real_t)"/> called on each component.</returns> public readonly Vector3 Floor() { return new Vector3(Mathf.Floor(X), Mathf.Floor(Y), Mathf.Floor(Z)); @@ -332,7 +332,7 @@ namespace Godot /// <summary> /// Returns <see langword="true"/> if this vector is finite, by calling - /// <see cref="Mathf.IsFinite"/> on each component. + /// <see cref="Mathf.IsFinite(real_t)"/> on each component. /// </summary> /// <returns>Whether this vector is finite or not.</returns> public readonly bool IsFinite() @@ -660,6 +660,13 @@ namespace Godot private static readonly Vector3 _forward = new Vector3(0, 0, -1); private static readonly Vector3 _back = new Vector3(0, 0, 1); + private static readonly Vector3 _modelLeft = new Vector3(1, 0, 0); + private static readonly Vector3 _modelRight = new Vector3(-1, 0, 0); + private static readonly Vector3 _modelTop = new Vector3(0, 1, 0); + private static readonly Vector3 _modelBottom = new Vector3(0, -1, 0); + private static readonly Vector3 _modelFront = new Vector3(0, 0, 1); + private static readonly Vector3 _modelRear = new Vector3(0, 0, -1); + /// <summary> /// Zero vector, a vector with all components set to <c>0</c>. /// </summary> @@ -712,6 +719,31 @@ namespace Godot public static Vector3 Back { get { return _back; } } /// <summary> + /// Unit vector pointing towards the left side of imported 3D assets. + /// </summary> + public static Vector3 ModelLeft { get { return _modelLeft; } } + /// <summary> + /// Unit vector pointing towards the right side of imported 3D assets. + /// </summary> + public static Vector3 ModelRight { get { return _modelRight; } } + /// <summary> + /// Unit vector pointing towards the top side (up) of imported 3D assets. + /// </summary> + public static Vector3 ModelTop { get { return _modelTop; } } + /// <summary> + /// Unit vector pointing towards the bottom side (down) of imported 3D assets. + /// </summary> + public static Vector3 ModelBottom { get { return _modelBottom; } } + /// <summary> + /// Unit vector pointing towards the front side (facing forward) of imported 3D assets. + /// </summary> + public static Vector3 ModelFront { get { return _modelFront; } } + /// <summary> + /// Unit vector pointing towards the rear side (back) of imported 3D assets. + /// </summary> + public static Vector3 ModelRear { get { return _modelRear; } } + + /// <summary> /// Constructs a new <see cref="Vector3"/> with the given components. /// </summary> /// <param name="x">The vector's X component.</param> @@ -1018,7 +1050,7 @@ namespace Godot /// <summary> /// Returns <see langword="true"/> if the vector is exactly equal - /// to the given object (<see paramref="obj"/>). + /// to the given object (<paramref name="obj"/>). /// Note: Due to floating-point precision errors, consider using /// <see cref="IsEqualApprox"/> instead, which is more reliable. /// </summary> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3I.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3I.cs index a2927533f8..8543052f56 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3I.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3I.cs @@ -572,7 +572,7 @@ namespace Godot /// <summary> /// Returns <see langword="true"/> if the vector is equal - /// to the given object (<see paramref="obj"/>). + /// to the given object (<paramref name="obj"/>). /// </summary> /// <param name="obj">The object to compare with.</param> /// <returns>Whether or not the vector and the object are equal.</returns> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4.cs index 1fd39632b0..10a0b14162 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4.cs @@ -147,7 +147,7 @@ namespace Godot /// <summary> /// Returns a new vector with all components rounded up (towards positive infinity). /// </summary> - /// <returns>A vector with <see cref="Mathf.Ceil"/> called on each component.</returns> + /// <returns>A vector with <see cref="Mathf.Ceil(real_t)"/> called on each component.</returns> public readonly Vector4 Ceil() { return new Vector4(Mathf.Ceil(X), Mathf.Ceil(Y), Mathf.Ceil(Z), Mathf.Ceil(W)); @@ -264,7 +264,7 @@ namespace Godot /// <summary> /// Returns a new vector with all components rounded down (towards negative infinity). /// </summary> - /// <returns>A vector with <see cref="Mathf.Floor"/> called on each component.</returns> + /// <returns>A vector with <see cref="Mathf.Floor(real_t)"/> called on each component.</returns> public readonly Vector4 Floor() { return new Vector4(Mathf.Floor(X), Mathf.Floor(Y), Mathf.Floor(Z), Mathf.Floor(W)); @@ -281,7 +281,7 @@ namespace Godot /// <summary> /// Returns <see langword="true"/> if this vector is finite, by calling - /// <see cref="Mathf.IsFinite"/> on each component. + /// <see cref="Mathf.IsFinite(real_t)"/> on each component. /// </summary> /// <returns>Whether this vector is finite or not.</returns> public readonly bool IsFinite() @@ -832,7 +832,7 @@ namespace Godot /// <summary> /// Returns <see langword="true"/> if the vector is exactly equal - /// to the given object (<see paramref="obj"/>). + /// to the given object (<paramref name="obj"/>). /// Note: Due to floating-point precision errors, consider using /// <see cref="IsEqualApprox"/> instead, which is more reliable. /// </summary> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4I.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4I.cs index bb552b939d..f813903177 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4I.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4I.cs @@ -593,7 +593,7 @@ namespace Godot /// <summary> /// Returns <see langword="true"/> if the vector is equal - /// to the given object (<see paramref="obj"/>). + /// to the given object (<paramref name="obj"/>). /// </summary> /// <param name="obj">The object to compare with.</param> /// <returns>Whether or not the vector and the object are equal.</returns> diff --git a/modules/multiplayer/multiplayer_synchronizer.cpp b/modules/multiplayer/multiplayer_synchronizer.cpp index e5207fdae2..9c2d281f72 100644 --- a/modules/multiplayer/multiplayer_synchronizer.cpp +++ b/modules/multiplayer/multiplayer_synchronizer.cpp @@ -157,7 +157,7 @@ Error MultiplayerSynchronizer::get_state(const List<NodePath> &p_properties, Obj bool valid = false; const Object *obj = _get_prop_target(p_obj, prop); ERR_FAIL_COND_V(!obj, FAILED); - r_variant.write[i] = obj->get(prop.get_concatenated_subnames(), &valid); + r_variant.write[i] = obj->get_indexed(prop.get_subnames(), &valid); r_variant_ptrs.write[i] = &r_variant[i]; ERR_FAIL_COND_V_MSG(!valid, ERR_INVALID_DATA, vformat("Property '%s' not found.", prop)); i++; @@ -171,7 +171,7 @@ Error MultiplayerSynchronizer::set_state(const List<NodePath> &p_properties, Obj for (const NodePath &prop : p_properties) { Object *obj = _get_prop_target(p_obj, prop); ERR_FAIL_COND_V(!obj, FAILED); - obj->set(prop.get_concatenated_subnames(), p_state[i]); + obj->set_indexed(prop.get_subnames(), p_state[i]); i += 1; } return OK; diff --git a/modules/navigation/godot_navigation_server.cpp b/modules/navigation/godot_navigation_server.cpp index b73c5ca3e2..29fa73aa85 100644 --- a/modules/navigation/godot_navigation_server.cpp +++ b/modules/navigation/godot_navigation_server.cpp @@ -340,6 +340,20 @@ RID GodotNavigationServer::region_create() { return rid; } +COMMAND_2(region_set_enabled, RID, p_region, bool, p_enabled) { + NavRegion *region = region_owner.get_or_null(p_region); + ERR_FAIL_COND(region == nullptr); + + region->set_enabled(p_enabled); +} + +bool GodotNavigationServer::region_get_enabled(RID p_region) const { + const NavRegion *region = region_owner.get_or_null(p_region); + ERR_FAIL_COND_V(region == nullptr, false); + + return region->get_enabled(); +} + COMMAND_2(region_set_use_edge_connections, RID, p_region, bool, p_enabled) { NavRegion *region = region_owner.get_or_null(p_region); ERR_FAIL_COND(region == nullptr); @@ -512,6 +526,20 @@ RID GodotNavigationServer::link_get_map(const RID p_link) const { return RID(); } +COMMAND_2(link_set_enabled, RID, p_link, bool, p_enabled) { + NavLink *link = link_owner.get_or_null(p_link); + ERR_FAIL_COND(link == nullptr); + + link->set_enabled(p_enabled); +} + +bool GodotNavigationServer::link_get_enabled(RID p_link) const { + const NavLink *link = link_owner.get_or_null(p_link); + ERR_FAIL_COND_V(link == nullptr, false); + + return link->get_enabled(); +} + COMMAND_2(link_set_bidirectional, RID, p_link, bool, p_bidirectional) { NavLink *link = link_owner.get_or_null(p_link); ERR_FAIL_COND(link == nullptr); diff --git a/modules/navigation/godot_navigation_server.h b/modules/navigation/godot_navigation_server.h index 95aa88194e..0b3789102c 100644 --- a/modules/navigation/godot_navigation_server.h +++ b/modules/navigation/godot_navigation_server.h @@ -135,6 +135,9 @@ public: virtual RID region_create() override; + COMMAND_2(region_set_enabled, RID, p_region, bool, p_enabled); + virtual bool region_get_enabled(RID p_region) const override; + COMMAND_2(region_set_use_edge_connections, RID, p_region, bool, p_enabled); virtual bool region_get_use_edge_connections(RID p_region) const override; @@ -164,6 +167,8 @@ public: virtual RID link_create() override; COMMAND_2(link_set_map, RID, p_link, RID, p_map); virtual RID link_get_map(RID p_link) const override; + COMMAND_2(link_set_enabled, RID, p_link, bool, p_enabled); + virtual bool link_get_enabled(RID p_link) const override; COMMAND_2(link_set_bidirectional, RID, p_link, bool, p_bidirectional); virtual bool link_is_bidirectional(RID p_link) const override; COMMAND_2(link_set_navigation_layers, RID, p_link, uint32_t, p_navigation_layers); diff --git a/modules/navigation/nav_link.cpp b/modules/navigation/nav_link.cpp index d712987a46..c693cc91c8 100644 --- a/modules/navigation/nav_link.cpp +++ b/modules/navigation/nav_link.cpp @@ -49,6 +49,16 @@ void NavLink::set_map(NavMap *p_map) { } } +void NavLink::set_enabled(bool p_enabled) { + if (enabled == p_enabled) { + return; + } + enabled = p_enabled; + + // TODO: This should not require a full rebuild as the link has not really changed. + link_dirty = true; +}; + void NavLink::set_bidirectional(bool p_bidirectional) { if (bidirectional == p_bidirectional) { return; diff --git a/modules/navigation/nav_link.h b/modules/navigation/nav_link.h index 0b8ad4db69..a7609831db 100644 --- a/modules/navigation/nav_link.h +++ b/modules/navigation/nav_link.h @@ -39,6 +39,7 @@ class NavLink : public NavBase { bool bidirectional = true; Vector3 start_position; Vector3 end_position; + bool enabled = true; bool link_dirty = true; @@ -52,6 +53,9 @@ public: return map; } + void set_enabled(bool p_enabled); + bool get_enabled() const { return enabled; } + void set_bidirectional(bool p_bidirectional); bool is_bidirectional() const { return bidirectional; diff --git a/modules/navigation/nav_map.cpp b/modules/navigation/nav_map.cpp index 3a1d412618..745c227fe5 100644 --- a/modules/navigation/nav_map.cpp +++ b/modules/navigation/nav_map.cpp @@ -804,6 +804,9 @@ void NavMap::sync() { // Resize the polygon count. int count = 0; for (const NavRegion *region : regions) { + if (!region->get_enabled()) { + continue; + } count += region->get_polygons().size(); } polygons.resize(count); @@ -811,6 +814,9 @@ void NavMap::sync() { // Copy all region polygons in the map. count = 0; for (const NavRegion *region : regions) { + if (!region->get_enabled()) { + continue; + } const LocalVector<gd::Polygon> &polygons_source = region->get_polygons(); for (uint32_t n = 0; n < polygons_source.size(); n++) { polygons[count + n] = polygons_source[n]; diff --git a/modules/navigation/nav_region.cpp b/modules/navigation/nav_region.cpp index 867cf5d8fc..4e7964ed76 100644 --- a/modules/navigation/nav_region.cpp +++ b/modules/navigation/nav_region.cpp @@ -51,6 +51,16 @@ void NavRegion::set_map(NavMap *p_map) { } } +void NavRegion::set_enabled(bool p_enabled) { + if (enabled == p_enabled) { + return; + } + enabled = p_enabled; + + // TODO: This should not require a full rebuild as the region has not really changed. + polygons_dirty = true; +}; + void NavRegion::set_use_edge_connections(bool p_enabled) { if (use_edge_connections != p_enabled) { use_edge_connections = p_enabled; @@ -115,11 +125,11 @@ void NavRegion::update_polygons() { #ifdef DEBUG_ENABLED if (!Math::is_equal_approx(double(map->get_cell_size()), double(mesh->get_cell_size()))) { - ERR_PRINT_ONCE("Navigation map synchronization error. Attempted to update a navigation region with a navigation mesh that uses a different `cell_size` than the `cell_size` set on the navigation map."); + ERR_PRINT_ONCE(vformat("Navigation map synchronization error. Attempted to update a navigation region with a navigation mesh that uses a `cell_size` of %s while assigned to a navigation map set to a `cell_size` of %s. The cell size for navigation maps can be changed by using the NavigationServer map_set_cell_size() function. The cell size for default navigation maps can also be changed in the ProjectSettings.", double(map->get_cell_size()), double(mesh->get_cell_size()))); } if (!Math::is_equal_approx(double(map->get_cell_height()), double(mesh->get_cell_height()))) { - ERR_PRINT_ONCE("Navigation map synchronization error. Attempted to update a navigation region with a navigation mesh that uses a different `cell_height` than the `cell_height` set on the navigation map."); + ERR_PRINT_ONCE(vformat("Navigation map synchronization error. Attempted to update a navigation region with a navigation mesh that uses a `cell_height` of %s while assigned to a navigation map set to a `cell_height` of %s. The cell height for navigation maps can be changed by using the NavigationServer map_set_cell_height() function. The cell height for default navigation maps can also be changed in the ProjectSettings.", double(map->get_cell_height()), double(mesh->get_cell_height()))); } if (map && Math::rad_to_deg(map->get_up().angle_to(transform.basis.get_column(1))) >= 90.0f) { diff --git a/modules/navigation/nav_region.h b/modules/navigation/nav_region.h index 0c3c1b56b6..6a8ebe5336 100644 --- a/modules/navigation/nav_region.h +++ b/modules/navigation/nav_region.h @@ -41,6 +41,7 @@ class NavRegion : public NavBase { Transform3D transform; Ref<NavigationMesh> mesh; Vector<gd::Edge::Connection> connections; + bool enabled = true; bool use_edge_connections = true; @@ -58,6 +59,9 @@ public: polygons_dirty = true; } + void set_enabled(bool p_enabled); + bool get_enabled() const { return enabled; } + void set_map(NavMap *p_map); NavMap *get_map() const { return map; diff --git a/modules/noise/noise_texture_2d.cpp b/modules/noise/noise_texture_2d.cpp index a7176e0816..1b0c5cb9e3 100644 --- a/modules/noise/noise_texture_2d.cpp +++ b/modules/noise/noise_texture_2d.cpp @@ -32,8 +32,6 @@ #include "noise.h" -#include "core/core_string_names.h" - NoiseTexture2D::NoiseTexture2D() { noise = Ref<Noise>(); @@ -223,11 +221,11 @@ void NoiseTexture2D::set_noise(Ref<Noise> p_noise) { return; } if (noise.is_valid()) { - noise->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(this, &NoiseTexture2D::_queue_update)); + noise->disconnect_changed(callable_mp(this, &NoiseTexture2D::_queue_update)); } noise = p_noise; if (noise.is_valid()) { - noise->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &NoiseTexture2D::_queue_update)); + noise->connect_changed(callable_mp(this, &NoiseTexture2D::_queue_update)); } _queue_update(); } @@ -347,11 +345,11 @@ void NoiseTexture2D::set_color_ramp(const Ref<Gradient> &p_gradient) { return; } if (color_ramp.is_valid()) { - color_ramp->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(this, &NoiseTexture2D::_queue_update)); + color_ramp->disconnect_changed(callable_mp(this, &NoiseTexture2D::_queue_update)); } color_ramp = p_gradient; if (color_ramp.is_valid()) { - color_ramp->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &NoiseTexture2D::_queue_update)); + color_ramp->connect_changed(callable_mp(this, &NoiseTexture2D::_queue_update)); } _queue_update(); } diff --git a/modules/noise/noise_texture_3d.cpp b/modules/noise/noise_texture_3d.cpp index f6c67b0f2d..ed242e7faa 100644 --- a/modules/noise/noise_texture_3d.cpp +++ b/modules/noise/noise_texture_3d.cpp @@ -32,8 +32,6 @@ #include "noise.h" -#include "core/core_string_names.h" - NoiseTexture3D::NoiseTexture3D() { noise = Ref<Noise>(); @@ -214,11 +212,11 @@ void NoiseTexture3D::set_noise(Ref<Noise> p_noise) { return; } if (noise.is_valid()) { - noise->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(this, &NoiseTexture3D::_queue_update)); + noise->disconnect_changed(callable_mp(this, &NoiseTexture3D::_queue_update)); } noise = p_noise; if (noise.is_valid()) { - noise->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &NoiseTexture3D::_queue_update)); + noise->connect_changed(callable_mp(this, &NoiseTexture3D::_queue_update)); } _queue_update(); } @@ -297,11 +295,11 @@ void NoiseTexture3D::set_color_ramp(const Ref<Gradient> &p_gradient) { return; } if (color_ramp.is_valid()) { - color_ramp->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(this, &NoiseTexture3D::_queue_update)); + color_ramp->disconnect_changed(callable_mp(this, &NoiseTexture3D::_queue_update)); } color_ramp = p_gradient; if (color_ramp.is_valid()) { - color_ramp->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &NoiseTexture3D::_queue_update)); + color_ramp->connect_changed(callable_mp(this, &NoiseTexture3D::_queue_update)); } _queue_update(); } diff --git a/modules/noise/tests/test_noise_texture_2d.h b/modules/noise/tests/test_noise_texture_2d.h index e2ec39ef48..938e8fd6ab 100644 --- a/modules/noise/tests/test_noise_texture_2d.h +++ b/modules/noise/tests/test_noise_texture_2d.h @@ -210,7 +210,7 @@ TEST_CASE("[NoiseTexture2D][SceneTree] Generating a basic noise texture with mip noise_texture->set_generate_mipmaps(true); Ref<NoiseTextureTester> tester = memnew(NoiseTextureTester(noise_texture.ptr())); - noise_texture->connect("changed", callable_mp(tester.ptr(), &NoiseTextureTester::check_mip_and_color_ramp)); + noise_texture->connect_changed(callable_mp(tester.ptr(), &NoiseTextureTester::check_mip_and_color_ramp)); MessageQueue::get_singleton()->flush(); } @@ -227,7 +227,7 @@ TEST_CASE("[NoiseTexture2D][SceneTree] Generating a normal map without mipmaps") noise_texture->set_generate_mipmaps(false); Ref<NoiseTextureTester> tester = memnew(NoiseTextureTester(noise_texture.ptr())); - noise_texture->connect("changed", callable_mp(tester.ptr(), &NoiseTextureTester::check_normal_map)); + noise_texture->connect_changed(callable_mp(tester.ptr(), &NoiseTextureTester::check_normal_map)); MessageQueue::get_singleton()->flush(); } @@ -245,7 +245,7 @@ TEST_CASE("[NoiseTexture2D][SceneTree] Generating a seamless noise texture") { SUBCASE("Grayscale(L8) 16x16, with seamless blend skirt of 0.05") { noise_texture->set_seamless_blend_skirt(0.05); - noise_texture->connect("changed", callable_mp(tester.ptr(), &NoiseTextureTester::check_seamless_texture_grayscale)); + noise_texture->connect_changed(callable_mp(tester.ptr(), &NoiseTextureTester::check_seamless_texture_grayscale)); MessageQueue::get_singleton()->flush(); } @@ -257,7 +257,7 @@ TEST_CASE("[NoiseTexture2D][SceneTree] Generating a seamless noise texture") { gradient->set_points(points); noise_texture->set_color_ramp(gradient); noise_texture->set_seamless_blend_skirt(1.0); - noise_texture->connect("changed", callable_mp(tester.ptr(), &NoiseTextureTester::check_seamless_texture_rgba)); + noise_texture->connect_changed(callable_mp(tester.ptr(), &NoiseTextureTester::check_seamless_texture_rgba)); MessageQueue::get_singleton()->flush(); } } diff --git a/modules/noise/tests/test_noise_texture_3d.h b/modules/noise/tests/test_noise_texture_3d.h index a612f2920a..b708eac43b 100644 --- a/modules/noise/tests/test_noise_texture_3d.h +++ b/modules/noise/tests/test_noise_texture_3d.h @@ -194,7 +194,7 @@ TEST_CASE("[NoiseTexture3D][SceneTree] Generating a basic noise texture with mip noise_texture->set_depth(16); Ref<NoiseTexture3DTester> tester = memnew(NoiseTexture3DTester(noise_texture.ptr())); - noise_texture->connect("changed", callable_mp(tester.ptr(), &NoiseTexture3DTester::check_mip_and_color_ramp)); + noise_texture->connect_changed(callable_mp(tester.ptr(), &NoiseTexture3DTester::check_mip_and_color_ramp)); MessageQueue::get_singleton()->flush(); } @@ -213,7 +213,7 @@ TEST_CASE("[NoiseTexture3D][SceneTree] Generating a seamless noise texture") { SUBCASE("Grayscale(L8) 16x16x16, with seamless blend skirt of 0.05") { noise_texture->set_seamless_blend_skirt(0.05); - noise_texture->connect("changed", callable_mp(tester.ptr(), &NoiseTexture3DTester::check_seamless_texture_grayscale)); + noise_texture->connect_changed(callable_mp(tester.ptr(), &NoiseTexture3DTester::check_seamless_texture_grayscale)); MessageQueue::get_singleton()->flush(); } @@ -225,7 +225,7 @@ TEST_CASE("[NoiseTexture3D][SceneTree] Generating a seamless noise texture") { gradient->set_points(points); noise_texture->set_color_ramp(gradient); noise_texture->set_seamless_blend_skirt(1.0); - noise_texture->connect("changed", callable_mp(tester.ptr(), &NoiseTexture3DTester::check_seamless_texture_rgba)); + noise_texture->connect_changed(callable_mp(tester.ptr(), &NoiseTexture3DTester::check_seamless_texture_rgba)); MessageQueue::get_singleton()->flush(); } } diff --git a/modules/openxr/SCsub b/modules/openxr/SCsub index d5abbe4c72..fbff4c7f8f 100644 --- a/modules/openxr/SCsub +++ b/modules/openxr/SCsub @@ -28,6 +28,11 @@ elif env["platform"] == "linuxbsd": env_openxr.AppendUnique(CPPDEFINES=["HAVE_SECURE_GETENV"]) elif env["platform"] == "windows": env_openxr.AppendUnique(CPPDEFINES=["XR_OS_WINDOWS", "NOMINMAX", "XR_USE_PLATFORM_WIN32"]) +elif env["platform"] == "macos": + env_openxr.AppendUnique(CPPDEFINES=["XR_OS_APPLE"]) + + # There does not seem to be a XR_USE_PLATFORM_XYZ for Apple + # may need to check and set: # - XR_USE_TIMESPEC @@ -95,7 +100,7 @@ if env["platform"] == "android": env_openxr.add_source_files(module_obj, "extensions/openxr_android_extension.cpp") if env["vulkan"]: env_openxr.add_source_files(module_obj, "extensions/openxr_vulkan_extension.cpp") -if env["opengl3"]: +if env["opengl3"] and env["platform"] != "macos": env_openxr.add_source_files(module_obj, "extensions/openxr_opengl_extension.cpp") env_openxr.add_source_files(module_obj, "extensions/openxr_palm_pose_extension.cpp") diff --git a/modules/openxr/config.py b/modules/openxr/config.py index e503f12739..8ed06a1606 100644 --- a/modules/openxr/config.py +++ b/modules/openxr/config.py @@ -1,5 +1,5 @@ def can_build(env, platform): - if platform in ("linuxbsd", "windows", "android"): + if platform in ("linuxbsd", "windows", "android", "macos"): return env["openxr"] and not env["disable_3d"] else: # not supported on these platforms diff --git a/modules/openxr/extensions/openxr_hand_tracking_extension.cpp b/modules/openxr/extensions/openxr_hand_tracking_extension.cpp index 6fffa1ed07..65559afed0 100644 --- a/modules/openxr/extensions/openxr_hand_tracking_extension.cpp +++ b/modules/openxr/extensions/openxr_hand_tracking_extension.cpp @@ -134,6 +134,10 @@ void OpenXRHandTrackingExtension::on_process() { // process our hands const XrTime time = OpenXRAPI::get_singleton()->get_next_frame_time(); // This data will be used for the next frame we render + if (time == 0) { + // we don't have timing info yet, or we're skipping a frame... + return; + } XrResult result; diff --git a/modules/openxr/openxr_api.cpp b/modules/openxr/openxr_api.cpp index 4ab280f3c3..c5efe609c2 100644 --- a/modules/openxr/openxr_api.cpp +++ b/modules/openxr/openxr_api.cpp @@ -47,7 +47,7 @@ #ifdef VULKAN_ENABLED #define XR_USE_GRAPHICS_API_VULKAN #endif -#ifdef GLES3_ENABLED +#if defined(GLES3_ENABLED) && !defined(MACOS_ENABLED) #ifdef ANDROID_ENABLED #define XR_USE_GRAPHICS_API_OPENGL_ES #include <EGL/egl.h> @@ -72,7 +72,7 @@ #include "extensions/openxr_vulkan_extension.h" #endif -#ifdef GLES3_ENABLED +#if defined(GLES3_ENABLED) && !defined(MACOS_ENABLED) #include "extensions/openxr_opengl_extension.h" #endif @@ -1306,7 +1306,7 @@ bool OpenXRAPI::initialize(const String &p_rendering_driver) { ERR_FAIL_V(false); #endif } else if (p_rendering_driver == "opengl3") { -#ifdef GLES3_ENABLED +#if defined(GLES3_ENABLED) && !defined(MACOS_ENABLED) graphics_extension = memnew(OpenXROpenGLExtension); register_extension_wrapper(graphics_extension); #else diff --git a/modules/raycast/raycast_occlusion_cull.cpp b/modules/raycast/raycast_occlusion_cull.cpp index 69fbf87483..eee0de967e 100644 --- a/modules/raycast/raycast_occlusion_cull.cpp +++ b/modules/raycast/raycast_occlusion_cull.cpp @@ -355,41 +355,14 @@ void RaycastOcclusionCull::Scenario::_update_dirty_instance(int p_idx, RID *p_in // Embree requires the last element to be readable by a 16-byte SSE load instruction, so we add padding to be safe. occ_inst->xformed_vertices.resize(vertices_size + 1); - const Vector3 *read_ptr = occ->vertices.ptr(); - Vector3 *write_ptr = occ_inst->xformed_vertices.ptr(); - - if (vertices_size > 1024) { - TransformThreadData td; - td.xform = occ_inst->xform; - td.read = read_ptr; - td.write = write_ptr; - td.vertex_count = vertices_size; - td.thread_count = WorkerThreadPool::get_singleton()->get_thread_count(); - WorkerThreadPool::GroupID group_task = WorkerThreadPool::get_singleton()->add_template_group_task(this, &Scenario::_transform_vertices_thread, &td, td.thread_count, -1, true, SNAME("RaycastOcclusionCull")); - WorkerThreadPool::get_singleton()->wait_for_group_task_completion(group_task); - - } else { - _transform_vertices_range(read_ptr, write_ptr, occ_inst->xform, 0, vertices_size); - } + for_range(0, vertices_size, vertices_size > 1024, SNAME("RaycastOcclusionCull"), [&](const int i) { + occ_inst->xformed_vertices[i] = occ_inst->xform.xform(occ->vertices[i]); + }); occ_inst->indices.resize(occ->indices.size()); memcpy(occ_inst->indices.ptr(), occ->indices.ptr(), occ->indices.size() * sizeof(int32_t)); } -void RaycastOcclusionCull::Scenario::_transform_vertices_thread(uint32_t p_thread, TransformThreadData *p_data) { - uint32_t vertex_total = p_data->vertex_count; - uint32_t total_threads = p_data->thread_count; - uint32_t from = p_thread * vertex_total / total_threads; - uint32_t to = (p_thread + 1 == total_threads) ? vertex_total : ((p_thread + 1) * vertex_total / total_threads); - _transform_vertices_range(p_data->read, p_data->write, p_data->xform, from, to); -} - -void RaycastOcclusionCull::Scenario::_transform_vertices_range(const Vector3 *p_read, Vector3 *p_write, const Transform3D &p_xform, int p_from, int p_to) { - for (int i = p_from; i < p_to; i++) { - p_write[i] = p_xform.xform(p_read[i]); - } -} - void RaycastOcclusionCull::Scenario::_commit_scene(void *p_ud) { Scenario *scenario = (Scenario *)p_ud; int commit_idx = 1 - (scenario->current_scene_idx); diff --git a/modules/raycast/raycast_occlusion_cull.h b/modules/raycast/raycast_occlusion_cull.h index c4e733b664..7a5346878b 100644 --- a/modules/raycast/raycast_occlusion_cull.h +++ b/modules/raycast/raycast_occlusion_cull.h @@ -121,14 +121,6 @@ private: const uint32_t *masks; }; - struct TransformThreadData { - uint32_t thread_count; - uint32_t vertex_count; - Transform3D xform; - const Vector3 *read; - Vector3 *write = nullptr; - }; - Thread *commit_thread = nullptr; bool commit_done = true; bool dirty = false; @@ -144,8 +136,6 @@ private: void _update_dirty_instance_thread(int p_idx, RID *p_instances); void _update_dirty_instance(int p_idx, RID *p_instances); - void _transform_vertices_thread(uint32_t p_thread, TransformThreadData *p_data); - void _transform_vertices_range(const Vector3 *p_read, Vector3 *p_write, const Transform3D &p_xform, int p_from, int p_to); static void _commit_scene(void *p_ud); bool update(); diff --git a/modules/text_server_adv/text_server_adv.cpp b/modules/text_server_adv/text_server_adv.cpp index b8010e6692..13d8a2c17a 100644 --- a/modules/text_server_adv/text_server_adv.cpp +++ b/modules/text_server_adv/text_server_adv.cpp @@ -52,6 +52,7 @@ using namespace godot; #include "core/object/worker_thread_pool.h" #include "core/string/print_string.h" #include "core/string/translation.h" +#include "scene/resources/image_texture.h" #include "modules/modules_enabled.gen.h" // For freetype, msdfgen, svg. diff --git a/modules/text_server_adv/text_server_adv.h b/modules/text_server_adv/text_server_adv.h index aba727edaa..44700e045b 100644 --- a/modules/text_server_adv/text_server_adv.h +++ b/modules/text_server_adv/text_server_adv.h @@ -85,7 +85,7 @@ using namespace godot; #include "core/object/worker_thread_pool.h" #include "core/templates/hash_map.h" #include "core/templates/rid_owner.h" -#include "scene/resources/texture.h" +#include "scene/resources/image_texture.h" #include "servers/text/text_server_extension.h" #include "modules/modules_enabled.gen.h" // For freetype, msdfgen, svg. diff --git a/modules/text_server_fb/text_server_fb.h b/modules/text_server_fb/text_server_fb.h index d81b50779e..54311caaf9 100644 --- a/modules/text_server_fb/text_server_fb.h +++ b/modules/text_server_fb/text_server_fb.h @@ -83,7 +83,7 @@ using namespace godot; #include "core/object/worker_thread_pool.h" #include "core/templates/hash_map.h" #include "core/templates/rid_owner.h" -#include "scene/resources/texture.h" +#include "scene/resources/image_texture.h" #include "servers/text/text_server_extension.h" #include "modules/modules_enabled.gen.h" // For freetype, msdfgen, svg. diff --git a/modules/theora/video_stream_theora.cpp b/modules/theora/video_stream_theora.cpp index 6c961813b4..d964fd7627 100644 --- a/modules/theora/video_stream_theora.cpp +++ b/modules/theora/video_stream_theora.cpp @@ -32,6 +32,7 @@ #include "core/config/project_settings.h" #include "core/os/os.h" +#include "scene/resources/image_texture.h" #ifdef _MSC_VER #pragma warning(push) diff --git a/modules/theora/video_stream_theora.h b/modules/theora/video_stream_theora.h index 32adc28a88..21d4caef45 100644 --- a/modules/theora/video_stream_theora.h +++ b/modules/theora/video_stream_theora.h @@ -43,6 +43,8 @@ #include <theora/theoradec.h> #include <vorbis/codec.h> +class ImageTexture; + //#define THEORA_USE_THREAD_STREAMING class VideoStreamPlaybackTheora : public VideoStreamPlayback { diff --git a/modules/vorbis/audio_stream_ogg_vorbis.cpp b/modules/vorbis/audio_stream_ogg_vorbis.cpp index fcd717cfec..b54335b724 100644 --- a/modules/vorbis/audio_stream_ogg_vorbis.cpp +++ b/modules/vorbis/audio_stream_ogg_vorbis.cpp @@ -33,6 +33,7 @@ #include "core/io/file_access.h" #include "core/variant/typed_array.h" +#include "modules/vorbis/resource_importer_ogg_vorbis.h" #include <ogg/ogg.h> int AudioStreamPlaybackOggVorbis::_mix_internal(AudioFrame *p_buffer, int p_frames) { @@ -520,6 +521,9 @@ bool AudioStreamOggVorbis::is_monophonic() const { } void AudioStreamOggVorbis::_bind_methods() { + ClassDB::bind_static_method("AudioStreamOggVorbis", D_METHOD("load_from_buffer", "buffer"), &AudioStreamOggVorbis::load_from_buffer); + ClassDB::bind_static_method("AudioStreamOggVorbis", D_METHOD("load_from_file", "path"), &AudioStreamOggVorbis::load_from_file); + 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); @@ -549,3 +553,11 @@ void AudioStreamOggVorbis::_bind_methods() { AudioStreamOggVorbis::AudioStreamOggVorbis() {} AudioStreamOggVorbis::~AudioStreamOggVorbis() {} + +Ref<AudioStreamOggVorbis> AudioStreamOggVorbis::load_from_buffer(const Vector<uint8_t> &file_data) { + return ResourceImporterOggVorbis::load_from_buffer(file_data); +} + +Ref<AudioStreamOggVorbis> AudioStreamOggVorbis::load_from_file(const String &p_path) { + return ResourceImporterOggVorbis::load_from_file(p_path); +} diff --git a/modules/vorbis/audio_stream_ogg_vorbis.h b/modules/vorbis/audio_stream_ogg_vorbis.h index c76df7f84d..41ce942eec 100644 --- a/modules/vorbis/audio_stream_ogg_vorbis.h +++ b/modules/vorbis/audio_stream_ogg_vorbis.h @@ -125,6 +125,8 @@ protected: static void _bind_methods(); public: + static Ref<AudioStreamOggVorbis> load_from_file(const String &p_path); + static Ref<AudioStreamOggVorbis> load_from_buffer(const Vector<uint8_t> &file_data); void set_loop(bool p_enable); virtual bool has_loop() const override; diff --git a/modules/vorbis/doc_classes/AudioStreamOggVorbis.xml b/modules/vorbis/doc_classes/AudioStreamOggVorbis.xml index 4f920e2e04..7e3af6688a 100644 --- a/modules/vorbis/doc_classes/AudioStreamOggVorbis.xml +++ b/modules/vorbis/doc_classes/AudioStreamOggVorbis.xml @@ -1,11 +1,29 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="AudioStreamOggVorbis" inherits="AudioStream" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> + A class representing an Ogg Vorbis audio stream. </brief_description> <description> + The AudioStreamOggVorbis class is a specialized [AudioStream] for handling Ogg Vorbis file formats. It offers functionality for loading and playing back Ogg Vorbis files, as well as managing looping and other playback properties. This class is part of the audio stream system, which also supports WAV files through the [AudioStreamWAV] class. </description> <tutorials> </tutorials> + <methods> + <method name="load_from_buffer" qualifiers="static"> + <return type="AudioStreamOggVorbis" /> + <param index="0" name="buffer" type="PackedByteArray" /> + <description> + Creates a new AudioStreamOggVorbis instance from the given buffer. The buffer must contain Ogg Vorbis data. + </description> + </method> + <method name="load_from_file" qualifiers="static"> + <return type="AudioStreamOggVorbis" /> + <param index="0" name="path" type="String" /> + <description> + Creates a new AudioStreamOggVorbis instance from the given file path. The file must be in Ogg Vorbis format. + </description> + </method> + </methods> <members> <member name="bar_beats" type="int" setter="set_bar_beats" getter="get_bar_beats" default="4"> </member> diff --git a/modules/vorbis/doc_classes/ResourceImporterOggVorbis.xml b/modules/vorbis/doc_classes/ResourceImporterOggVorbis.xml index ccedb9c98e..10c87b899f 100644 --- a/modules/vorbis/doc_classes/ResourceImporterOggVorbis.xml +++ b/modules/vorbis/doc_classes/ResourceImporterOggVorbis.xml @@ -6,6 +6,22 @@ </description> <tutorials> </tutorials> + <methods> + <method name="load_from_buffer" qualifiers="static"> + <return type="AudioStreamOggVorbis" /> + <param index="0" name="buffer" type="PackedByteArray" /> + <description> + This method loads audio data from a PackedByteArray buffer into an AudioStreamOggVorbis object. + </description> + </method> + <method name="load_from_file" qualifiers="static"> + <return type="AudioStreamOggVorbis" /> + <param index="0" name="path" type="String" /> + <description> + This method loads audio data from a file into an AudioStreamOggVorbis object. The file path is provided as a string. + </description> + </method> + </methods> <members> <member name="bar_beats" type="int" setter="" getter="" default="4"> </member> diff --git a/modules/vorbis/register_types.cpp b/modules/vorbis/register_types.cpp index 028b7a3086..26af912999 100644 --- a/modules/vorbis/register_types.cpp +++ b/modules/vorbis/register_types.cpp @@ -31,7 +31,10 @@ #include "register_types.h" #include "audio_stream_ogg_vorbis.h" + +#ifdef TOOLS_ENABLED #include "resource_importer_ogg_vorbis.h" +#endif void initialize_vorbis_module(ModuleInitializationLevel p_level) { if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) { diff --git a/modules/vorbis/resource_importer_ogg_vorbis.cpp b/modules/vorbis/resource_importer_ogg_vorbis.cpp index 8392750798..b42cd20589 100644 --- a/modules/vorbis/resource_importer_ogg_vorbis.cpp +++ b/modules/vorbis/resource_importer_ogg_vorbis.cpp @@ -81,18 +81,50 @@ void ResourceImporterOggVorbis::get_import_options(const String &p_path, List<Im r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "bar_beats", PROPERTY_HINT_RANGE, "2,32,or_greater"), 4)); } -Ref<AudioStreamOggVorbis> ResourceImporterOggVorbis::import_ogg_vorbis(const String &p_path) { - Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ); - ERR_FAIL_COND_V_MSG(f.is_null(), Ref<AudioStreamOggVorbis>(), "Cannot open file '" + p_path + "'."); +#ifdef TOOLS_ENABLED + +bool ResourceImporterOggVorbis::has_advanced_options() const { + return true; +} + +void ResourceImporterOggVorbis::show_advanced_options(const String &p_path) { + Ref<AudioStreamOggVorbis> ogg_stream = load_from_file(p_path); + if (ogg_stream.is_valid()) { + AudioStreamImportSettings::get_singleton()->edit(p_path, "oggvorbisstr", ogg_stream); + } +} +#endif + +Error ResourceImporterOggVorbis::import(const String &p_source_file, const String &p_save_path, const HashMap<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files, Variant *r_metadata) { + bool loop = p_options["loop"]; + float loop_offset = p_options["loop_offset"]; + double bpm = p_options["bpm"]; + int beat_count = p_options["beat_count"]; + int bar_beats = p_options["bar_beats"]; - uint64_t len = f->get_length(); + Ref<AudioStreamOggVorbis> ogg_vorbis_stream = load_from_file(p_source_file); + if (ogg_vorbis_stream.is_null()) { + return ERR_CANT_OPEN; + } - Vector<uint8_t> file_data; - file_data.resize(len); - uint8_t *w = file_data.ptrw(); + ogg_vorbis_stream->set_loop(loop); + ogg_vorbis_stream->set_loop_offset(loop_offset); + ogg_vorbis_stream->set_bpm(bpm); + ogg_vorbis_stream->set_beat_count(beat_count); + ogg_vorbis_stream->set_bar_beats(bar_beats); + + return ResourceSaver::save(ogg_vorbis_stream, p_save_path + ".oggvorbisstr"); +} - f->get_buffer(w, len); +ResourceImporterOggVorbis::ResourceImporterOggVorbis() { +} + +void ResourceImporterOggVorbis::_bind_methods() { + ClassDB::bind_static_method("ResourceImporterOggVorbis", D_METHOD("load_from_buffer", "buffer"), &ResourceImporterOggVorbis::load_from_buffer); + ClassDB::bind_static_method("ResourceImporterOggVorbis", D_METHOD("load_from_file", "path"), &ResourceImporterOggVorbis::load_from_file); +} +Ref<AudioStreamOggVorbis> ResourceImporterOggVorbis::load_from_buffer(const Vector<uint8_t> &file_data) { Ref<AudioStreamOggVorbis> ogg_vorbis_stream; ogg_vorbis_stream.instantiate(); @@ -114,7 +146,7 @@ Ref<AudioStreamOggVorbis> ResourceImporterOggVorbis::import_ogg_vorbis(const Str err = ogg_sync_check(&sync_state); ERR_FAIL_COND_V_MSG(err != 0, Ref<AudioStreamOggVorbis>(), "Ogg sync error " + itos(err)); while (ogg_sync_pageout(&sync_state, &page) != 1) { - if (cursor >= len) { + if (cursor >= size_t(file_data.size())) { done = true; break; } @@ -123,8 +155,8 @@ Ref<AudioStreamOggVorbis> ResourceImporterOggVorbis::import_ogg_vorbis(const Str char *sync_buf = ogg_sync_buffer(&sync_state, OGG_SYNC_BUFFER_SIZE); err = ogg_sync_check(&sync_state); ERR_FAIL_COND_V_MSG(err != 0, Ref<AudioStreamOggVorbis>(), "Ogg sync error " + itos(err)); - ERR_FAIL_COND_V(cursor > len, Ref<AudioStreamOggVorbis>()); - size_t copy_size = len - cursor; + ERR_FAIL_COND_V(cursor > size_t(file_data.size()), Ref<AudioStreamOggVorbis>()); + size_t copy_size = file_data.size() - cursor; if (copy_size > OGG_SYNC_BUFFER_SIZE) { copy_size = OGG_SYNC_BUFFER_SIZE; } @@ -201,40 +233,8 @@ Ref<AudioStreamOggVorbis> ResourceImporterOggVorbis::import_ogg_vorbis(const Str return ogg_vorbis_stream; } -#ifdef TOOLS_ENABLED - -bool ResourceImporterOggVorbis::has_advanced_options() const { - return true; -} - -void ResourceImporterOggVorbis::show_advanced_options(const String &p_path) { - Ref<AudioStreamOggVorbis> ogg_stream = import_ogg_vorbis(p_path); - if (ogg_stream.is_valid()) { - AudioStreamImportSettings::get_singleton()->edit(p_path, "oggvorbisstr", ogg_stream); - } -} -#endif - -Error ResourceImporterOggVorbis::import(const String &p_source_file, const String &p_save_path, const HashMap<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files, Variant *r_metadata) { - bool loop = p_options["loop"]; - float loop_offset = p_options["loop_offset"]; - double bpm = p_options["bpm"]; - int beat_count = p_options["beat_count"]; - int bar_beats = p_options["bar_beats"]; - - Ref<AudioStreamOggVorbis> ogg_vorbis_stream = import_ogg_vorbis(p_source_file); - if (ogg_vorbis_stream.is_null()) { - return ERR_CANT_OPEN; - } - - ogg_vorbis_stream->set_loop(loop); - ogg_vorbis_stream->set_loop_offset(loop_offset); - ogg_vorbis_stream->set_bpm(bpm); - ogg_vorbis_stream->set_beat_count(beat_count); - ogg_vorbis_stream->set_bar_beats(bar_beats); - - return ResourceSaver::save(ogg_vorbis_stream, p_save_path + ".oggvorbisstr"); -} - -ResourceImporterOggVorbis::ResourceImporterOggVorbis() { +Ref<AudioStreamOggVorbis> ResourceImporterOggVorbis::load_from_file(const String &p_path) { + Vector<uint8_t> file_data = FileAccess::get_file_as_bytes(p_path); + ERR_FAIL_COND_V_MSG(file_data.is_empty(), Ref<AudioStreamOggVorbis>(), "Cannot open file '" + p_path + "'."); + return load_from_buffer(file_data); } diff --git a/modules/vorbis/resource_importer_ogg_vorbis.h b/modules/vorbis/resource_importer_ogg_vorbis.h index 4874419834..59ae3378a0 100644 --- a/modules/vorbis/resource_importer_ogg_vorbis.h +++ b/modules/vorbis/resource_importer_ogg_vorbis.h @@ -42,16 +42,17 @@ class ResourceImporterOggVorbis : public ResourceImporter { OGG_SYNC_BUFFER_SIZE = 8192, }; -private: - // virtual int get_samples_in_packet(Vector<uint8_t> p_packet) = 0; - - static Ref<AudioStreamOggVorbis> import_ogg_vorbis(const String &p_path); +protected: + static void _bind_methods(); public: #ifdef TOOLS_ENABLED virtual bool has_advanced_options() const override; virtual void show_advanced_options(const String &p_path) override; #endif + + static Ref<AudioStreamOggVorbis> load_from_file(const String &p_path); + static Ref<AudioStreamOggVorbis> load_from_buffer(const Vector<uint8_t> &file_data); 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; diff --git a/modules/webp/resource_saver_webp.cpp b/modules/webp/resource_saver_webp.cpp index 92285e2eab..52289334f8 100644 --- a/modules/webp/resource_saver_webp.cpp +++ b/modules/webp/resource_saver_webp.cpp @@ -34,7 +34,7 @@ #include "core/io/file_access.h" #include "core/io/image.h" -#include "scene/resources/texture.h" +#include "scene/resources/image_texture.h" Error ResourceSaverWebP::save(const Ref<Resource> &p_resource, const String &p_path, uint32_t p_flags) { Ref<ImageTexture> texture = p_resource; diff --git a/modules/webp/webp_common.cpp b/modules/webp/webp_common.cpp index 60cb0091e1..bc34a25733 100644 --- a/modules/webp/webp_common.cpp +++ b/modules/webp/webp_common.cpp @@ -84,6 +84,7 @@ Vector<uint8_t> _webp_packer(const Ref<Image> &p_image, float p_quality, bool p_ } config.method = compression_method; config.quality = p_quality; + config.use_sharp_yuv = 1; pic.use_argb = 1; pic.width = s.width; pic.height = s.height; diff --git a/platform/android/doc_classes/EditorExportPlatformAndroid.xml b/platform/android/doc_classes/EditorExportPlatformAndroid.xml index 6d3affed15..d61d63d242 100644 --- a/platform/android/doc_classes/EditorExportPlatformAndroid.xml +++ b/platform/android/doc_classes/EditorExportPlatformAndroid.xml @@ -104,6 +104,12 @@ <member name="package/retain_data_on_uninstall" type="bool" setter="" getter=""> If [code]true[/code], when the user uninstalls an app, a prompt to keep the app's data will be shown. </member> + <member name="package/show_as_launcher_app" type="bool" setter="" getter=""> + If [code]true[/code], the user will be able to set this app as the system launcher in Android preferences. + </member> + <member name="package/show_in_android_tv" type="bool" setter="" getter=""> + If [code]true[/code], this app will show in Android TV launcher UI. + </member> <member name="package/signed" type="bool" setter="" getter=""> If [code]true[/code], package signing is enabled. </member> @@ -578,12 +584,6 @@ <member name="version/name" type="String" setter="" getter=""> Application version visible to the user. </member> - <member name="xr_features/hand_tracking" type="int" setter="" getter=""> - </member> - <member name="xr_features/hand_tracking_frequency" type="int" setter="" getter=""> - </member> - <member name="xr_features/passthrough" type="int" setter="" getter=""> - </member> <member name="xr_features/xr_mode" type="int" setter="" getter=""> </member> </members> diff --git a/platform/android/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp index 830548d801..4eb516fb63 100644 --- a/platform/android/export/export_plugin.cpp +++ b/platform/android/export/export_plugin.cpp @@ -48,6 +48,7 @@ #include "editor/editor_scale.h" #include "editor/editor_settings.h" #include "main/splash.gen.h" +#include "scene/resources/image_texture.h" #include "modules/modules_enabled.gen.h" // For mono and svg. #ifdef MODULE_SVG_ENABLED @@ -260,30 +261,32 @@ void EditorExportPlatformAndroid::_check_for_changes_poll_thread(void *ud) { EditorExportPlatformAndroid *ea = static_cast<EditorExportPlatformAndroid *>(ud); while (!ea->quit_request.is_set()) { - // Check for plugins updates +#ifndef DISABLE_DEPRECATED + // Check for android plugins updates { // Nothing to do if we already know the plugins have changed. - if (!ea->plugins_changed.is_set()) { + if (!ea->android_plugins_changed.is_set()) { Vector<PluginConfigAndroid> loaded_plugins = get_plugins(); - MutexLock lock(ea->plugins_lock); + MutexLock lock(ea->android_plugins_lock); - if (ea->plugins.size() != loaded_plugins.size()) { - ea->plugins_changed.set(); + if (ea->android_plugins.size() != loaded_plugins.size()) { + ea->android_plugins_changed.set(); } else { - for (int i = 0; i < ea->plugins.size(); i++) { - if (ea->plugins[i].name != loaded_plugins[i].name) { - ea->plugins_changed.set(); + for (int i = 0; i < ea->android_plugins.size(); i++) { + if (ea->android_plugins[i].name != loaded_plugins[i].name) { + ea->android_plugins_changed.set(); break; } } } - if (ea->plugins_changed.is_set()) { - ea->plugins = loaded_plugins; + if (ea->android_plugins_changed.is_set()) { + ea->android_plugins = loaded_plugins; } } } +#endif // DISABLE_DEPRECATED // Check for devices updates String adb = get_adb_path(); @@ -627,6 +630,7 @@ Vector<EditorExportPlatformAndroid::ABI> EditorExportPlatformAndroid::get_abis() return abis; } +#ifndef DISABLE_DEPRECATED /// List the gdap files in the directory specified by the p_path parameter. Vector<String> EditorExportPlatformAndroid::list_gdap_files(const String &p_path) { Vector<String> dir_files; @@ -693,6 +697,7 @@ Vector<PluginConfigAndroid> EditorExportPlatformAndroid::get_enabled_plugins(con return enabled_plugins; } +#endif // DISABLE_DEPRECATED Error EditorExportPlatformAndroid::store_in_apk(APKExportData *ed, const String &p_path, const Vector<uint8_t> &p_data, int compression_method) { zip_fileinfo zipfi = get_zip_fileinfo(); @@ -827,16 +832,6 @@ void EditorExportPlatformAndroid::_get_permissions(const Ref<EditorExportPreset> r_permissions.push_back("android.permission.INTERNET"); } } - - int xr_mode_index = p_preset->get("xr_features/xr_mode"); - if (xr_mode_index == XR_MODE_OPENXR) { - int hand_tracking_index = p_preset->get("xr_features/hand_tracking"); // 0: none, 1: optional, 2: required - if (hand_tracking_index > XR_HAND_TRACKING_NONE) { - if (r_permissions.find("com.oculus.permission.HAND_TRACKING") == -1) { - r_permissions.push_back("com.oculus.permission.HAND_TRACKING"); - } - } - } } void EditorExportPlatformAndroid::_write_tmp_manifest(const Ref<EditorExportPreset> &p_preset, bool p_give_internet, bool p_debug) { @@ -860,8 +855,23 @@ void EditorExportPlatformAndroid::_write_tmp_manifest(const Ref<EditorExportPres } } - manifest_text += _get_xr_features_tag(p_preset, _uses_vulkan()); - manifest_text += _get_application_tag(p_preset, _has_read_write_storage_permission(perms)); + if (_uses_vulkan()) { + manifest_text += " <uses-feature tools:node=\"replace\" android:name=\"android.hardware.vulkan.level\" android:required=\"false\" android:version=\"1\" />\n"; + manifest_text += " <uses-feature tools:node=\"replace\" android:name=\"android.hardware.vulkan.version\" android:required=\"true\" android:version=\"0x400003\" />\n"; + } + + Vector<Ref<EditorExportPlugin>> export_plugins = EditorExport::get_singleton()->get_export_plugins(); + for (int i = 0; i < export_plugins.size(); i++) { + if (export_plugins[i]->supports_platform(Ref<EditorExportPlatform>(this))) { + const String contents = export_plugins[i]->get_android_manifest_element_contents(Ref<EditorExportPlatform>(this), p_debug); + if (!contents.is_empty()) { + manifest_text += contents; + manifest_text += "\n"; + } + } + } + + manifest_text += _get_application_tag(Ref<EditorExportPlatform>(this), p_preset, _has_read_write_storage_permission(perms), p_debug); manifest_text += "</manifest>\n"; String manifest_path = vformat("res://android/build/src/%s/AndroidManifest.xml", (p_debug ? "debug" : "release")); @@ -1720,7 +1730,7 @@ String EditorExportPlatformAndroid::get_export_option_warning(const EditorExport } } else if (p_name == "gradle_build/use_gradle_build") { bool gradle_build_enabled = p_preset->get("gradle_build/use_gradle_build"); - String enabled_plugins_names = PluginConfigAndroid::get_plugins_names(get_enabled_plugins(Ref<EditorExportPreset>(p_preset))); + String enabled_plugins_names = _get_plugins_names(Ref<EditorExportPreset>(p_preset)); if (!enabled_plugins_names.is_empty() && !gradle_build_enabled) { return TTR("\"Use Gradle Build\" must be enabled to use the plugins."); } @@ -1730,22 +1740,6 @@ String EditorExportPlatformAndroid::get_export_option_warning(const EditorExport if (xr_mode_index == XR_MODE_OPENXR && !gradle_build_enabled) { return TTR("OpenXR requires \"Use Gradle Build\" to be enabled"); } - } else if (p_name == "xr_features/hand_tracking") { - int xr_mode_index = p_preset->get("xr_features/xr_mode"); - int hand_tracking = p_preset->get("xr_features/hand_tracking"); - if (xr_mode_index != XR_MODE_OPENXR) { - if (hand_tracking > XR_HAND_TRACKING_NONE) { - return TTR("\"Hand Tracking\" is only valid when \"XR Mode\" is \"OpenXR\"."); - } - } - } else if (p_name == "xr_features/passthrough") { - int xr_mode_index = p_preset->get("xr_features/xr_mode"); - int passthrough_mode = p_preset->get("xr_features/passthrough"); - if (xr_mode_index != XR_MODE_OPENXR) { - if (passthrough_mode > XR_PASSTHROUGH_NONE) { - return TTR("\"Passthrough\" is only valid when \"XR Mode\" is \"OpenXR\"."); - } - } } else if (p_name == "gradle_build/export_format") { bool gradle_build_enabled = p_preset->get("gradle_build/use_gradle_build"); if (int(p_preset->get("gradle_build/export_format")) == EXPORT_FORMAT_AAB && !gradle_build_enabled) { @@ -1807,12 +1801,14 @@ void EditorExportPlatformAndroid::get_export_options(List<ExportOption> *r_optio r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "gradle_build/min_sdk", PROPERTY_HINT_PLACEHOLDER_TEXT, vformat("%d (default)", VULKAN_MIN_SDK_VERSION)), "", false, true)); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "gradle_build/target_sdk", PROPERTY_HINT_PLACEHOLDER_TEXT, vformat("%d (default)", DEFAULT_TARGET_SDK_VERSION)), "", false, true)); +#ifndef DISABLE_DEPRECATED Vector<PluginConfigAndroid> plugins_configs = get_plugins(); for (int i = 0; i < plugins_configs.size(); i++) { print_verbose("Found Android plugin " + plugins_configs[i].name); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, vformat("%s/%s", PNAME("plugins"), plugins_configs[i].name)), false)); } - plugins_changed.clear(); + android_plugins_changed.clear(); +#endif // DISABLE_DEPRECATED // Android supports multiple architectures in an app bundle, so // we expose each option as a checkbox in the export dialog. @@ -1841,6 +1837,8 @@ void EditorExportPlatformAndroid::get_export_options(List<ExportOption> *r_optio r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "package/app_category", PROPERTY_HINT_ENUM, "Accessibility,Audio,Game,Image,Maps,News,Productivity,Social,Video"), APP_CATEGORY_GAME)); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "package/retain_data_on_uninstall"), false)); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "package/exclude_from_recents"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "package/show_in_android_tv"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "package/show_as_launcher_app"), false)); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, launcher_icon_option, PROPERTY_HINT_FILE, "*.png"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, launcher_adaptive_icon_foreground_option, PROPERTY_HINT_FILE, "*.png"), "")); @@ -1849,9 +1847,6 @@ void EditorExportPlatformAndroid::get_export_options(List<ExportOption> *r_optio r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "graphics/opengl_debug"), false)); r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "xr_features/xr_mode", PROPERTY_HINT_ENUM, "Regular,OpenXR"), XR_MODE_REGULAR, false, true)); - r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "xr_features/hand_tracking", PROPERTY_HINT_ENUM, "None,Optional,Required"), XR_HAND_TRACKING_NONE, false, true)); - r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "xr_features/hand_tracking_frequency", PROPERTY_HINT_ENUM, "Low,High"), XR_HAND_TRACKING_FREQUENCY_LOW)); - r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "xr_features/passthrough", PROPERTY_HINT_ENUM, "None,Optional,Required"), XR_PASSTHROUGH_NONE, false, true)); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "screen/immersive_mode"), true)); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "screen/support_small"), true)); @@ -1889,12 +1884,14 @@ Ref<Texture2D> EditorExportPlatformAndroid::get_logo() const { } bool EditorExportPlatformAndroid::should_update_export_options() { - bool export_options_changed = plugins_changed.is_set(); - if (export_options_changed) { +#ifndef DISABLE_DEPRECATED + if (android_plugins_changed.is_set()) { // don't clear unless we're reporting true, to avoid race - plugins_changed.clear(); + android_plugins_changed.clear(); + return true; } - return export_options_changed; +#endif // DISABLE_DEPRECATED + return false; } bool EditorExportPlatformAndroid::poll_export() { @@ -2228,17 +2225,16 @@ String EditorExportPlatformAndroid::get_apksigner_path(int p_target_sdk, bool p_ } bool EditorExportPlatformAndroid::has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug) const { - String err; - bool valid = false; - const bool gradle_build_enabled = p_preset->get("gradle_build/use_gradle_build"); - #ifdef MODULE_MONO_ENABLED - err += TTR("Exporting to Android is currently not supported in Godot 4 when using C#/.NET. Use Godot 3 to target Android with C#/Mono instead.") + "\n"; - err += TTR("If this project does not use C#, use a non-C# editor build to export the project.") + "\n"; // Don't check for additional errors, as this particular error cannot be resolved. - r_error = err; + r_error += TTR("Exporting to Android is currently not supported in Godot 4 when using C#/.NET. Use Godot 3 to target Android with C#/Mono instead.") + "\n"; + r_error += TTR("If this project does not use C#, use a non-C# editor build to export the project.") + "\n"; return false; -#endif +#else + + String err; + bool valid = false; + const bool gradle_build_enabled = p_preset->get("gradle_build/use_gradle_build"); // Look for export templates (first official, and if defined custom templates). @@ -2369,6 +2365,7 @@ bool EditorExportPlatformAndroid::has_valid_export_configuration(const Ref<Edito } return valid; +#endif // !MODULE_MONO_ENABLED } bool EditorExportPlatformAndroid::has_valid_project_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error) const { @@ -2694,6 +2691,64 @@ String EditorExportPlatformAndroid::join_abis(const Vector<EditorExportPlatformA return ret; } +String EditorExportPlatformAndroid::_get_plugins_names(const Ref<EditorExportPreset> &p_preset) const { + Vector<String> names; + +#ifndef DISABLE_DEPRECATED + PluginConfigAndroid::get_plugins_names(get_enabled_plugins(p_preset), names); +#endif // DISABLE_DEPRECATED + + Vector<Ref<EditorExportPlugin>> export_plugins = EditorExport::get_singleton()->get_export_plugins(); + for (int i = 0; i < export_plugins.size(); i++) { + if (export_plugins[i]->supports_platform(Ref<EditorExportPlatform>(this))) { + names.push_back(export_plugins[i]->get_name()); + } + } + + String plugins_names = String("|").join(names); + return plugins_names; +} + +String EditorExportPlatformAndroid::_resolve_export_plugin_android_library_path(const String &p_android_library_path) const { + String absolute_path; + if (!p_android_library_path.is_empty()) { + if (p_android_library_path.is_absolute_path()) { + absolute_path = ProjectSettings::get_singleton()->globalize_path(p_android_library_path); + } else { + const String export_plugin_absolute_path = String("res://addons/").path_join(p_android_library_path); + absolute_path = ProjectSettings::get_singleton()->globalize_path(export_plugin_absolute_path); + } + } + return absolute_path; +} + +bool EditorExportPlatformAndroid::_is_clean_build_required(const Ref<EditorExportPreset> &p_preset) { + bool first_build = last_gradle_build_time == 0; + bool have_plugins_changed = false; + + String plugin_names = _get_plugins_names(p_preset); + + if (!first_build) { + have_plugins_changed = plugin_names != last_plugin_names; +#ifndef DISABLE_DEPRECATED + if (!have_plugins_changed) { + Vector<PluginConfigAndroid> enabled_plugins = get_enabled_plugins(p_preset); + for (int i = 0; i < enabled_plugins.size(); i++) { + if (enabled_plugins.get(i).last_updated > last_gradle_build_time) { + have_plugins_changed = true; + break; + } + } + } +#endif // DISABLE_DEPRECATED + } + + last_gradle_build_time = OS::get_singleton()->get_unix_time(); + last_plugin_names = plugin_names; + + return have_plugins_changed || first_build; +} + Error EditorExportPlatformAndroid::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) { int export_format = int(p_preset->get("gradle_build/export_format")); bool should_sign = p_preset->get("package/signed"); @@ -2851,11 +2906,40 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP String sign_flag = should_sign ? "true" : "false"; String zipalign_flag = "true"; + Vector<String> android_libraries; + Vector<String> android_dependencies; + Vector<String> android_dependencies_maven_repos; + +#ifndef DISABLE_DEPRECATED Vector<PluginConfigAndroid> enabled_plugins = get_enabled_plugins(p_preset); - String local_plugins_binaries = PluginConfigAndroid::get_plugins_binaries(PluginConfigAndroid::BINARY_TYPE_LOCAL, enabled_plugins); - String remote_plugins_binaries = PluginConfigAndroid::get_plugins_binaries(PluginConfigAndroid::BINARY_TYPE_REMOTE, enabled_plugins); - String custom_maven_repos = PluginConfigAndroid::get_plugins_custom_maven_repos(enabled_plugins); - bool clean_build_required = is_clean_build_required(enabled_plugins); + PluginConfigAndroid::get_plugins_binaries(PluginConfigAndroid::BINARY_TYPE_LOCAL, enabled_plugins, android_libraries); + PluginConfigAndroid::get_plugins_binaries(PluginConfigAndroid::BINARY_TYPE_REMOTE, enabled_plugins, android_dependencies); + PluginConfigAndroid::get_plugins_custom_maven_repos(enabled_plugins, android_dependencies_maven_repos); +#endif // DISABLE_DEPRECATED + + Vector<Ref<EditorExportPlugin>> export_plugins = EditorExport::get_singleton()->get_export_plugins(); + for (int i = 0; i < export_plugins.size(); i++) { + if (export_plugins[i]->supports_platform(Ref<EditorExportPlatform>(this))) { + PackedStringArray export_plugin_android_libraries = export_plugins[i]->get_android_libraries(Ref<EditorExportPlatform>(this), p_debug); + for (int k = 0; k < export_plugin_android_libraries.size(); k++) { + const String resolved_android_library_path = _resolve_export_plugin_android_library_path(export_plugin_android_libraries[k]); + if (!resolved_android_library_path.is_empty()) { + android_libraries.push_back(resolved_android_library_path); + } + } + + PackedStringArray export_plugin_android_dependencies = export_plugins[i]->get_android_dependencies(Ref<EditorExportPlatform>(this), p_debug); + android_dependencies.append_array(export_plugin_android_dependencies); + + PackedStringArray export_plugin_android_dependencies_maven_repos = export_plugins[i]->get_android_dependencies_maven_repos(Ref<EditorExportPlatform>(this), p_debug); + android_dependencies_maven_repos.append_array(export_plugin_android_dependencies_maven_repos); + } + } + + bool clean_build_required = _is_clean_build_required(p_preset); + String combined_android_libraries = String("|").join(android_libraries); + String combined_android_dependencies = String("|").join(android_dependencies); + String combined_android_dependencies_maven_repos = String("|").join(android_dependencies_maven_repos); List<String> cmdline; if (clean_build_required) { @@ -2879,9 +2963,9 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP cmdline.push_back("-Pexport_version_min_sdk=" + min_sdk_version); // argument to specify the min sdk. cmdline.push_back("-Pexport_version_target_sdk=" + target_sdk_version); // argument to specify the target sdk. cmdline.push_back("-Pexport_enabled_abis=" + enabled_abi_string); // argument to specify enabled ABIs. - cmdline.push_back("-Pplugins_local_binaries=" + local_plugins_binaries); // argument to specify the list of plugins local dependencies. - cmdline.push_back("-Pplugins_remote_binaries=" + remote_plugins_binaries); // argument to specify the list of plugins remote dependencies. - cmdline.push_back("-Pplugins_maven_repos=" + custom_maven_repos); // argument to specify the list of custom maven repos for the plugins dependencies. + cmdline.push_back("-Pplugins_local_binaries=" + combined_android_libraries); // argument to specify the list of android libraries provided by plugins. + cmdline.push_back("-Pplugins_remote_binaries=" + combined_android_dependencies); // argument to specify the list of android dependencies provided by plugins. + cmdline.push_back("-Pplugins_maven_repos=" + combined_android_dependencies_maven_repos); // argument to specify the list of maven repos for android dependencies provided by plugins. cmdline.push_back("-Pperform_zipalign=" + zipalign_flag); // argument to specify whether the build should be zipaligned. cmdline.push_back("-Pperform_signing=" + sign_flag); // argument to specify whether the build should be signed. cmdline.push_back("-Pgodot_editor_version=" + String(VERSION_FULL_CONFIG)); @@ -3307,7 +3391,9 @@ EditorExportPlatformAndroid::EditorExportPlatformAndroid() { #endif devices_changed.set(); - plugins_changed.set(); +#ifndef DISABLE_DEPRECATED + android_plugins_changed.set(); +#endif // DISABLE_DEPRECATED #ifndef ANDROID_ENABLED check_for_changes_thread.start(_check_for_changes_poll_thread, this); #endif diff --git a/platform/android/export/export_plugin.h b/platform/android/export/export_plugin.h index 0ac0fbb10b..a2d0417c5d 100644 --- a/platform/android/export/export_plugin.h +++ b/platform/android/export/export_plugin.h @@ -31,7 +31,9 @@ #ifndef ANDROID_EXPORT_PLUGIN_H #define ANDROID_EXPORT_PLUGIN_H +#ifndef DISABLE_DEPRECATED #include "godot_plugin_config.h" +#endif // DISABLE_DEPRECATED #include "core/io/zip_io.h" #include "core/os/os.h" @@ -81,11 +83,14 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { EditorProgress *ep = nullptr; }; - mutable Vector<PluginConfigAndroid> plugins; +#ifndef DISABLE_DEPRECATED + mutable Vector<PluginConfigAndroid> android_plugins; + mutable SafeFlag android_plugins_changed; + Mutex android_plugins_lock; +#endif // DISABLE_DEPRECATED String last_plugin_names; uint64_t last_gradle_build_time = 0; - mutable SafeFlag plugins_changed; - Mutex plugins_lock; + Vector<Device> devices; SafeFlag devices_changed; Mutex device_lock; @@ -128,12 +133,14 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { static Vector<ABI> get_abis(); +#ifndef DISABLE_DEPRECATED /// List the gdap files in the directory specified by the p_path parameter. static Vector<String> list_gdap_files(const String &p_path); static Vector<PluginConfigAndroid> get_plugins(); static Vector<PluginConfigAndroid> get_enabled_plugins(const Ref<EditorExportPreset> &p_presets); +#endif // DISABLE_DEPRECATED static Error store_in_apk(APKExportData *ed, const String &p_path, const Vector<uint8_t> &p_data, int compression_method = Z_DEFLATED); @@ -224,28 +231,11 @@ public: virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const override; - inline bool is_clean_build_required(Vector<PluginConfigAndroid> enabled_plugins) { - String plugin_names = PluginConfigAndroid::get_plugins_names(enabled_plugins); - bool first_build = last_gradle_build_time == 0; - bool have_plugins_changed = false; - - if (!first_build) { - have_plugins_changed = plugin_names != last_plugin_names; - if (!have_plugins_changed) { - for (int i = 0; i < enabled_plugins.size(); i++) { - if (enabled_plugins.get(i).last_updated > last_gradle_build_time) { - have_plugins_changed = true; - break; - } - } - } - } + String _get_plugins_names(const Ref<EditorExportPreset> &p_preset) const; - last_gradle_build_time = OS::get_singleton()->get_unix_time(); - last_plugin_names = plugin_names; + String _resolve_export_plugin_android_library_path(const String &p_android_library_path) const; - return have_plugins_changed || first_build; - } + bool _is_clean_build_required(const Ref<EditorExportPreset> &p_preset); String get_apk_expansion_fullpath(const Ref<EditorExportPreset> &p_preset, const String &p_path); diff --git a/platform/android/export/godot_plugin_config.cpp b/platform/android/export/godot_plugin_config.cpp index b64cca3254..cdec5f55b7 100644 --- a/platform/android/export/godot_plugin_config.cpp +++ b/platform/android/export/godot_plugin_config.cpp @@ -30,6 +30,8 @@ #include "godot_plugin_config.h" +#ifndef DISABLE_DEPRECATED + /* * Set of prebuilt plugins. * Currently unused, this is just for future reference: @@ -145,10 +147,8 @@ PluginConfigAndroid PluginConfigAndroid::load_plugin_config(Ref<ConfigFile> conf return plugin_config; } -String PluginConfigAndroid::get_plugins_binaries(String binary_type, Vector<PluginConfigAndroid> plugins_configs) { - String plugins_binaries; +void PluginConfigAndroid::get_plugins_binaries(String binary_type, Vector<PluginConfigAndroid> plugins_configs, Vector<String> &r_result) { if (!plugins_configs.is_empty()) { - Vector<String> binaries; for (int i = 0; i < plugins_configs.size(); i++) { PluginConfigAndroid config = plugins_configs[i]; if (!config.valid_config) { @@ -156,56 +156,44 @@ String PluginConfigAndroid::get_plugins_binaries(String binary_type, Vector<Plug } if (config.binary_type == binary_type) { - binaries.push_back(config.binary); + r_result.push_back(config.binary); } if (binary_type == PluginConfigAndroid::BINARY_TYPE_LOCAL) { - binaries.append_array(config.local_dependencies); + r_result.append_array(config.local_dependencies); } if (binary_type == PluginConfigAndroid::BINARY_TYPE_REMOTE) { - binaries.append_array(config.remote_dependencies); + r_result.append_array(config.remote_dependencies); } } - - plugins_binaries = String(PluginConfigAndroid::PLUGIN_VALUE_SEPARATOR).join(binaries); } - - return plugins_binaries; } -String PluginConfigAndroid::get_plugins_custom_maven_repos(Vector<PluginConfigAndroid> plugins_configs) { - String custom_maven_repos; +void PluginConfigAndroid::get_plugins_custom_maven_repos(Vector<PluginConfigAndroid> plugins_configs, Vector<String> &r_result) { if (!plugins_configs.is_empty()) { - Vector<String> repos_urls; for (int i = 0; i < plugins_configs.size(); i++) { PluginConfigAndroid config = plugins_configs[i]; if (!config.valid_config) { continue; } - repos_urls.append_array(config.custom_maven_repos); + r_result.append_array(config.custom_maven_repos); } - - custom_maven_repos = String(PluginConfigAndroid::PLUGIN_VALUE_SEPARATOR).join(repos_urls); } - return custom_maven_repos; } -String PluginConfigAndroid::get_plugins_names(Vector<PluginConfigAndroid> plugins_configs) { - String plugins_names; +void PluginConfigAndroid::get_plugins_names(Vector<PluginConfigAndroid> plugins_configs, Vector<String> &r_result) { if (!plugins_configs.is_empty()) { - Vector<String> names; for (int i = 0; i < plugins_configs.size(); i++) { PluginConfigAndroid config = plugins_configs[i]; if (!config.valid_config) { continue; } - names.push_back(config.name); + r_result.push_back(config.name); } - plugins_names = String(PluginConfigAndroid::PLUGIN_VALUE_SEPARATOR).join(names); } - - return plugins_names; } + +#endif // DISABLE_DEPRECATED diff --git a/platform/android/export/godot_plugin_config.h b/platform/android/export/godot_plugin_config.h index bef00979a9..8c56d00187 100644 --- a/platform/android/export/godot_plugin_config.h +++ b/platform/android/export/godot_plugin_config.h @@ -31,6 +31,8 @@ #ifndef ANDROID_GODOT_PLUGIN_CONFIG_H #define ANDROID_GODOT_PLUGIN_CONFIG_H +#ifndef DISABLE_DEPRECATED + #include "core/config/project_settings.h" #include "core/error/error_list.h" #include "core/io/config_file.h" @@ -67,8 +69,6 @@ struct PluginConfigAndroid { inline static const char *BINARY_TYPE_LOCAL = "local"; inline static const char *BINARY_TYPE_REMOTE = "remote"; - inline static const char *PLUGIN_VALUE_SEPARATOR = "|"; - // Set to true when the config file is properly loaded. bool valid_config = false; // Unix timestamp of last change to this plugin. @@ -96,11 +96,13 @@ struct PluginConfigAndroid { static PluginConfigAndroid load_plugin_config(Ref<ConfigFile> config_file, const String &path); - static String get_plugins_binaries(String binary_type, Vector<PluginConfigAndroid> plugins_configs); + static void get_plugins_binaries(String binary_type, Vector<PluginConfigAndroid> plugins_configs, Vector<String> &r_result); - static String get_plugins_custom_maven_repos(Vector<PluginConfigAndroid> plugins_configs); + static void get_plugins_custom_maven_repos(Vector<PluginConfigAndroid> plugins_configs, Vector<String> &r_result); - static String get_plugins_names(Vector<PluginConfigAndroid> plugins_configs); + static void get_plugins_names(Vector<PluginConfigAndroid> plugins_configs, Vector<String> &r_result); }; +#endif // DISABLE_DEPRECATED + #endif // ANDROID_GODOT_PLUGIN_CONFIG_H diff --git a/platform/android/export/gradle_export_util.cpp b/platform/android/export/gradle_export_util.cpp index ba4487cc4d..d0d0c34bb4 100644 --- a/platform/android/export/gradle_export_util.cpp +++ b/platform/android/export/gradle_export_util.cpp @@ -254,34 +254,7 @@ String _get_screen_sizes_tag(const Ref<EditorExportPreset> &p_preset) { return manifest_screen_sizes; } -String _get_xr_features_tag(const Ref<EditorExportPreset> &p_preset, bool p_uses_vulkan) { - String manifest_xr_features; - int xr_mode_index = (int)(p_preset->get("xr_features/xr_mode")); - bool uses_xr = xr_mode_index == XR_MODE_OPENXR; - if (uses_xr) { - int hand_tracking_index = p_preset->get("xr_features/hand_tracking"); // 0: none, 1: optional, 2: required - if (hand_tracking_index == XR_HAND_TRACKING_OPTIONAL) { - manifest_xr_features += " <uses-feature tools:node=\"replace\" android:name=\"oculus.software.handtracking\" android:required=\"false\" />\n"; - } else if (hand_tracking_index == XR_HAND_TRACKING_REQUIRED) { - manifest_xr_features += " <uses-feature tools:node=\"replace\" android:name=\"oculus.software.handtracking\" android:required=\"true\" />\n"; - } - - int passthrough_mode = p_preset->get("xr_features/passthrough"); - if (passthrough_mode == XR_PASSTHROUGH_OPTIONAL) { - manifest_xr_features += " <uses-feature tools:node=\"replace\" android:name=\"com.oculus.feature.PASSTHROUGH\" android:required=\"false\" />\n"; - } else if (passthrough_mode == XR_PASSTHROUGH_REQUIRED) { - manifest_xr_features += " <uses-feature tools:node=\"replace\" android:name=\"com.oculus.feature.PASSTHROUGH\" android:required=\"true\" />\n"; - } - } - - if (p_uses_vulkan) { - manifest_xr_features += " <uses-feature tools:node=\"replace\" android:name=\"android.hardware.vulkan.level\" android:required=\"false\" android:version=\"1\" />\n"; - manifest_xr_features += " <uses-feature tools:node=\"replace\" android:name=\"android.hardware.vulkan.version\" android:required=\"true\" android:version=\"0x400003\" />\n"; - } - return manifest_xr_features; -} - -String _get_activity_tag(const Ref<EditorExportPreset> &p_preset, bool p_uses_xr) { +String _get_activity_tag(const Ref<EditorExportPlatform> &p_export_platform, const Ref<EditorExportPreset> &p_preset, bool p_debug) { String orientation = _get_android_orientation_label(DisplayServer::ScreenOrientation(int(GLOBAL_GET("display/window/handheld/orientation")))); String manifest_activity_text = vformat( " <activity android:name=\"com.godot.game.GodotApp\" " @@ -294,40 +267,42 @@ String _get_activity_tag(const Ref<EditorExportPreset> &p_preset, bool p_uses_xr orientation, bool_to_string(bool(GLOBAL_GET("display/window/size/resizable")))); - if (p_uses_xr) { - manifest_activity_text += " <intent-filter>\n" - " <action android:name=\"android.intent.action.MAIN\" />\n" - " <category android:name=\"android.intent.category.LAUNCHER\" />\n" - "\n" - " <!-- Enable access to OpenXR on Oculus mobile devices, no-op on other Android\n" - " platforms. -->\n" - " <category android:name=\"com.oculus.intent.category.VR\" />\n" - "\n" - " <!-- OpenXR category tag to indicate the activity starts in an immersive OpenXR mode. \n" - " See https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#android-runtime-category. -->\n" - " <category android:name=\"org.khronos.openxr.intent.category.IMMERSIVE_HMD\" />\n" - "\n" - " <!-- Enable VR access on HTC Vive Focus devices. -->\n" - " <category android:name=\"com.htc.intent.category.VRAPP\" />\n" - " </intent-filter>\n"; - } else { - manifest_activity_text += " <intent-filter>\n" - " <action android:name=\"android.intent.action.MAIN\" />\n" - " <category android:name=\"android.intent.category.LAUNCHER\" />\n" - " </intent-filter>\n"; + manifest_activity_text += " <intent-filter>\n" + " <action android:name=\"android.intent.action.MAIN\" />\n" + " <category android:name=\"android.intent.category.LAUNCHER\" />\n"; + + bool uses_leanback_category = p_preset->get("package/show_in_android_tv"); + if (uses_leanback_category) { + manifest_activity_text += " <category android:name=\"android.intent.category.LEANBACK_LAUNCHER\" />\n"; + } + + bool uses_home_category = p_preset->get("package/show_as_launcher_app"); + if (uses_home_category) { + manifest_activity_text += " <category android:name=\"android.intent.category.HOME\" />\n"; + manifest_activity_text += " <category android:name=\"android.intent.category.DEFAULT\" />\n"; + } + + manifest_activity_text += " </intent-filter>\n"; + + Vector<Ref<EditorExportPlugin>> export_plugins = EditorExport::get_singleton()->get_export_plugins(); + for (int i = 0; i < export_plugins.size(); i++) { + if (export_plugins[i]->supports_platform(p_export_platform)) { + const String contents = export_plugins[i]->get_android_manifest_activity_element_contents(p_export_platform, p_debug); + if (!contents.is_empty()) { + manifest_activity_text += contents; + manifest_activity_text += "\n"; + } + } } manifest_activity_text += " </activity>\n"; return manifest_activity_text; } -String _get_application_tag(const Ref<EditorExportPreset> &p_preset, bool p_has_read_write_storage_permission) { +String _get_application_tag(const Ref<EditorExportPlatform> &p_export_platform, const Ref<EditorExportPreset> &p_preset, bool p_has_read_write_storage_permission, bool p_debug) { int app_category_index = (int)(p_preset->get("package/app_category")); bool is_game = app_category_index == APP_CATEGORY_GAME; - int xr_mode_index = (int)(p_preset->get("xr_features/xr_mode")); - bool uses_xr = xr_mode_index == XR_MODE_OPENXR; - String manifest_application_text = vformat( " <application android:label=\"@string/godot_project_name_string\"\n" " android:allowBackup=\"%s\"\n" @@ -344,18 +319,18 @@ String _get_application_tag(const Ref<EditorExportPreset> &p_preset, bool p_has_ bool_to_string(p_preset->get("package/retain_data_on_uninstall")), bool_to_string(p_has_read_write_storage_permission)); - if (uses_xr) { - bool hand_tracking_enabled = (int)(p_preset->get("xr_features/hand_tracking")) > XR_HAND_TRACKING_NONE; - if (hand_tracking_enabled) { - int hand_tracking_frequency_index = p_preset->get("xr_features/hand_tracking_frequency"); - String hand_tracking_frequency = hand_tracking_frequency_index == XR_HAND_TRACKING_FREQUENCY_LOW ? "LOW" : "HIGH"; - manifest_application_text += vformat( - " <meta-data tools:node=\"replace\" android:name=\"com.oculus.handtracking.frequency\" android:value=\"%s\" />\n", - hand_tracking_frequency); - manifest_application_text += " <meta-data tools:node=\"replace\" android:name=\"com.oculus.handtracking.version\" android:value=\"V2.0\" />\n"; + Vector<Ref<EditorExportPlugin>> export_plugins = EditorExport::get_singleton()->get_export_plugins(); + for (int i = 0; i < export_plugins.size(); i++) { + if (export_plugins[i]->supports_platform(p_export_platform)) { + const String contents = export_plugins[i]->get_android_manifest_application_element_contents(p_export_platform, p_debug); + if (!contents.is_empty()) { + manifest_application_text += contents; + manifest_application_text += "\n"; + } } } - manifest_application_text += _get_activity_tag(p_preset, uses_xr); + + manifest_application_text += _get_activity_tag(p_export_platform, p_preset, p_debug); manifest_application_text += " </application>\n"; return manifest_application_text; } diff --git a/platform/android/export/gradle_export_util.h b/platform/android/export/gradle_export_util.h index 8a885a0d12..2498394add 100644 --- a/platform/android/export/gradle_export_util.h +++ b/platform/android/export/gradle_export_util.h @@ -61,20 +61,6 @@ static const int APP_CATEGORY_VIDEO = 8; static const int XR_MODE_REGULAR = 0; static const int XR_MODE_OPENXR = 1; -// Supported XR hand tracking modes. -static const int XR_HAND_TRACKING_NONE = 0; -static const int XR_HAND_TRACKING_OPTIONAL = 1; -static const int XR_HAND_TRACKING_REQUIRED = 2; - -// Supported XR hand tracking frequencies. -static const int XR_HAND_TRACKING_FREQUENCY_LOW = 0; -static const int XR_HAND_TRACKING_FREQUENCY_HIGH = 1; - -// Supported XR passthrough modes. -static const int XR_PASSTHROUGH_NONE = 0; -static const int XR_PASSTHROUGH_OPTIONAL = 1; -static const int XR_PASSTHROUGH_REQUIRED = 2; - struct CustomExportData { String assets_directory; bool debug; @@ -116,10 +102,8 @@ String _get_gles_tag(); String _get_screen_sizes_tag(const Ref<EditorExportPreset> &p_preset); -String _get_xr_features_tag(const Ref<EditorExportPreset> &p_preset, bool p_uses_vulkan); - -String _get_activity_tag(const Ref<EditorExportPreset> &p_preset, bool p_uses_xr); +String _get_activity_tag(const Ref<EditorExportPlatform> &p_export_platform, const Ref<EditorExportPreset> &p_preset, bool p_debug); -String _get_application_tag(const Ref<EditorExportPreset> &p_preset, bool p_has_read_write_storage_permission); +String _get_application_tag(const Ref<EditorExportPlatform> &p_export_platform, const Ref<EditorExportPreset> &p_preset, bool p_has_read_write_storage_permission, bool p_debug); #endif // ANDROID_GRADLE_EXPORT_UTIL_H diff --git a/platform/android/java/app/src/com/godot/game/GodotApp.java b/platform/android/java/app/src/com/godot/game/GodotApp.java index 1d2cc05715..9142d767b4 100644 --- a/platform/android/java/app/src/com/godot/game/GodotApp.java +++ b/platform/android/java/app/src/com/godot/game/GodotApp.java @@ -30,7 +30,7 @@ package com.godot.game; -import org.godotengine.godot.FullScreenGodotApp; +import org.godotengine.godot.GodotActivity; import android.os.Bundle; @@ -38,7 +38,7 @@ import android.os.Bundle; * Template activity for Godot Android builds. * Feel free to extend and modify this class for your custom logic. */ -public class GodotApp extends FullScreenGodotApp { +public class GodotApp extends GodotActivity { @Override public void onCreate(Bundle savedInstanceState) { setTheme(R.style.GodotAppMainTheme); diff --git a/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt b/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt index 64d3d4eca1..7cedfa6888 100644 --- a/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt +++ b/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt @@ -39,7 +39,7 @@ import android.os.* import android.util.Log import android.widget.Toast import androidx.window.layout.WindowMetricsCalculator -import org.godotengine.godot.FullScreenGodotApp +import org.godotengine.godot.GodotActivity import org.godotengine.godot.GodotLib import org.godotengine.godot.utils.PermissionsUtil import org.godotengine.godot.utils.ProcessPhoenix @@ -55,7 +55,7 @@ import kotlin.math.min * * It also plays the role of the primary editor window. */ -open class GodotEditor : FullScreenGodotApp() { +open class GodotEditor : GodotActivity() { companion object { private val TAG = GodotEditor::class.java.simpleName @@ -115,7 +115,7 @@ open class GodotEditor : FullScreenGodotApp() { runOnUiThread { // Enable long press, panning and scaling gestures - godotFragment?.renderView?.inputHandler?.apply { + godotFragment?.godot?.renderView?.inputHandler?.apply { enableLongPress(longPressEnabled) enablePanningAndScalingGestures(panScaleEnabled) } @@ -318,7 +318,7 @@ open class GodotEditor : FullScreenGodotApp() { override fun onRequestPermissionsResult( requestCode: Int, - permissions: Array<String?>, + permissions: Array<String>, grantResults: IntArray ) { super.onRequestPermissionsResult(requestCode, permissions, grantResults) diff --git a/platform/android/java/lib/src/org/godotengine/godot/FullScreenGodotApp.java b/platform/android/java/lib/src/org/godotengine/godot/FullScreenGodotApp.java index 3e975449d8..91d272735e 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/FullScreenGodotApp.java +++ b/platform/android/java/lib/src/org/godotengine/godot/FullScreenGodotApp.java @@ -30,156 +30,10 @@ package org.godotengine.godot; -import org.godotengine.godot.utils.ProcessPhoenix; - -import android.content.Intent; -import android.os.Bundle; -import android.util.Log; - -import androidx.annotation.CallSuper; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.fragment.app.Fragment; -import androidx.fragment.app.FragmentActivity; - /** - * Base activity for Android apps intending to use Godot as the primary and only screen. + * Base abstract activity for Android apps intending to use Godot as the primary screen. * - * It's also a reference implementation for how to setup and use the {@link Godot} fragment - * within an Android app. + * @deprecated Use {@link GodotActivity} */ -public abstract class FullScreenGodotApp extends FragmentActivity implements GodotHost { - private static final String TAG = FullScreenGodotApp.class.getSimpleName(); - - protected static final String EXTRA_FORCE_QUIT = "force_quit_requested"; - protected static final String EXTRA_NEW_LAUNCH = "new_launch_requested"; - - @Nullable - private Godot godotFragment; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.godot_app_layout); - - handleStartIntent(getIntent(), true); - - Fragment currentFragment = getSupportFragmentManager().findFragmentById(R.id.godot_fragment_container); - if (currentFragment instanceof Godot) { - Log.v(TAG, "Reusing existing Godot fragment instance."); - godotFragment = (Godot)currentFragment; - } else { - Log.v(TAG, "Creating new Godot fragment instance."); - godotFragment = initGodotInstance(); - getSupportFragmentManager().beginTransaction().replace(R.id.godot_fragment_container, godotFragment).setPrimaryNavigationFragment(godotFragment).commitNowAllowingStateLoss(); - } - } - - @Override - public void onDestroy() { - Log.v(TAG, "Destroying Godot app..."); - super.onDestroy(); - terminateGodotInstance(godotFragment); - } - - @Override - public final void onGodotForceQuit(Godot instance) { - runOnUiThread(() -> { - terminateGodotInstance(instance); - }); - } - - private void terminateGodotInstance(Godot instance) { - if (instance == godotFragment) { - Log.v(TAG, "Force quitting Godot instance"); - ProcessPhoenix.forceQuit(FullScreenGodotApp.this); - } - } - - @Override - public final void onGodotRestartRequested(Godot instance) { - runOnUiThread(() -> { - if (instance == godotFragment) { - // It's very hard to properly de-initialize Godot on Android to restart the game - // from scratch. Therefore, we need to kill the whole app process and relaunch it. - // - // Restarting only the activity, wouldn't be enough unless it did proper cleanup (including - // releasing and reloading native libs or resetting their state somehow and clearing static data). - Log.v(TAG, "Restarting Godot instance..."); - ProcessPhoenix.triggerRebirth(FullScreenGodotApp.this); - } - }); - } - - @Override - public void onNewIntent(Intent intent) { - super.onNewIntent(intent); - setIntent(intent); - - handleStartIntent(intent, false); - - if (godotFragment != null) { - godotFragment.onNewIntent(intent); - } - } - - private void handleStartIntent(Intent intent, boolean newLaunch) { - boolean forceQuitRequested = intent.getBooleanExtra(EXTRA_FORCE_QUIT, false); - if (forceQuitRequested) { - Log.d(TAG, "Force quit requested, terminating.."); - ProcessPhoenix.forceQuit(this); - return; - } - - if (!newLaunch) { - boolean newLaunchRequested = intent.getBooleanExtra(EXTRA_NEW_LAUNCH, false); - if (newLaunchRequested) { - Log.d(TAG, "New launch requested, restarting.."); - - Intent restartIntent = new Intent(intent).putExtra(EXTRA_NEW_LAUNCH, false); - ProcessPhoenix.triggerRebirth(this, restartIntent); - return; - } - } - } - - @CallSuper - @Override - public void onActivityResult(int requestCode, int resultCode, Intent data) { - super.onActivityResult(requestCode, resultCode, data); - if (godotFragment != null) { - godotFragment.onActivityResult(requestCode, resultCode, data); - } - } - - @CallSuper - @Override - public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { - super.onRequestPermissionsResult(requestCode, permissions, grantResults); - if (godotFragment != null) { - godotFragment.onRequestPermissionsResult(requestCode, permissions, grantResults); - } - } - - @Override - public void onBackPressed() { - if (godotFragment != null) { - godotFragment.onBackPressed(); - } else { - super.onBackPressed(); - } - } - - /** - * Used to initialize the Godot fragment instance in {@link FullScreenGodotApp#onCreate(Bundle)}. - */ - @NonNull - protected Godot initGodotInstance() { - return new Godot(); - } - - @Nullable - protected final Godot getGodotFragment() { - return godotFragment; - } -} +@Deprecated +public abstract class FullScreenGodotApp extends GodotActivity {} diff --git a/platform/android/java/lib/src/org/godotengine/godot/Godot.java b/platform/android/java/lib/src/org/godotengine/godot/Godot.java deleted file mode 100644 index 9f2dec7317..0000000000 --- a/platform/android/java/lib/src/org/godotengine/godot/Godot.java +++ /dev/null @@ -1,1195 +0,0 @@ -/**************************************************************************/ -/* Godot.java */ -/**************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/**************************************************************************/ -/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ -/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/**************************************************************************/ - -package org.godotengine.godot; - -import static android.content.Context.MODE_PRIVATE; -import static android.content.Context.WINDOW_SERVICE; - -import org.godotengine.godot.input.GodotEditText; -import org.godotengine.godot.io.directory.DirectoryAccessHandler; -import org.godotengine.godot.io.file.FileAccessHandler; -import org.godotengine.godot.plugin.GodotPlugin; -import org.godotengine.godot.plugin.GodotPluginRegistry; -import org.godotengine.godot.tts.GodotTTS; -import org.godotengine.godot.utils.BenchmarkUtils; -import org.godotengine.godot.utils.GodotNetUtils; -import org.godotengine.godot.utils.PermissionsUtil; -import org.godotengine.godot.xr.XRMode; - -import android.annotation.SuppressLint; -import android.app.Activity; -import android.app.ActivityManager; -import android.app.AlertDialog; -import android.app.PendingIntent; -import android.content.ClipData; -import android.content.ClipboardManager; -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.content.SharedPreferences.Editor; -import android.content.pm.ConfigurationInfo; -import android.content.pm.PackageManager; -import android.content.pm.PackageManager.NameNotFoundException; -import android.content.res.Resources; -import android.graphics.Point; -import android.graphics.Rect; -import android.hardware.Sensor; -import android.hardware.SensorEvent; -import android.hardware.SensorEventListener; -import android.hardware.SensorManager; -import android.os.Build; -import android.os.Bundle; -import android.os.Environment; -import android.os.Messenger; -import android.os.VibrationEffect; -import android.os.Vibrator; -import android.util.Log; -import android.view.Display; -import android.view.LayoutInflater; -import android.view.Surface; -import android.view.SurfaceView; -import android.view.View; -import android.view.ViewGroup; -import android.view.ViewGroup.LayoutParams; -import android.view.ViewTreeObserver; -import android.view.Window; -import android.view.WindowInsets; -import android.view.WindowInsetsAnimation; -import android.view.WindowManager; -import android.widget.Button; -import android.widget.FrameLayout; -import android.widget.ProgressBar; -import android.widget.TextView; -import android.widget.Toast; - -import androidx.annotation.CallSuper; -import androidx.annotation.Keep; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.StringRes; -import androidx.fragment.app.Fragment; - -import com.google.android.vending.expansion.downloader.DownloadProgressInfo; -import com.google.android.vending.expansion.downloader.DownloaderClientMarshaller; -import com.google.android.vending.expansion.downloader.DownloaderServiceMarshaller; -import com.google.android.vending.expansion.downloader.Helpers; -import com.google.android.vending.expansion.downloader.IDownloaderClient; -import com.google.android.vending.expansion.downloader.IDownloaderService; -import com.google.android.vending.expansion.downloader.IStub; - -import java.io.File; -import java.io.FileInputStream; -import java.io.InputStream; -import java.security.MessageDigest; -import java.util.Arrays; -import java.util.LinkedList; -import java.util.List; -import java.util.Locale; - -public class Godot extends Fragment implements SensorEventListener, IDownloaderClient { - private static final String TAG = Godot.class.getSimpleName(); - - private IStub mDownloaderClientStub; - private TextView mStatusText; - private TextView mProgressFraction; - private TextView mProgressPercent; - private TextView mAverageSpeed; - private TextView mTimeRemaining; - private ProgressBar mPB; - private ClipboardManager mClipboard; - - private View mDashboard; - private View mCellMessage; - - private Button mPauseButton; - private Button mWiFiSettingsButton; - - private XRMode xrMode = XRMode.REGULAR; - private boolean use_immersive = false; - private boolean use_debug_opengl = false; - private boolean mStatePaused; - private boolean activityResumed; - private int mState; - - private GodotHost godotHost; - private GodotPluginRegistry pluginRegistry; - - static private Intent mCurrentIntent; - - public void onNewIntent(Intent intent) { - mCurrentIntent = intent; - } - - static public Intent getCurrentIntent() { - return mCurrentIntent; - } - - private void setState(int newState) { - if (mState != newState) { - mState = newState; - mStatusText.setText(Helpers.getDownloaderStringResourceIDFromState(newState)); - } - } - - private void setButtonPausedState(boolean paused) { - mStatePaused = paused; - int stringResourceID = paused ? R.string.text_button_resume : R.string.text_button_pause; - mPauseButton.setText(stringResourceID); - } - - private String[] command_line; - private boolean use_apk_expansion; - - private ViewGroup containerLayout; - public GodotRenderView mRenderView; - private boolean godot_initialized = false; - - private SensorManager mSensorManager; - private Sensor mAccelerometer; - private Sensor mGravity; - private Sensor mMagnetometer; - private Sensor mGyroscope; - - public GodotIO io; - public GodotNetUtils netUtils; - public GodotTTS tts; - private DirectoryAccessHandler directoryAccessHandler; - private FileAccessHandler fileAccessHandler; - - public interface ResultCallback { - void callback(int requestCode, int resultCode, Intent data); - } - public ResultCallback result_callback; - - @Override - public void onAttach(Context context) { - super.onAttach(context); - if (getParentFragment() instanceof GodotHost) { - godotHost = (GodotHost)getParentFragment(); - } else if (getActivity() instanceof GodotHost) { - godotHost = (GodotHost)getActivity(); - } - } - - @Override - public void onDetach() { - super.onDetach(); - godotHost = null; - } - - @CallSuper - @Override - public void onActivityResult(int requestCode, int resultCode, Intent data) { - super.onActivityResult(requestCode, resultCode, data); - if (result_callback != null) { - result_callback.callback(requestCode, resultCode, data); - result_callback = null; - } - - for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) { - plugin.onMainActivityResult(requestCode, resultCode, data); - } - } - - @CallSuper - @Override - public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { - super.onRequestPermissionsResult(requestCode, permissions, grantResults); - for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) { - plugin.onMainRequestPermissionsResult(requestCode, permissions, grantResults); - } - - for (int i = 0; i < permissions.length; i++) { - GodotLib.requestPermissionResult(permissions[i], grantResults[i] == PackageManager.PERMISSION_GRANTED); - } - } - - /** - * Invoked on the render thread when the Godot setup is complete. - */ - @CallSuper - protected void onGodotSetupCompleted() { - for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) { - plugin.onGodotSetupCompleted(); - } - - if (godotHost != null) { - godotHost.onGodotSetupCompleted(); - } - } - - /** - * Invoked on the render thread when the Godot main loop has started. - */ - @CallSuper - protected void onGodotMainLoopStarted() { - for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) { - plugin.onGodotMainLoopStarted(); - } - - if (godotHost != null) { - godotHost.onGodotMainLoopStarted(); - } - } - - /** - * Used by the native code (java_godot_lib_jni.cpp) to complete initialization of the GLSurfaceView view and renderer. - */ - @Keep - private boolean onVideoInit() { - final Activity activity = requireActivity(); - containerLayout = new FrameLayout(activity); - containerLayout.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); - - // GodotEditText layout - GodotEditText editText = new GodotEditText(activity); - editText.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, - (int)getResources().getDimension(R.dimen.text_edit_height))); - // ...add to FrameLayout - containerLayout.addView(editText); - - tts = new GodotTTS(activity); - - if (!GodotLib.setup(command_line, tts)) { - Log.e(TAG, "Unable to setup the Godot engine! Aborting..."); - alert(R.string.error_engine_setup_message, R.string.text_error_title, this::forceQuit); - return false; - } - - if (usesVulkan()) { - if (!meetsVulkanRequirements(activity.getPackageManager())) { - alert(R.string.error_missing_vulkan_requirements_message, R.string.text_error_title, this::forceQuit); - return false; - } - mRenderView = new GodotVulkanRenderView(activity, this); - } else { - // Fallback to openGl - mRenderView = new GodotGLRenderView(activity, this, xrMode, use_debug_opengl); - } - - View view = mRenderView.getView(); - containerLayout.addView(view, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); - editText.setView(mRenderView); - io.setEdit(editText); - - // Listeners for keyboard height. - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - // Report the height of virtual keyboard as it changes during the animation. - final View decorView = activity.getWindow().getDecorView(); - decorView.setWindowInsetsAnimationCallback(new WindowInsetsAnimation.Callback(WindowInsetsAnimation.Callback.DISPATCH_MODE_STOP) { - int startBottom, endBottom; - @Override - public void onPrepare(@NonNull WindowInsetsAnimation animation) { - startBottom = decorView.getRootWindowInsets().getInsets(WindowInsets.Type.ime()).bottom; - } - - @NonNull - @Override - public WindowInsetsAnimation.Bounds onStart(@NonNull WindowInsetsAnimation animation, @NonNull WindowInsetsAnimation.Bounds bounds) { - endBottom = decorView.getRootWindowInsets().getInsets(WindowInsets.Type.ime()).bottom; - return bounds; - } - - @NonNull - @Override - public WindowInsets onProgress(@NonNull WindowInsets windowInsets, @NonNull List<WindowInsetsAnimation> list) { - // Find the IME animation. - WindowInsetsAnimation imeAnimation = null; - for (WindowInsetsAnimation animation : list) { - if ((animation.getTypeMask() & WindowInsets.Type.ime()) != 0) { - imeAnimation = animation; - break; - } - } - // Update keyboard height based on IME animation. - if (imeAnimation != null) { - float interpolatedFraction = imeAnimation.getInterpolatedFraction(); - // Linear interpolation between start and end values. - float keyboardHeight = startBottom * (1.0f - interpolatedFraction) + endBottom * interpolatedFraction; - GodotLib.setVirtualKeyboardHeight((int)keyboardHeight); - } - return windowInsets; - } - - @Override - public void onEnd(@NonNull WindowInsetsAnimation animation) { - } - }); - } else { - // Infer the virtual keyboard height using visible area. - view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { - // Don't allocate a new Rect every time the callback is called. - final Rect visibleSize = new Rect(); - - @Override - public void onGlobalLayout() { - final SurfaceView view = mRenderView.getView(); - view.getWindowVisibleDisplayFrame(visibleSize); - final int keyboardHeight = view.getHeight() - visibleSize.bottom; - GodotLib.setVirtualKeyboardHeight(keyboardHeight); - } - }); - } - - mRenderView.queueOnRenderThread(() -> { - for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) { - plugin.onRegisterPluginWithGodotNative(); - } - setKeepScreenOn(Boolean.parseBoolean(GodotLib.getGlobal("display/window/energy_saving/keep_screen_on"))); - }); - - // Include the returned non-null views in the Godot view hierarchy. - for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) { - View pluginView = plugin.onMainCreate(activity); - if (pluginView != null) { - if (plugin.shouldBeOnTop()) { - containerLayout.addView(pluginView); - } else { - containerLayout.addView(pluginView, 0); - } - } - } - return true; - } - - /** - * Returns true if `Vulkan` is used for rendering. - */ - private boolean usesVulkan() { - final String renderer = GodotLib.getGlobal("rendering/renderer/rendering_method"); - final String renderingDevice = GodotLib.getGlobal("rendering/rendering_device/driver"); - return ("forward_plus".equals(renderer) || "mobile".equals(renderer)) && "vulkan".equals(renderingDevice); - } - - /** - * Returns true if the device meets the base requirements for Vulkan support, false otherwise. - */ - private boolean meetsVulkanRequirements(@Nullable PackageManager packageManager) { - if (packageManager == null) { - return false; - } - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - if (!packageManager.hasSystemFeature(PackageManager.FEATURE_VULKAN_HARDWARE_LEVEL, 1)) { - // Optional requirements.. log as warning if missing - Log.w(TAG, "The vulkan hardware level does not meet the minimum requirement: 1"); - } - - // Check for api version 1.0 - return packageManager.hasSystemFeature(PackageManager.FEATURE_VULKAN_HARDWARE_VERSION, 0x400003); - } - - return false; - } - - public void setKeepScreenOn(final boolean p_enabled) { - runOnUiThread(() -> { - if (p_enabled) { - getActivity().getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); - } else { - getActivity().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); - } - }); - } - - /** - * Used by the native code (java_godot_wrapper.h) to vibrate the device. - * @param durationMs - */ - @SuppressLint("MissingPermission") - @Keep - private void vibrate(int durationMs) { - if (durationMs > 0 && requestPermission("VIBRATE")) { - Vibrator v = (Vibrator)getContext().getSystemService(Context.VIBRATOR_SERVICE); - if (v != null) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - v.vibrate(VibrationEffect.createOneShot(durationMs, VibrationEffect.DEFAULT_AMPLITUDE)); - } else { - // deprecated in API 26 - v.vibrate(durationMs); - } - } - } - } - - public void restart() { - if (godotHost != null) { - godotHost.onGodotRestartRequested(this); - } - } - - public void alert(final String message, final String title) { - alert(message, title, null); - } - - private void alert(@StringRes int messageResId, @StringRes int titleResId, @Nullable Runnable okCallback) { - Resources res = getResources(); - alert(res.getString(messageResId), res.getString(titleResId), okCallback); - } - - private void alert(final String message, final String title, @Nullable Runnable okCallback) { - final Activity activity = getActivity(); - runOnUiThread(() -> { - AlertDialog.Builder builder = new AlertDialog.Builder(activity); - builder.setMessage(message).setTitle(title); - builder.setPositiveButton( - "OK", - (dialog, id) -> { - if (okCallback != null) { - okCallback.run(); - } - dialog.cancel(); - }); - AlertDialog dialog = builder.create(); - dialog.show(); - }); - } - - public int getGLESVersionCode() { - ActivityManager am = (ActivityManager)getContext().getSystemService(Context.ACTIVITY_SERVICE); - ConfigurationInfo deviceInfo = am.getDeviceConfigurationInfo(); - return deviceInfo.reqGlEsVersion; - } - - @CallSuper - protected String[] getCommandLine() { - String[] original = parseCommandLine(); - String[] updated; - List<String> hostCommandLine = godotHost != null ? godotHost.getCommandLine() : null; - if (hostCommandLine == null || hostCommandLine.isEmpty()) { - updated = original; - } else { - updated = Arrays.copyOf(original, original.length + hostCommandLine.size()); - for (int i = 0; i < hostCommandLine.size(); i++) { - updated[original.length + i] = hostCommandLine.get(i); - } - } - return updated; - } - - private String[] parseCommandLine() { - InputStream is; - try { - is = getActivity().getAssets().open("_cl_"); - byte[] len = new byte[4]; - int r = is.read(len); - if (r < 4) { - return new String[0]; - } - int argc = ((int)(len[3] & 0xFF) << 24) | ((int)(len[2] & 0xFF) << 16) | ((int)(len[1] & 0xFF) << 8) | ((int)(len[0] & 0xFF)); - String[] cmdline = new String[argc]; - - for (int i = 0; i < argc; i++) { - r = is.read(len); - if (r < 4) { - return new String[0]; - } - int strlen = ((int)(len[3] & 0xFF) << 24) | ((int)(len[2] & 0xFF) << 16) | ((int)(len[1] & 0xFF) << 8) | ((int)(len[0] & 0xFF)); - if (strlen > 65535) { - return new String[0]; - } - byte[] arg = new byte[strlen]; - r = is.read(arg); - if (r == strlen) { - cmdline[i] = new String(arg, "UTF-8"); - } - } - return cmdline; - } catch (Exception e) { - // The _cl_ file can be missing with no adverse effect - return new String[0]; - } - } - - /** - * Used by the native code (java_godot_wrapper.h) to check whether the activity is resumed or paused. - */ - @Keep - private boolean isActivityResumed() { - return activityResumed; - } - - /** - * Used by the native code (java_godot_wrapper.h) to access the Android surface. - */ - @Keep - private Surface getSurface() { - return mRenderView.getView().getHolder().getSurface(); - } - - /** - * Used by the native code (java_godot_wrapper.h) to access the input fallback mapping. - * @return The input fallback mapping for the current XR mode. - */ - @Keep - private String getInputFallbackMapping() { - return xrMode.inputFallbackMapping; - } - - String expansion_pack_path; - - private void initializeGodot() { - if (expansion_pack_path != null) { - String[] new_cmdline; - int cll = 0; - if (command_line != null) { - new_cmdline = new String[command_line.length + 2]; - cll = command_line.length; - for (int i = 0; i < command_line.length; i++) { - new_cmdline[i] = command_line[i]; - } - } else { - new_cmdline = new String[2]; - } - - new_cmdline[cll] = "--main-pack"; - new_cmdline[cll + 1] = expansion_pack_path; - command_line = new_cmdline; - } - - final Activity activity = getActivity(); - io = new GodotIO(activity); - netUtils = new GodotNetUtils(activity); - Context context = getContext(); - directoryAccessHandler = new DirectoryAccessHandler(context); - fileAccessHandler = new FileAccessHandler(context); - mSensorManager = (SensorManager)activity.getSystemService(Context.SENSOR_SERVICE); - mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); - mGravity = mSensorManager.getDefaultSensor(Sensor.TYPE_GRAVITY); - mMagnetometer = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD); - mGyroscope = mSensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE); - - godot_initialized = GodotLib.initialize(activity, - this, - activity.getAssets(), - io, - netUtils, - directoryAccessHandler, - fileAccessHandler, - use_apk_expansion); - - result_callback = null; - } - - @Override - public void onServiceConnected(Messenger m) { - IDownloaderService remoteService = DownloaderServiceMarshaller.CreateProxy(m); - remoteService.onClientUpdated(mDownloaderClientStub.getMessenger()); - } - - @Override - public void onCreate(Bundle icicle) { - BenchmarkUtils.beginBenchmarkMeasure("Godot::onCreate"); - super.onCreate(icicle); - - final Activity activity = getActivity(); - Window window = activity.getWindow(); - window.addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON); - mClipboard = (ClipboardManager)activity.getSystemService(Context.CLIPBOARD_SERVICE); - pluginRegistry = GodotPluginRegistry.initializePluginRegistry(this); - - // check for apk expansion API - boolean md5mismatch = false; - command_line = getCommandLine(); - String main_pack_md5 = null; - String main_pack_key = null; - - List<String> new_args = new LinkedList<>(); - - for (int i = 0; i < command_line.length; i++) { - boolean has_extra = i < command_line.length - 1; - if (command_line[i].equals(XRMode.REGULAR.cmdLineArg)) { - xrMode = XRMode.REGULAR; - } else if (command_line[i].equals(XRMode.OPENXR.cmdLineArg)) { - xrMode = XRMode.OPENXR; - } else if (command_line[i].equals("--debug_opengl")) { - use_debug_opengl = true; - } else if (command_line[i].equals("--use_immersive")) { - use_immersive = true; - window.getDecorView().setSystemUiVisibility( - View.SYSTEM_UI_FLAG_LAYOUT_STABLE | - View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | - View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | - View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | // hide nav bar - View.SYSTEM_UI_FLAG_FULLSCREEN | // hide status bar - View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); - UiChangeListener(); - } else if (command_line[i].equals("--use_apk_expansion")) { - use_apk_expansion = true; - } else if (has_extra && command_line[i].equals("--apk_expansion_md5")) { - main_pack_md5 = command_line[i + 1]; - i++; - } else if (has_extra && command_line[i].equals("--apk_expansion_key")) { - main_pack_key = command_line[i + 1]; - SharedPreferences prefs = activity.getSharedPreferences("app_data_keys", - MODE_PRIVATE); - Editor editor = prefs.edit(); - editor.putString("store_public_key", main_pack_key); - - editor.apply(); - i++; - } else if (command_line[i].equals("--benchmark")) { - BenchmarkUtils.setUseBenchmark(true); - new_args.add(command_line[i]); - } else if (has_extra && command_line[i].equals("--benchmark-file")) { - BenchmarkUtils.setUseBenchmark(true); - new_args.add(command_line[i]); - - // Retrieve the filepath - BenchmarkUtils.setBenchmarkFile(command_line[i + 1]); - new_args.add(command_line[i + 1]); - - i++; - } else if (command_line[i].trim().length() != 0) { - new_args.add(command_line[i]); - } - } - - if (new_args.isEmpty()) { - command_line = null; - } else { - command_line = new_args.toArray(new String[new_args.size()]); - } - if (use_apk_expansion && main_pack_md5 != null && main_pack_key != null) { - // check that environment is ok! - if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { - // show popup and die - } - - // Build the full path to the app's expansion files - try { - expansion_pack_path = Helpers.getSaveFilePath(getContext()); - expansion_pack_path += "/main." + activity.getPackageManager().getPackageInfo(activity.getPackageName(), 0).versionCode + "." + activity.getPackageName() + ".obb"; - } catch (Exception e) { - e.printStackTrace(); - } - - File f = new File(expansion_pack_path); - - boolean pack_valid = true; - - if (!f.exists()) { - pack_valid = false; - - } else if (obbIsCorrupted(expansion_pack_path, main_pack_md5)) { - pack_valid = false; - try { - f.delete(); - } catch (Exception e) { - } - } - - if (!pack_valid) { - Intent notifierIntent = new Intent(activity, activity.getClass()); - notifierIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); - - PendingIntent pendingIntent; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - pendingIntent = PendingIntent.getActivity(activity, 0, - notifierIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); - } else { - pendingIntent = PendingIntent.getActivity(activity, 0, - notifierIntent, PendingIntent.FLAG_UPDATE_CURRENT); - } - - int startResult; - try { - startResult = DownloaderClientMarshaller.startDownloadServiceIfRequired( - getContext(), - pendingIntent, - GodotDownloaderService.class); - - if (startResult != DownloaderClientMarshaller.NO_DOWNLOAD_REQUIRED) { - // This is where you do set up to display the download - // progress (next step in onCreateView) - mDownloaderClientStub = DownloaderClientMarshaller.CreateStub(this, - GodotDownloaderService.class); - - return; - } - } catch (NameNotFoundException e) { - // TODO Auto-generated catch block - } - } - } - - mCurrentIntent = activity.getIntent(); - - initializeGodot(); - BenchmarkUtils.endBenchmarkMeasure("Godot::onCreate"); - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle icicle) { - if (mDownloaderClientStub != null) { - View downloadingExpansionView = - inflater.inflate(R.layout.downloading_expansion, container, false); - mPB = (ProgressBar)downloadingExpansionView.findViewById(R.id.progressBar); - mStatusText = (TextView)downloadingExpansionView.findViewById(R.id.statusText); - mProgressFraction = (TextView)downloadingExpansionView.findViewById(R.id.progressAsFraction); - mProgressPercent = (TextView)downloadingExpansionView.findViewById(R.id.progressAsPercentage); - mAverageSpeed = (TextView)downloadingExpansionView.findViewById(R.id.progressAverageSpeed); - mTimeRemaining = (TextView)downloadingExpansionView.findViewById(R.id.progressTimeRemaining); - mDashboard = downloadingExpansionView.findViewById(R.id.downloaderDashboard); - mCellMessage = downloadingExpansionView.findViewById(R.id.approveCellular); - mPauseButton = (Button)downloadingExpansionView.findViewById(R.id.pauseButton); - mWiFiSettingsButton = (Button)downloadingExpansionView.findViewById(R.id.wifiSettingsButton); - - return downloadingExpansionView; - } - - return containerLayout; - } - - @Override - public void onDestroy() { - for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) { - plugin.onMainDestroy(); - } - - GodotLib.ondestroy(); - - super.onDestroy(); - - forceQuit(); - } - - @Override - public void onPause() { - super.onPause(); - activityResumed = false; - - if (!godot_initialized) { - if (null != mDownloaderClientStub) { - mDownloaderClientStub.disconnect(getActivity()); - } - return; - } - mRenderView.onActivityPaused(); - - mSensorManager.unregisterListener(this); - - for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) { - plugin.onMainPause(); - } - } - - public boolean hasClipboard() { - return mClipboard.hasPrimaryClip(); - } - - public String getClipboard() { - ClipData clipData = mClipboard.getPrimaryClip(); - if (clipData == null) - return ""; - CharSequence text = clipData.getItemAt(0).getText(); - if (text == null) - return ""; - return text.toString(); - } - - public void setClipboard(String p_text) { - ClipData clip = ClipData.newPlainText("myLabel", p_text); - mClipboard.setPrimaryClip(clip); - } - - @Override - public void onResume() { - super.onResume(); - activityResumed = true; - if (!godot_initialized) { - if (null != mDownloaderClientStub) { - mDownloaderClientStub.connect(getActivity()); - } - return; - } - - mRenderView.onActivityResumed(); - - mSensorManager.registerListener(this, mAccelerometer, SensorManager.SENSOR_DELAY_GAME); - mSensorManager.registerListener(this, mGravity, SensorManager.SENSOR_DELAY_GAME); - mSensorManager.registerListener(this, mMagnetometer, SensorManager.SENSOR_DELAY_GAME); - mSensorManager.registerListener(this, mGyroscope, SensorManager.SENSOR_DELAY_GAME); - - if (use_immersive) { - Window window = getActivity().getWindow(); - window.getDecorView().setSystemUiVisibility( - View.SYSTEM_UI_FLAG_LAYOUT_STABLE | - View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | - View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | - View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | // hide nav bar - View.SYSTEM_UI_FLAG_FULLSCREEN | // hide status bar - View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); - } - - for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) { - plugin.onMainResume(); - } - } - - public void UiChangeListener() { - final View decorView = getActivity().getWindow().getDecorView(); - decorView.setOnSystemUiVisibilityChangeListener(visibility -> { - if ((visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0) { - decorView.setSystemUiVisibility( - View.SYSTEM_UI_FLAG_LAYOUT_STABLE | - View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | - View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | - View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | - View.SYSTEM_UI_FLAG_FULLSCREEN | - View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); - } - }); - } - - public float[] getRotatedValues(float values[]) { - if (values == null || values.length != 3) { - return values; - } - - Display display = - ((WindowManager)getActivity().getSystemService(WINDOW_SERVICE)).getDefaultDisplay(); - int displayRotation = display.getRotation(); - - float[] rotatedValues = new float[3]; - switch (displayRotation) { - case Surface.ROTATION_0: - rotatedValues[0] = values[0]; - rotatedValues[1] = values[1]; - rotatedValues[2] = values[2]; - break; - case Surface.ROTATION_90: - rotatedValues[0] = -values[1]; - rotatedValues[1] = values[0]; - rotatedValues[2] = values[2]; - break; - case Surface.ROTATION_180: - rotatedValues[0] = -values[0]; - rotatedValues[1] = -values[1]; - rotatedValues[2] = values[2]; - break; - case Surface.ROTATION_270: - rotatedValues[0] = values[1]; - rotatedValues[1] = -values[0]; - rotatedValues[2] = values[2]; - break; - } - - return rotatedValues; - } - - @Override - public void onSensorChanged(SensorEvent event) { - if (mRenderView == null) { - return; - } - - final int typeOfSensor = event.sensor.getType(); - switch (typeOfSensor) { - case Sensor.TYPE_ACCELEROMETER: { - float[] rotatedValues = getRotatedValues(event.values); - mRenderView.queueOnRenderThread(() -> { - GodotLib.accelerometer(-rotatedValues[0], -rotatedValues[1], -rotatedValues[2]); - }); - break; - } - case Sensor.TYPE_GRAVITY: { - float[] rotatedValues = getRotatedValues(event.values); - mRenderView.queueOnRenderThread(() -> { - GodotLib.gravity(-rotatedValues[0], -rotatedValues[1], -rotatedValues[2]); - }); - break; - } - case Sensor.TYPE_MAGNETIC_FIELD: { - float[] rotatedValues = getRotatedValues(event.values); - mRenderView.queueOnRenderThread(() -> { - GodotLib.magnetometer(-rotatedValues[0], -rotatedValues[1], -rotatedValues[2]); - }); - break; - } - case Sensor.TYPE_GYROSCOPE: { - float[] rotatedValues = getRotatedValues(event.values); - mRenderView.queueOnRenderThread(() -> { - GodotLib.gyroscope(rotatedValues[0], rotatedValues[1], rotatedValues[2]); - }); - break; - } - } - } - - @Override - public final void onAccuracyChanged(Sensor sensor, int accuracy) { - // Do something here if sensor accuracy changes. - } - - public void onBackPressed() { - boolean shouldQuit = true; - - for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) { - if (plugin.onMainBackPressed()) { - shouldQuit = false; - } - } - - if (shouldQuit && mRenderView != null) { - mRenderView.queueOnRenderThread(GodotLib::back); - } - } - - /** - * Queue a runnable to be run on the render thread. - * <p> - * This must be called after the render thread has started. - */ - public final void runOnRenderThread(@NonNull Runnable action) { - if (mRenderView != null) { - mRenderView.queueOnRenderThread(action); - } - } - - public final void runOnUiThread(@NonNull Runnable action) { - if (getActivity() != null) { - getActivity().runOnUiThread(action); - } - } - - private void forceQuit() { - // TODO: This is a temp solution. The proper fix will involve tracking down and properly shutting down each - // native Godot components that is started in Godot#onVideoInit. - forceQuit(0); - } - - @Keep - private boolean forceQuit(int instanceId) { - if (godotHost == null) { - return false; - } - if (instanceId == 0) { - godotHost.onGodotForceQuit(this); - return true; - } else { - return godotHost.onGodotForceQuit(instanceId); - } - } - - private boolean obbIsCorrupted(String f, String main_pack_md5) { - try { - InputStream fis = new FileInputStream(f); - - // Create MD5 Hash - byte[] buffer = new byte[16384]; - - MessageDigest complete = MessageDigest.getInstance("MD5"); - int numRead; - do { - numRead = fis.read(buffer); - if (numRead > 0) { - complete.update(buffer, 0, numRead); - } - } while (numRead != -1); - - fis.close(); - byte[] messageDigest = complete.digest(); - - // Create Hex String - StringBuilder hexString = new StringBuilder(); - for (byte b : messageDigest) { - String s = Integer.toHexString(0xFF & b); - if (s.length() == 1) { - s = "0" + s; - } - hexString.append(s); - } - String md5str = hexString.toString(); - - if (!md5str.equals(main_pack_md5)) { - return true; - } - return false; - } catch (Exception e) { - e.printStackTrace(); - return true; - } - } - - public boolean requestPermission(String p_name) { - return PermissionsUtil.requestPermission(p_name, getActivity()); - } - - public boolean requestPermissions() { - return PermissionsUtil.requestManifestPermissions(getActivity()); - } - - public String[] getGrantedPermissions() { - return PermissionsUtil.getGrantedPermissions(getActivity()); - } - - @Keep - private String getCACertificates() { - return GodotNetUtils.getCACertificates(); - } - - /** - * The download state should trigger changes in the UI --- it may be useful - * to show the state as being indeterminate at times. This sample can be - * considered a guideline. - */ - @Override - public void onDownloadStateChanged(int newState) { - setState(newState); - boolean showDashboard = true; - boolean showCellMessage = false; - boolean paused; - boolean indeterminate; - switch (newState) { - case IDownloaderClient.STATE_IDLE: - // STATE_IDLE means the service is listening, so it's - // safe to start making remote service calls. - paused = false; - indeterminate = true; - break; - case IDownloaderClient.STATE_CONNECTING: - case IDownloaderClient.STATE_FETCHING_URL: - showDashboard = true; - paused = false; - indeterminate = true; - break; - case IDownloaderClient.STATE_DOWNLOADING: - paused = false; - showDashboard = true; - indeterminate = false; - break; - - case IDownloaderClient.STATE_FAILED_CANCELED: - case IDownloaderClient.STATE_FAILED: - case IDownloaderClient.STATE_FAILED_FETCHING_URL: - case IDownloaderClient.STATE_FAILED_UNLICENSED: - paused = true; - showDashboard = false; - indeterminate = false; - break; - case IDownloaderClient.STATE_PAUSED_NEED_CELLULAR_PERMISSION: - case IDownloaderClient.STATE_PAUSED_WIFI_DISABLED_NEED_CELLULAR_PERMISSION: - showDashboard = false; - paused = true; - indeterminate = false; - showCellMessage = true; - break; - - case IDownloaderClient.STATE_PAUSED_BY_REQUEST: - paused = true; - indeterminate = false; - break; - case IDownloaderClient.STATE_PAUSED_ROAMING: - case IDownloaderClient.STATE_PAUSED_SDCARD_UNAVAILABLE: - paused = true; - indeterminate = false; - break; - case IDownloaderClient.STATE_COMPLETED: - showDashboard = false; - paused = false; - indeterminate = false; - initializeGodot(); - return; - default: - paused = true; - indeterminate = true; - showDashboard = true; - } - int newDashboardVisibility = showDashboard ? View.VISIBLE : View.GONE; - if (mDashboard.getVisibility() != newDashboardVisibility) { - mDashboard.setVisibility(newDashboardVisibility); - } - int cellMessageVisibility = showCellMessage ? View.VISIBLE : View.GONE; - if (mCellMessage.getVisibility() != cellMessageVisibility) { - mCellMessage.setVisibility(cellMessageVisibility); - } - - mPB.setIndeterminate(indeterminate); - setButtonPausedState(paused); - } - - @Override - public void onDownloadProgress(DownloadProgressInfo progress) { - mAverageSpeed.setText(getString(R.string.kilobytes_per_second, - Helpers.getSpeedString(progress.mCurrentSpeed))); - mTimeRemaining.setText(getString(R.string.time_remaining, - Helpers.getTimeRemaining(progress.mTimeRemaining))); - - mPB.setMax((int)(progress.mOverallTotal >> 8)); - mPB.setProgress((int)(progress.mOverallProgress >> 8)); - mProgressPercent.setText(String.format(Locale.ENGLISH, "%d %%", progress.mOverallProgress * 100 / progress.mOverallTotal)); - mProgressFraction.setText(Helpers.getDownloadProgressString(progress.mOverallProgress, - progress.mOverallTotal)); - } - - public void initInputDevices() { - mRenderView.initInputDevices(); - } - - @Keep - public GodotRenderView getRenderView() { // used by native side to get renderView - return mRenderView; - } - - @Keep - public DirectoryAccessHandler getDirectoryAccessHandler() { - return directoryAccessHandler; - } - - @Keep - public FileAccessHandler getFileAccessHandler() { - return fileAccessHandler; - } - - @Keep - private int createNewGodotInstance(String[] args) { - if (godotHost != null) { - return godotHost.onNewGodotInstanceRequested(args); - } - return 0; - } - - @Keep - private void beginBenchmarkMeasure(String label) { - BenchmarkUtils.beginBenchmarkMeasure(label); - } - - @Keep - private void endBenchmarkMeasure(String label) { - BenchmarkUtils.endBenchmarkMeasure(label); - } - - @Keep - private void dumpBenchmark(String benchmarkFile) { - BenchmarkUtils.dumpBenchmark(fileAccessHandler, benchmarkFile); - } -} diff --git a/platform/android/java/lib/src/org/godotengine/godot/Godot.kt b/platform/android/java/lib/src/org/godotengine/godot/Godot.kt new file mode 100644 index 0000000000..23de01a191 --- /dev/null +++ b/platform/android/java/lib/src/org/godotengine/godot/Godot.kt @@ -0,0 +1,965 @@ +/**************************************************************************/ +/* Godot.kt */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +package org.godotengine.godot + +import android.annotation.SuppressLint +import android.app.Activity +import android.app.AlertDialog +import android.content.* +import android.content.pm.PackageManager +import android.content.res.Resources +import android.graphics.Rect +import android.hardware.Sensor +import android.hardware.SensorEvent +import android.hardware.SensorEventListener +import android.hardware.SensorManager +import android.os.* +import android.util.Log +import android.view.* +import android.view.ViewTreeObserver.OnGlobalLayoutListener +import android.widget.FrameLayout +import androidx.annotation.Keep +import androidx.annotation.StringRes +import com.google.android.vending.expansion.downloader.* +import org.godotengine.godot.input.GodotEditText +import org.godotengine.godot.io.directory.DirectoryAccessHandler +import org.godotengine.godot.io.file.FileAccessHandler +import org.godotengine.godot.plugin.GodotPluginRegistry +import org.godotengine.godot.tts.GodotTTS +import org.godotengine.godot.utils.GodotNetUtils +import org.godotengine.godot.utils.PermissionsUtil +import org.godotengine.godot.utils.PermissionsUtil.requestPermission +import org.godotengine.godot.utils.beginBenchmarkMeasure +import org.godotengine.godot.utils.benchmarkFile +import org.godotengine.godot.utils.dumpBenchmark +import org.godotengine.godot.utils.endBenchmarkMeasure +import org.godotengine.godot.utils.useBenchmark +import org.godotengine.godot.xr.XRMode +import java.io.File +import java.io.FileInputStream +import java.io.InputStream +import java.nio.charset.StandardCharsets +import java.security.MessageDigest +import java.util.* + +/** + * Core component used to interface with the native layer of the engine. + * + * Can be hosted by [Activity], [Fragment] or [Service] android components, so long as its + * lifecycle methods are properly invoked. + */ +class Godot(private val context: Context) : SensorEventListener { + + private companion object { + private val TAG = Godot::class.java.simpleName + } + + private val pluginRegistry: GodotPluginRegistry by lazy { + GodotPluginRegistry.initializePluginRegistry(this) + } + private val mSensorManager: SensorManager by lazy { + requireActivity().getSystemService(Context.SENSOR_SERVICE) as SensorManager + } + private val mAccelerometer: Sensor by lazy { + mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER) + } + private val mGravity: Sensor by lazy { + mSensorManager.getDefaultSensor(Sensor.TYPE_GRAVITY) + } + private val mMagnetometer: Sensor by lazy { + mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD) + } + private val mGyroscope: Sensor by lazy { + mSensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE) + } + private val mClipboard: ClipboardManager by lazy { + requireActivity().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager + } + + private val uiChangeListener = View.OnSystemUiVisibilityChangeListener { visibility: Int -> + if (visibility and View.SYSTEM_UI_FLAG_FULLSCREEN == 0) { + val decorView = requireActivity().window.decorView + decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or + View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or + View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or + View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or + View.SYSTEM_UI_FLAG_FULLSCREEN or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY + }} + + val tts = GodotTTS(context) + val directoryAccessHandler = DirectoryAccessHandler(context) + val fileAccessHandler = FileAccessHandler(context) + val netUtils = GodotNetUtils(context) + + /** + * Tracks whether [onCreate] was completed successfully. + */ + private var initializationStarted = false + + /** + * Tracks whether [GodotLib.initialize] was completed successfully. + */ + private var nativeLayerInitializeCompleted = false + + /** + * Tracks whether [GodotLib.setup] was completed successfully. + */ + private var nativeLayerSetupCompleted = false + + /** + * Tracks whether [onInitRenderView] was completed successfully. + */ + private var renderViewInitialized = false + private var primaryHost: GodotHost? = null + + var io: GodotIO? = null + + private var commandLine : MutableList<String> = ArrayList<String>() + private var xrMode = XRMode.REGULAR + private var expansionPackPath: String = "" + private var useApkExpansion = false + private var useImmersive = false + private var useDebugOpengl = false + + private var containerLayout: FrameLayout? = null + var renderView: GodotRenderView? = null + + /** + * Returns true if the native engine has been initialized through [onInitNativeLayer], false otherwise. + */ + private fun isNativeInitialized() = nativeLayerInitializeCompleted && nativeLayerSetupCompleted + + /** + * Returns true if the engine has been initialized, false otherwise. + */ + fun isInitialized() = initializationStarted && isNativeInitialized() && renderViewInitialized + + /** + * Provides access to the primary host [Activity] + */ + fun getActivity() = primaryHost?.activity + private fun requireActivity() = getActivity() ?: throw IllegalStateException("Host activity must be non-null") + + /** + * Start initialization of the Godot engine. + * + * This must be followed by [onInitNativeLayer] and [onInitRenderView] in that order to complete + * initialization of the engine. + * + * @throws IllegalArgumentException exception if the specified expansion pack (if any) + * is invalid. + */ + fun onCreate(primaryHost: GodotHost) { + if (this.primaryHost != null || initializationStarted) { + Log.d(TAG, "OnCreate already invoked") + return + } + + beginBenchmarkMeasure("Godot::onCreate") + try { + this.primaryHost = primaryHost + val activity = requireActivity() + val window = activity.window + window.addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON) + GodotPluginRegistry.initializePluginRegistry(this) + if (io == null) { + io = GodotIO(activity) + } + + // check for apk expansion API + commandLine = getCommandLine() + var mainPackMd5: String? = null + var mainPackKey: String? = null + val newArgs: MutableList<String> = ArrayList() + var i = 0 + while (i < commandLine.size) { + val hasExtra: Boolean = i < commandLine.size - 1 + if (commandLine[i] == XRMode.REGULAR.cmdLineArg) { + xrMode = XRMode.REGULAR + } else if (commandLine[i] == XRMode.OPENXR.cmdLineArg) { + xrMode = XRMode.OPENXR + } else if (commandLine[i] == "--debug_opengl") { + useDebugOpengl = true + } else if (commandLine[i] == "--use_immersive") { + useImmersive = true + window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or + View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or + View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or + View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or // hide nav bar + View.SYSTEM_UI_FLAG_FULLSCREEN or // hide status bar + View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY + registerUiChangeListener() + } else if (commandLine[i] == "--use_apk_expansion") { + useApkExpansion = true + } else if (hasExtra && commandLine[i] == "--apk_expansion_md5") { + mainPackMd5 = commandLine[i + 1] + i++ + } else if (hasExtra && commandLine[i] == "--apk_expansion_key") { + mainPackKey = commandLine[i + 1] + val prefs = activity.getSharedPreferences( + "app_data_keys", + Context.MODE_PRIVATE + ) + val editor = prefs.edit() + editor.putString("store_public_key", mainPackKey) + editor.apply() + i++ + } else if (commandLine[i] == "--benchmark") { + useBenchmark = true + newArgs.add(commandLine[i]) + } else if (hasExtra && commandLine[i] == "--benchmark-file") { + useBenchmark = true + newArgs.add(commandLine[i]) + + // Retrieve the filepath + benchmarkFile = commandLine[i + 1] + newArgs.add(commandLine[i + 1]) + + i++ + } else if (commandLine[i].trim().isNotEmpty()) { + newArgs.add(commandLine[i]) + } + i++ + } + if (newArgs.isEmpty()) { + commandLine = mutableListOf() + } else { + commandLine = newArgs + } + if (useApkExpansion && mainPackMd5 != null && mainPackKey != null) { + // Build the full path to the app's expansion files + try { + expansionPackPath = Helpers.getSaveFilePath(context) + expansionPackPath += "/main." + activity.packageManager.getPackageInfo( + activity.packageName, + 0 + ).versionCode + "." + activity.packageName + ".obb" + } catch (e: java.lang.Exception) { + Log.e(TAG, "Unable to build full path to the app's expansion files", e) + } + val f = File(expansionPackPath) + var packValid = true + if (!f.exists()) { + packValid = false + } else if (obbIsCorrupted(expansionPackPath, mainPackMd5)) { + packValid = false + try { + f.delete() + } catch (_: java.lang.Exception) { + } + } + if (!packValid) { + // Aborting engine initialization + throw IllegalArgumentException("Invalid expansion pack") + } + } + + initializationStarted = true + } catch (e: java.lang.Exception) { + // Clear the primary host and rethrow + this.primaryHost = null + initializationStarted = false + throw e + } finally { + endBenchmarkMeasure("Godot::onCreate"); + } + } + + /** + * Initializes the native layer of the Godot engine. + * + * This must be preceded by [onCreate] and followed by [onInitRenderView] to complete + * initialization of the engine. + * + * @return false if initialization of the native layer fails, true otherwise. + * + * @throws IllegalStateException if [onCreate] has not been called. + */ + fun onInitNativeLayer(host: GodotHost): Boolean { + if (!initializationStarted) { + throw IllegalStateException("OnCreate must be invoked successfully prior to initializing the native layer") + } + if (isNativeInitialized()) { + Log.d(TAG, "OnInitNativeLayer already invoked") + return true + } + if (host != primaryHost) { + Log.e(TAG, "Native initialization is only supported for the primary host") + return false + } + + if (expansionPackPath.isNotEmpty()) { + commandLine.add("--main-pack") + commandLine.add(expansionPackPath) + } + val activity = requireActivity() + if (!nativeLayerInitializeCompleted) { + nativeLayerInitializeCompleted = GodotLib.initialize( + activity, + this, + activity.assets, + io, + netUtils, + directoryAccessHandler, + fileAccessHandler, + useApkExpansion, + ) + } + + if (nativeLayerInitializeCompleted && !nativeLayerSetupCompleted) { + nativeLayerSetupCompleted = GodotLib.setup(commandLine.toTypedArray(), tts) + if (!nativeLayerSetupCompleted) { + Log.e(TAG, "Unable to setup the Godot engine! Aborting...") + alert(R.string.error_engine_setup_message, R.string.text_error_title, this::forceQuit) + } + } + return isNativeInitialized() + } + + /** + * Used to complete initialization of the view used by the engine for rendering. + * + * This must be preceded by [onCreate] and [onInitNativeLayer] in that order to properly + * initialize the engine. + * + * @param host The [GodotHost] that's initializing the render views + * @param providedContainerLayout Optional argument; if provided, this is reused to host the Godot's render views + * + * @return A [FrameLayout] instance containing Godot's render views if initialization is successful, null otherwise. + * + * @throws IllegalStateException if [onInitNativeLayer] has not been called + */ + @JvmOverloads + fun onInitRenderView(host: GodotHost, providedContainerLayout: FrameLayout = FrameLayout(host.activity)): FrameLayout? { + if (!isNativeInitialized()) { + throw IllegalStateException("onInitNativeLayer() must be invoked successfully prior to initializing the render view") + } + + try { + val activity: Activity = host.activity + containerLayout = providedContainerLayout + containerLayout?.removeAllViews() + containerLayout?.layoutParams = ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT + ) + + // GodotEditText layout + val editText = GodotEditText(activity) + editText.layoutParams = + ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + activity.resources.getDimension(R.dimen.text_edit_height).toInt() + ) + // ...add to FrameLayout + containerLayout?.addView(editText) + renderView = if (usesVulkan()) { + if (!meetsVulkanRequirements(activity.packageManager)) { + alert(R.string.error_missing_vulkan_requirements_message, R.string.text_error_title, this::forceQuit) + return null + } + GodotVulkanRenderView(host, this) + } else { + // Fallback to openGl + GodotGLRenderView(host, this, xrMode, useDebugOpengl) + } + if (host == primaryHost) { + renderView!!.startRenderer() + } + val view: View = renderView!!.view + containerLayout?.addView( + view, + ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT + ) + ) + editText.setView(renderView) + io?.setEdit(editText) + + // Listeners for keyboard height. + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + // Report the height of virtual keyboard as it changes during the animation. + val decorView = activity.window.decorView + decorView.setWindowInsetsAnimationCallback(object : WindowInsetsAnimation.Callback(DISPATCH_MODE_STOP) { + var startBottom = 0 + var endBottom = 0 + override fun onPrepare(animation: WindowInsetsAnimation) { + startBottom = decorView.rootWindowInsets.getInsets(WindowInsets.Type.ime()).bottom + } + + override fun onStart(animation: WindowInsetsAnimation, bounds: WindowInsetsAnimation.Bounds): WindowInsetsAnimation.Bounds { + endBottom = decorView.rootWindowInsets.getInsets(WindowInsets.Type.ime()).bottom + return bounds + } + + override fun onProgress(windowInsets: WindowInsets, list: List<WindowInsetsAnimation>): WindowInsets { + // Find the IME animation. + var imeAnimation: WindowInsetsAnimation? = null + for (animation in list) { + if (animation.typeMask and WindowInsets.Type.ime() != 0) { + imeAnimation = animation + break + } + } + // Update keyboard height based on IME animation. + if (imeAnimation != null) { + val interpolatedFraction = imeAnimation.interpolatedFraction + // Linear interpolation between start and end values. + val keyboardHeight = startBottom * (1.0f - interpolatedFraction) + endBottom * interpolatedFraction + GodotLib.setVirtualKeyboardHeight(keyboardHeight.toInt()) + } + return windowInsets + } + + override fun onEnd(animation: WindowInsetsAnimation) {} + }) + } else { + // Infer the virtual keyboard height using visible area. + view.viewTreeObserver.addOnGlobalLayoutListener(object : OnGlobalLayoutListener { + // Don't allocate a new Rect every time the callback is called. + val visibleSize = Rect() + override fun onGlobalLayout() { + val surfaceView = renderView!!.view + surfaceView.getWindowVisibleDisplayFrame(visibleSize) + val keyboardHeight = surfaceView.height - visibleSize.bottom + GodotLib.setVirtualKeyboardHeight(keyboardHeight) + } + }) + } + + if (host == primaryHost) { + renderView!!.queueOnRenderThread { + for (plugin in pluginRegistry.allPlugins) { + plugin.onRegisterPluginWithGodotNative() + } + setKeepScreenOn(java.lang.Boolean.parseBoolean(GodotLib.getGlobal("display/window/energy_saving/keep_screen_on"))) + } + + // Include the returned non-null views in the Godot view hierarchy. + for (plugin in pluginRegistry.allPlugins) { + val pluginView = plugin.onMainCreate(activity) + if (pluginView != null) { + if (plugin.shouldBeOnTop()) { + containerLayout?.addView(pluginView) + } else { + containerLayout?.addView(pluginView, 0) + } + } + } + } + renderViewInitialized = true + } finally { + if (!renderViewInitialized) { + containerLayout?.removeAllViews() + containerLayout = null + } + } + return containerLayout + } + + fun onResume(host: GodotHost) { + if (host != primaryHost) { + return + } + + renderView!!.onActivityResumed() + mSensorManager.registerListener(this, mAccelerometer, SensorManager.SENSOR_DELAY_GAME) + mSensorManager.registerListener(this, mGravity, SensorManager.SENSOR_DELAY_GAME) + mSensorManager.registerListener(this, mMagnetometer, SensorManager.SENSOR_DELAY_GAME) + mSensorManager.registerListener(this, mGyroscope, SensorManager.SENSOR_DELAY_GAME) + if (useImmersive) { + val window = requireActivity().window + window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or + View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or + View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or + View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or // hide nav bar + View.SYSTEM_UI_FLAG_FULLSCREEN or // hide status bar + View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY + } + for (plugin in pluginRegistry.allPlugins) { + plugin.onMainResume() + } + } + + fun onPause(host: GodotHost) { + if (host != primaryHost) { + return + } + + renderView!!.onActivityPaused() + mSensorManager.unregisterListener(this) + for (plugin in pluginRegistry.allPlugins) { + plugin.onMainPause() + } + } + + fun onDestroy(primaryHost: GodotHost) { + if (this.primaryHost != primaryHost) { + return + } + + for (plugin in pluginRegistry.allPlugins) { + plugin.onMainDestroy() + } + GodotLib.ondestroy() + forceQuit() + } + + /** + * Activity result callback + */ + fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + for (plugin in pluginRegistry.allPlugins) { + plugin.onMainActivityResult(requestCode, resultCode, data) + } + } + + /** + * Permissions request callback + */ + fun onRequestPermissionsResult( + requestCode: Int, + permissions: Array<String?>, + grantResults: IntArray + ) { + for (plugin in pluginRegistry.allPlugins) { + plugin.onMainRequestPermissionsResult(requestCode, permissions, grantResults) + } + for (i in permissions.indices) { + GodotLib.requestPermissionResult( + permissions[i], + grantResults[i] == PackageManager.PERMISSION_GRANTED + ) + } + } + + /** + * Invoked on the render thread when the Godot setup is complete. + */ + private fun onGodotSetupCompleted() { + for (plugin in pluginRegistry.allPlugins) { + plugin.onGodotSetupCompleted() + } + primaryHost?.onGodotSetupCompleted() + } + + /** + * Invoked on the render thread when the Godot main loop has started. + */ + private fun onGodotMainLoopStarted() { + for (plugin in pluginRegistry.allPlugins) { + plugin.onGodotMainLoopStarted() + } + primaryHost?.onGodotMainLoopStarted() + } + + private fun restart() { + primaryHost?.onGodotRestartRequested(this) + } + + private fun registerUiChangeListener() { + val decorView = requireActivity().window.decorView + decorView.setOnSystemUiVisibilityChangeListener(uiChangeListener) + } + + @Keep + private fun alert(message: String, title: String) { + alert(message, title, null) + } + + private fun alert( + @StringRes messageResId: Int, + @StringRes titleResId: Int, + okCallback: Runnable? + ) { + val res: Resources = getActivity()?.resources ?: return + alert(res.getString(messageResId), res.getString(titleResId), okCallback) + } + + private fun alert(message: String, title: String, okCallback: Runnable?) { + val activity: Activity = getActivity() ?: return + runOnUiThread(Runnable { + val builder = AlertDialog.Builder(activity) + builder.setMessage(message).setTitle(title) + builder.setPositiveButton( + "OK" + ) { dialog: DialogInterface, id: Int -> + okCallback?.run() + dialog.cancel() + } + val dialog = builder.create() + dialog.show() + }) + } + + /** + * Queue a runnable to be run on the render thread. + * + * This must be called after the render thread has started. + */ + fun runOnRenderThread(action: Runnable) { + if (renderView != null) { + renderView!!.queueOnRenderThread(action) + } + } + + /** + * Runs the specified action on the UI thread. + * If the current thread is the UI thread, then the action is executed immediately. + * If the current thread is not the UI thread, the action is posted to the event queue + * of the UI thread. + */ + fun runOnUiThread(action: Runnable) { + val activity: Activity = getActivity() ?: return + activity.runOnUiThread(action) + } + + /** + * Returns true if the call is being made on the Ui thread. + */ + private fun isOnUiThread() = Looper.myLooper() == Looper.getMainLooper() + + /** + * Returns true if `Vulkan` is used for rendering. + */ + private fun usesVulkan(): Boolean { + val renderer = GodotLib.getGlobal("rendering/renderer/rendering_method") + val renderingDevice = GodotLib.getGlobal("rendering/rendering_device/driver") + return ("forward_plus" == renderer || "mobile" == renderer) && "vulkan" == renderingDevice + } + + /** + * Returns true if the device meets the base requirements for Vulkan support, false otherwise. + */ + private fun meetsVulkanRequirements(packageManager: PackageManager?): Boolean { + if (packageManager == null) { + return false + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + if (!packageManager.hasSystemFeature(PackageManager.FEATURE_VULKAN_HARDWARE_LEVEL, 1)) { + // Optional requirements.. log as warning if missing + Log.w(TAG, "The vulkan hardware level does not meet the minimum requirement: 1") + } + + // Check for api version 1.0 + return packageManager.hasSystemFeature(PackageManager.FEATURE_VULKAN_HARDWARE_VERSION, 0x400003) + } + return false + } + + private fun setKeepScreenOn(p_enabled: Boolean) { + runOnUiThread { + if (p_enabled) { + getActivity()?.window?.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) + } else { + getActivity()?.window?.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) + } + } + } + + fun hasClipboard(): Boolean { + return mClipboard.hasPrimaryClip() + } + + fun getClipboard(): String? { + val clipData = mClipboard.primaryClip ?: return "" + val text = clipData.getItemAt(0).text ?: return "" + return text.toString() + } + + fun setClipboard(text: String?) { + val clip = ClipData.newPlainText("myLabel", text) + mClipboard.setPrimaryClip(clip) + } + + private fun forceQuit() { + forceQuit(0) + } + + @Keep + private fun forceQuit(instanceId: Int): Boolean { + if (primaryHost == null) { + return false + } + return if (instanceId == 0) { + primaryHost!!.onGodotForceQuit(this) + true + } else { + primaryHost!!.onGodotForceQuit(instanceId) + } + } + + fun onBackPressed(host: GodotHost) { + if (host != primaryHost) { + return + } + + var shouldQuit = true + for (plugin in pluginRegistry.allPlugins) { + if (plugin.onMainBackPressed()) { + shouldQuit = false + } + } + if (shouldQuit && renderView != null) { + renderView!!.queueOnRenderThread { GodotLib.back() } + } + } + + private fun getRotatedValues(values: FloatArray?): FloatArray? { + if (values == null || values.size != 3) { + return values + } + val display = + (requireActivity().getSystemService(Context.WINDOW_SERVICE) as WindowManager).defaultDisplay + val displayRotation = display.rotation + val rotatedValues = FloatArray(3) + when (displayRotation) { + Surface.ROTATION_0 -> { + rotatedValues[0] = values[0] + rotatedValues[1] = values[1] + rotatedValues[2] = values[2] + } + Surface.ROTATION_90 -> { + rotatedValues[0] = -values[1] + rotatedValues[1] = values[0] + rotatedValues[2] = values[2] + } + Surface.ROTATION_180 -> { + rotatedValues[0] = -values[0] + rotatedValues[1] = -values[1] + rotatedValues[2] = values[2] + } + Surface.ROTATION_270 -> { + rotatedValues[0] = values[1] + rotatedValues[1] = -values[0] + rotatedValues[2] = values[2] + } + } + return rotatedValues + } + + override fun onSensorChanged(event: SensorEvent) { + if (renderView == null) { + return + } + when (event.sensor.type) { + Sensor.TYPE_ACCELEROMETER -> { + val rotatedValues = getRotatedValues(event.values) + renderView!!.queueOnRenderThread { + GodotLib.accelerometer( + -rotatedValues!![0], -rotatedValues[1], -rotatedValues[2] + ) + } + } + Sensor.TYPE_GRAVITY -> { + val rotatedValues = getRotatedValues(event.values) + renderView!!.queueOnRenderThread { + GodotLib.gravity( + -rotatedValues!![0], -rotatedValues[1], -rotatedValues[2] + ) + } + } + Sensor.TYPE_MAGNETIC_FIELD -> { + val rotatedValues = getRotatedValues(event.values) + renderView!!.queueOnRenderThread { + GodotLib.magnetometer( + -rotatedValues!![0], -rotatedValues[1], -rotatedValues[2] + ) + } + } + Sensor.TYPE_GYROSCOPE -> { + val rotatedValues = getRotatedValues(event.values) + renderView!!.queueOnRenderThread { + GodotLib.gyroscope( + rotatedValues!![0], rotatedValues[1], rotatedValues[2] + ) + } + } + } + } + + override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) { + // Do something here if sensor accuracy changes. + } + + /** + * Used by the native code (java_godot_wrapper.h) to vibrate the device. + * @param durationMs + */ + @SuppressLint("MissingPermission") + @Keep + private fun vibrate(durationMs: Int) { + if (durationMs > 0 && requestPermission("VIBRATE")) { + val vibratorService = getActivity()?.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator? ?: return + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + vibratorService.vibrate( + VibrationEffect.createOneShot( + durationMs.toLong(), + VibrationEffect.DEFAULT_AMPLITUDE + ) + ) + } else { + // deprecated in API 26 + vibratorService.vibrate(durationMs.toLong()) + } + } + } + + private fun getCommandLine(): MutableList<String> { + val original: MutableList<String> = parseCommandLine() + val hostCommandLine = primaryHost?.commandLine + if (hostCommandLine != null && hostCommandLine.isNotEmpty()) { + original.addAll(hostCommandLine) + } + return original + } + + private fun parseCommandLine(): MutableList<String> { + val inputStream: InputStream + return try { + inputStream = requireActivity().assets.open("_cl_") + val len = ByteArray(4) + var r = inputStream.read(len) + if (r < 4) { + return mutableListOf() + } + val argc = + (len[3].toInt() and 0xFF) shl 24 or ((len[2].toInt() and 0xFF) shl 16) or ((len[1].toInt() and 0xFF) shl 8) or (len[0].toInt() and 0xFF) + val cmdline = ArrayList<String>(argc) + for (i in 0 until argc) { + r = inputStream.read(len) + if (r < 4) { + return mutableListOf() + } + val strlen = + (len[3].toInt() and 0xFF) shl 24 or ((len[2].toInt() and 0xFF) shl 16) or ((len[1].toInt() and 0xFF) shl 8) or (len[0].toInt() and 0xFF) + if (strlen > 65535) { + return mutableListOf() + } + val arg = ByteArray(strlen) + r = inputStream.read(arg) + if (r == strlen) { + cmdline[i] = String(arg, StandardCharsets.UTF_8) + } + } + cmdline + } catch (e: Exception) { + // The _cl_ file can be missing with no adverse effect + mutableListOf() + } + } + + /** + * Used by the native code (java_godot_wrapper.h) to access the input fallback mapping. + * @return The input fallback mapping for the current XR mode. + */ + @Keep + private fun getInputFallbackMapping(): String? { + return xrMode.inputFallbackMapping + } + + fun requestPermission(name: String?): Boolean { + return requestPermission(name, getActivity()) + } + + fun requestPermissions(): Boolean { + return PermissionsUtil.requestManifestPermissions(getActivity()) + } + + fun getGrantedPermissions(): Array<String?>? { + return PermissionsUtil.getGrantedPermissions(getActivity()) + } + + @Keep + private fun getCACertificates(): String { + return GodotNetUtils.getCACertificates() + } + + private fun obbIsCorrupted(f: String, mainPackMd5: String): Boolean { + return try { + val fis: InputStream = FileInputStream(f) + + // Create MD5 Hash + val buffer = ByteArray(16384) + val complete = MessageDigest.getInstance("MD5") + var numRead: Int + do { + numRead = fis.read(buffer) + if (numRead > 0) { + complete.update(buffer, 0, numRead) + } + } while (numRead != -1) + fis.close() + val messageDigest = complete.digest() + + // Create Hex String + val hexString = StringBuilder() + for (b in messageDigest) { + var s = Integer.toHexString(0xFF and b.toInt()) + if (s.length == 1) { + s = "0$s" + } + hexString.append(s) + } + val md5str = hexString.toString() + md5str != mainPackMd5 + } catch (e: java.lang.Exception) { + e.printStackTrace() + true + } + } + + @Keep + private fun initInputDevices() { + renderView!!.initInputDevices() + } + + @Keep + private fun createNewGodotInstance(args: Array<String>): Int { + return primaryHost?.onNewGodotInstanceRequested(args) ?: 0 + } + + @Keep + private fun nativeBeginBenchmarkMeasure(label: String) { + beginBenchmarkMeasure(label) + } + + @Keep + private fun nativeEndBenchmarkMeasure(label: String) { + endBenchmarkMeasure(label) + } + + @Keep + private fun nativeDumpBenchmark(benchmarkFile: String) { + dumpBenchmark(fileAccessHandler, benchmarkFile) + } +} diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotActivity.kt b/platform/android/java/lib/src/org/godotengine/godot/GodotActivity.kt new file mode 100644 index 0000000000..4636f753af --- /dev/null +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotActivity.kt @@ -0,0 +1,167 @@ +/**************************************************************************/ +/* GodotActivity.kt */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +package org.godotengine.godot + +import android.app.Activity +import android.content.Intent +import android.os.Bundle +import android.util.Log +import androidx.annotation.CallSuper +import androidx.fragment.app.FragmentActivity +import org.godotengine.godot.utils.ProcessPhoenix + +/** + * Base abstract activity for Android apps intending to use Godot as the primary screen. + * + * Also a reference implementation for how to setup and use the [GodotFragment] fragment + * within an Android app. + */ +abstract class GodotActivity : FragmentActivity(), GodotHost { + + companion object { + private val TAG = GodotActivity::class.java.simpleName + + @JvmStatic + protected val EXTRA_FORCE_QUIT = "force_quit_requested" + @JvmStatic + protected val EXTRA_NEW_LAUNCH = "new_launch_requested" + } + + /** + * Interaction with the [Godot] object is delegated to the [GodotFragment] class. + */ + protected var godotFragment: GodotFragment? = null + private set + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.godot_app_layout) + + handleStartIntent(intent, true) + + val currentFragment = supportFragmentManager.findFragmentById(R.id.godot_fragment_container) + if (currentFragment is GodotFragment) { + Log.v(TAG, "Reusing existing Godot fragment instance.") + godotFragment = currentFragment + } else { + Log.v(TAG, "Creating new Godot fragment instance.") + godotFragment = initGodotInstance() + supportFragmentManager.beginTransaction().replace(R.id.godot_fragment_container, godotFragment!!).setPrimaryNavigationFragment(godotFragment).commitNowAllowingStateLoss() + } + } + + override fun onDestroy() { + Log.v(TAG, "Destroying Godot app...") + super.onDestroy() + if (godotFragment != null) { + terminateGodotInstance(godotFragment!!.godot) + } + } + + override fun onGodotForceQuit(instance: Godot) { + runOnUiThread { terminateGodotInstance(instance) } + } + + private fun terminateGodotInstance(instance: Godot) { + if (godotFragment != null && instance === godotFragment!!.godot) { + Log.v(TAG, "Force quitting Godot instance") + ProcessPhoenix.forceQuit(this) + } + } + + override fun onGodotRestartRequested(instance: Godot) { + runOnUiThread { + if (godotFragment != null && instance === godotFragment!!.godot) { + // It's very hard to properly de-initialize Godot on Android to restart the game + // from scratch. Therefore, we need to kill the whole app process and relaunch it. + // + // Restarting only the activity, wouldn't be enough unless it did proper cleanup (including + // releasing and reloading native libs or resetting their state somehow and clearing static data). + Log.v(TAG, "Restarting Godot instance...") + ProcessPhoenix.triggerRebirth(this) + } + } + } + + override fun onNewIntent(newIntent: Intent) { + super.onNewIntent(newIntent) + intent = newIntent + + handleStartIntent(newIntent, false) + + godotFragment?.onNewIntent(newIntent) + } + + private fun handleStartIntent(intent: Intent, newLaunch: Boolean) { + val forceQuitRequested = intent.getBooleanExtra(EXTRA_FORCE_QUIT, false) + if (forceQuitRequested) { + Log.d(TAG, "Force quit requested, terminating..") + ProcessPhoenix.forceQuit(this) + return + } + if (!newLaunch) { + val newLaunchRequested = intent.getBooleanExtra(EXTRA_NEW_LAUNCH, false) + if (newLaunchRequested) { + Log.d(TAG, "New launch requested, restarting..") + val restartIntent = Intent(intent).putExtra(EXTRA_NEW_LAUNCH, false) + ProcessPhoenix.triggerRebirth(this, restartIntent) + return + } + } + } + + @CallSuper + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + godotFragment?.onActivityResult(requestCode, resultCode, data) + } + + @CallSuper + override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults) + godotFragment?.onRequestPermissionsResult(requestCode, permissions, grantResults) + } + + override fun onBackPressed() { + godotFragment?.onBackPressed() ?: super.onBackPressed() + } + + override fun getActivity(): Activity? { + return this + } + + /** + * Used to initialize the Godot fragment instance in [onCreate]. + */ + protected open fun initGodotInstance(): GodotFragment { + return GodotFragment() + } +} diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotFragment.java b/platform/android/java/lib/src/org/godotengine/godot/GodotFragment.java new file mode 100644 index 0000000000..9a8b10ea3e --- /dev/null +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotFragment.java @@ -0,0 +1,429 @@ +/**************************************************************************/ +/* GodotFragment.java */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +package org.godotengine.godot; + +import org.godotengine.godot.utils.BenchmarkUtils; + +import android.app.Activity; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.os.Build; +import android.os.Bundle; +import android.os.Messenger; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.FrameLayout; +import android.widget.ProgressBar; +import android.widget.TextView; + +import androidx.annotation.CallSuper; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; + +import com.google.android.vending.expansion.downloader.DownloadProgressInfo; +import com.google.android.vending.expansion.downloader.DownloaderClientMarshaller; +import com.google.android.vending.expansion.downloader.DownloaderServiceMarshaller; +import com.google.android.vending.expansion.downloader.Helpers; +import com.google.android.vending.expansion.downloader.IDownloaderClient; +import com.google.android.vending.expansion.downloader.IDownloaderService; +import com.google.android.vending.expansion.downloader.IStub; + +import java.util.Collections; +import java.util.List; +import java.util.Locale; + +/** + * Base fragment for Android apps intending to use Godot for part of the app's UI. + */ +public class GodotFragment extends Fragment implements IDownloaderClient, GodotHost { + private static final String TAG = GodotFragment.class.getSimpleName(); + + private IStub mDownloaderClientStub; + private TextView mStatusText; + private TextView mProgressFraction; + private TextView mProgressPercent; + private TextView mAverageSpeed; + private TextView mTimeRemaining; + private ProgressBar mPB; + + private View mDashboard; + private View mCellMessage; + + private Button mPauseButton; + private Button mWiFiSettingsButton; + + private FrameLayout godotContainerLayout; + private boolean mStatePaused; + private int mState; + + @Nullable + private GodotHost parentHost; + private Godot godot; + + static private Intent mCurrentIntent; + + public void onNewIntent(Intent intent) { + mCurrentIntent = intent; + } + + static public Intent getCurrentIntent() { + return mCurrentIntent; + } + + private void setState(int newState) { + if (mState != newState) { + mState = newState; + mStatusText.setText(Helpers.getDownloaderStringResourceIDFromState(newState)); + } + } + + private void setButtonPausedState(boolean paused) { + mStatePaused = paused; + int stringResourceID = paused ? R.string.text_button_resume : R.string.text_button_pause; + mPauseButton.setText(stringResourceID); + } + + public interface ResultCallback { + void callback(int requestCode, int resultCode, Intent data); + } + public ResultCallback resultCallback; + + public Godot getGodot() { + return godot; + } + + @Override + public void onAttach(@NonNull Context context) { + super.onAttach(context); + if (getParentFragment() instanceof GodotHost) { + parentHost = (GodotHost)getParentFragment(); + } else if (getActivity() instanceof GodotHost) { + parentHost = (GodotHost)getActivity(); + } + } + + @Override + public void onDetach() { + super.onDetach(); + parentHost = null; + } + + @CallSuper + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (resultCallback != null) { + resultCallback.callback(requestCode, resultCode, data); + resultCallback = null; + } + + godot.onActivityResult(requestCode, resultCode, data); + } + + @CallSuper + @Override + public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + godot.onRequestPermissionsResult(requestCode, permissions, grantResults); + } + + @Override + public void onServiceConnected(Messenger m) { + IDownloaderService remoteService = DownloaderServiceMarshaller.CreateProxy(m); + remoteService.onClientUpdated(mDownloaderClientStub.getMessenger()); + } + + @Override + public void onCreate(Bundle icicle) { + BenchmarkUtils.beginBenchmarkMeasure("GodotFragment::onCreate"); + super.onCreate(icicle); + + final Activity activity = getActivity(); + mCurrentIntent = activity.getIntent(); + + godot = new Godot(requireContext()); + performEngineInitialization(); + BenchmarkUtils.endBenchmarkMeasure("GodotFragment::onCreate"); + } + + private void performEngineInitialization() { + try { + godot.onCreate(this); + + if (!godot.onInitNativeLayer(this)) { + throw new IllegalStateException("Unable to initialize engine native layer"); + } + + godotContainerLayout = godot.onInitRenderView(this); + if (godotContainerLayout == null) { + throw new IllegalStateException("Unable to initialize engine render view"); + } + } catch (IllegalArgumentException ignored) { + final Activity activity = getActivity(); + Intent notifierIntent = new Intent(activity, activity.getClass()); + notifierIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); + + PendingIntent pendingIntent; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + pendingIntent = PendingIntent.getActivity(activity, 0, + notifierIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); + } else { + pendingIntent = PendingIntent.getActivity(activity, 0, + notifierIntent, PendingIntent.FLAG_UPDATE_CURRENT); + } + + int startResult; + try { + startResult = DownloaderClientMarshaller.startDownloadServiceIfRequired(getContext(), pendingIntent, GodotDownloaderService.class); + + if (startResult != DownloaderClientMarshaller.NO_DOWNLOAD_REQUIRED) { + // This is where you do set up to display the download + // progress (next step in onCreateView) + mDownloaderClientStub = DownloaderClientMarshaller.CreateStub(this, GodotDownloaderService.class); + return; + } + + // Restart engine initialization + performEngineInitialization(); + } catch (PackageManager.NameNotFoundException e) { + Log.e(TAG, "Unable to start download service", e); + } + } + } + + @Override + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle icicle) { + if (mDownloaderClientStub != null) { + View downloadingExpansionView = + inflater.inflate(R.layout.downloading_expansion, container, false); + mPB = (ProgressBar)downloadingExpansionView.findViewById(R.id.progressBar); + mStatusText = (TextView)downloadingExpansionView.findViewById(R.id.statusText); + mProgressFraction = (TextView)downloadingExpansionView.findViewById(R.id.progressAsFraction); + mProgressPercent = (TextView)downloadingExpansionView.findViewById(R.id.progressAsPercentage); + mAverageSpeed = (TextView)downloadingExpansionView.findViewById(R.id.progressAverageSpeed); + mTimeRemaining = (TextView)downloadingExpansionView.findViewById(R.id.progressTimeRemaining); + mDashboard = downloadingExpansionView.findViewById(R.id.downloaderDashboard); + mCellMessage = downloadingExpansionView.findViewById(R.id.approveCellular); + mPauseButton = (Button)downloadingExpansionView.findViewById(R.id.pauseButton); + mWiFiSettingsButton = (Button)downloadingExpansionView.findViewById(R.id.wifiSettingsButton); + + return downloadingExpansionView; + } + + return godotContainerLayout; + } + + @Override + public void onDestroy() { + godot.onDestroy(this); + super.onDestroy(); + } + + @Override + public void onPause() { + super.onPause(); + + if (!godot.isInitialized()) { + if (null != mDownloaderClientStub) { + mDownloaderClientStub.disconnect(getActivity()); + } + return; + } + + godot.onPause(this); + } + + @Override + public void onResume() { + super.onResume(); + if (!godot.isInitialized()) { + if (null != mDownloaderClientStub) { + mDownloaderClientStub.connect(getActivity()); + } + return; + } + + godot.onResume(this); + } + + public void onBackPressed() { + godot.onBackPressed(this); + } + + /** + * The download state should trigger changes in the UI --- it may be useful + * to show the state as being indeterminate at times. This sample can be + * considered a guideline. + */ + @Override + public void onDownloadStateChanged(int newState) { + setState(newState); + boolean showDashboard = true; + boolean showCellMessage = false; + boolean paused; + boolean indeterminate; + switch (newState) { + case IDownloaderClient.STATE_IDLE: + // STATE_IDLE means the service is listening, so it's + // safe to start making remote service calls. + paused = false; + indeterminate = true; + break; + case IDownloaderClient.STATE_CONNECTING: + case IDownloaderClient.STATE_FETCHING_URL: + showDashboard = true; + paused = false; + indeterminate = true; + break; + case IDownloaderClient.STATE_DOWNLOADING: + paused = false; + showDashboard = true; + indeterminate = false; + break; + + case IDownloaderClient.STATE_FAILED_CANCELED: + case IDownloaderClient.STATE_FAILED: + case IDownloaderClient.STATE_FAILED_FETCHING_URL: + case IDownloaderClient.STATE_FAILED_UNLICENSED: + paused = true; + showDashboard = false; + indeterminate = false; + break; + case IDownloaderClient.STATE_PAUSED_NEED_CELLULAR_PERMISSION: + case IDownloaderClient.STATE_PAUSED_WIFI_DISABLED_NEED_CELLULAR_PERMISSION: + showDashboard = false; + paused = true; + indeterminate = false; + showCellMessage = true; + break; + + case IDownloaderClient.STATE_PAUSED_BY_REQUEST: + paused = true; + indeterminate = false; + break; + case IDownloaderClient.STATE_PAUSED_ROAMING: + case IDownloaderClient.STATE_PAUSED_SDCARD_UNAVAILABLE: + paused = true; + indeterminate = false; + break; + case IDownloaderClient.STATE_COMPLETED: + showDashboard = false; + paused = false; + indeterminate = false; + performEngineInitialization(); + return; + default: + paused = true; + indeterminate = true; + showDashboard = true; + } + int newDashboardVisibility = showDashboard ? View.VISIBLE : View.GONE; + if (mDashboard.getVisibility() != newDashboardVisibility) { + mDashboard.setVisibility(newDashboardVisibility); + } + int cellMessageVisibility = showCellMessage ? View.VISIBLE : View.GONE; + if (mCellMessage.getVisibility() != cellMessageVisibility) { + mCellMessage.setVisibility(cellMessageVisibility); + } + + mPB.setIndeterminate(indeterminate); + setButtonPausedState(paused); + } + + @Override + public void onDownloadProgress(DownloadProgressInfo progress) { + mAverageSpeed.setText(getString(R.string.kilobytes_per_second, + Helpers.getSpeedString(progress.mCurrentSpeed))); + mTimeRemaining.setText(getString(R.string.time_remaining, + Helpers.getTimeRemaining(progress.mTimeRemaining))); + + mPB.setMax((int)(progress.mOverallTotal >> 8)); + mPB.setProgress((int)(progress.mOverallProgress >> 8)); + mProgressPercent.setText(String.format(Locale.ENGLISH, "%d %%", progress.mOverallProgress * 100 / progress.mOverallTotal)); + mProgressFraction.setText(Helpers.getDownloadProgressString(progress.mOverallProgress, + progress.mOverallTotal)); + } + + @CallSuper + @Override + public List<String> getCommandLine() { + return parentHost != null ? parentHost.getCommandLine() : Collections.emptyList(); + } + + @CallSuper + @Override + public void onGodotSetupCompleted() { + if (parentHost != null) { + parentHost.onGodotSetupCompleted(); + } + } + + @CallSuper + @Override + public void onGodotMainLoopStarted() { + if (parentHost != null) { + parentHost.onGodotMainLoopStarted(); + } + } + + @Override + public void onGodotForceQuit(Godot instance) { + if (parentHost != null) { + parentHost.onGodotForceQuit(instance); + } + } + + @Override + public boolean onGodotForceQuit(int godotInstanceId) { + return parentHost != null && parentHost.onGodotForceQuit(godotInstanceId); + } + + @Override + public void onGodotRestartRequested(Godot instance) { + if (parentHost != null) { + parentHost.onGodotRestartRequested(instance); + } + } + + @Override + public int onNewGodotInstanceRequested(String[] args) { + if (parentHost != null) { + return parentHost.onNewGodotInstanceRequested(args); + } + return 0; + } +} diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotGLRenderView.java b/platform/android/java/lib/src/org/godotengine/godot/GodotGLRenderView.java index b465377743..52350c12a6 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotGLRenderView.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotGLRenderView.java @@ -29,10 +29,10 @@ /**************************************************************************/ package org.godotengine.godot; + import org.godotengine.godot.gl.GLSurfaceView; import org.godotengine.godot.gl.GodotRenderer; import org.godotengine.godot.input.GodotInputHandler; -import org.godotengine.godot.utils.GLUtils; import org.godotengine.godot.xr.XRMode; import org.godotengine.godot.xr.ovr.OvrConfigChooser; import org.godotengine.godot.xr.ovr.OvrContextFactory; @@ -78,22 +78,23 @@ import java.io.InputStream; * bit depths). Failure to do so would result in an EGL_BAD_MATCH error. */ public class GodotGLRenderView extends GLSurfaceView implements GodotRenderView { + private final GodotHost host; private final Godot godot; private final GodotInputHandler inputHandler; private final GodotRenderer godotRenderer; private final SparseArray<PointerIcon> customPointerIcons = new SparseArray<>(); - public GodotGLRenderView(Context context, Godot godot, XRMode xrMode, boolean p_use_debug_opengl) { - super(context); - GLUtils.use_debug_opengl = p_use_debug_opengl; + public GodotGLRenderView(GodotHost host, Godot godot, XRMode xrMode, boolean useDebugOpengl) { + super(host.getActivity()); + this.host = host; this.godot = godot; this.inputHandler = new GodotInputHandler(this); this.godotRenderer = new GodotRenderer(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { setPointerIcon(PointerIcon.getSystemIcon(getContext(), PointerIcon.TYPE_DEFAULT)); } - init(xrMode, false); + init(xrMode, false, useDebugOpengl); } @Override @@ -123,7 +124,7 @@ public class GodotGLRenderView extends GLSurfaceView implements GodotRenderView @Override public void onBackPressed() { - godot.onBackPressed(); + godot.onBackPressed(host); } @Override @@ -233,7 +234,7 @@ public class GodotGLRenderView extends GLSurfaceView implements GodotRenderView return super.onResolvePointerIcon(me, pointerIndex); } - private void init(XRMode xrMode, boolean translucent) { + private void init(XRMode xrMode, boolean translucent, boolean useDebugOpengl) { setPreserveEGLContextOnPause(true); setFocusableInTouchMode(true); switch (xrMode) { @@ -262,7 +263,7 @@ public class GodotGLRenderView extends GLSurfaceView implements GodotRenderView /* Setup the context factory for 2.0 rendering. * See ContextFactory class definition below */ - setEGLContextFactory(new RegularContextFactory()); + setEGLContextFactory(new RegularContextFactory(useDebugOpengl)); /* We need to choose an EGLConfig that matches the format of * our surface exactly. This is going to be done in our @@ -275,7 +276,10 @@ public class GodotGLRenderView extends GLSurfaceView implements GodotRenderView new RegularConfigChooser(8, 8, 8, 8, 16, 0))); break; } + } + @Override + public void startRenderer() { /* Set the renderer responsible for frame rendering */ setRenderer(godotRenderer); } diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotHost.java b/platform/android/java/lib/src/org/godotengine/godot/GodotHost.java index 7700b9b628..e5333085dd 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotHost.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotHost.java @@ -30,11 +30,13 @@ package org.godotengine.godot; +import android.app.Activity; + import java.util.Collections; import java.util.List; /** - * Denotate a component (e.g: Activity, Fragment) that hosts the {@link Godot} fragment. + * Denotate a component (e.g: Activity, Fragment) that hosts the {@link Godot} engine. */ public interface GodotHost { /** @@ -86,4 +88,9 @@ public interface GodotHost { default int onNewGodotInstanceRequested(String[] args) { return 0; } + + /** + * Provide access to the Activity hosting the Godot engine. + */ + Activity getActivity(); } diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotRenderView.java b/platform/android/java/lib/src/org/godotengine/godot/GodotRenderView.java index 00243dab2a..ebf3a6b2fb 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotRenderView.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotRenderView.java @@ -39,6 +39,11 @@ public interface GodotRenderView { void initInputDevices(); + /** + * Starts the thread that will drive Godot's rendering. + */ + void startRenderer(); + void queueOnRenderThread(Runnable event); void onActivityPaused(); diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotService.kt b/platform/android/java/lib/src/org/godotengine/godot/GodotService.kt new file mode 100644 index 0000000000..68cd2c1358 --- /dev/null +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotService.kt @@ -0,0 +1,54 @@ +package org.godotengine.godot + +import android.app.Service +import android.content.Intent +import android.os.Binder +import android.os.IBinder +import android.util.Log + +/** + * Godot service responsible for hosting the Godot engine instance. + */ +class GodotService : Service() { + + companion object { + private val TAG = GodotService::class.java.simpleName + } + + private var boundIntent: Intent? = null + private val godot by lazy { + Godot(applicationContext) + } + + override fun onCreate() { + super.onCreate() + } + + override fun onDestroy() { + super.onDestroy() + } + + override fun onBind(intent: Intent?): IBinder? { + if (boundIntent != null) { + Log.d(TAG, "GodotService already bound") + return null + } + + boundIntent = intent + return GodotHandle(godot) + } + + override fun onRebind(intent: Intent?) { + super.onRebind(intent) + } + + override fun onUnbind(intent: Intent?): Boolean { + return super.onUnbind(intent) + } + + override fun onTaskRemoved(rootIntent: Intent?) { + super.onTaskRemoved(rootIntent) + } + + class GodotHandle(val godot: Godot) : Binder() +} diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java b/platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java index 681e182adb..48708152be 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java @@ -35,7 +35,6 @@ import org.godotengine.godot.vulkan.VkRenderer; import org.godotengine.godot.vulkan.VkSurfaceView; import android.annotation.SuppressLint; -import android.content.Context; import android.content.res.AssetManager; import android.graphics.Bitmap; import android.graphics.BitmapFactory; @@ -52,14 +51,16 @@ import androidx.annotation.Keep; import java.io.InputStream; public class GodotVulkanRenderView extends VkSurfaceView implements GodotRenderView { + private final GodotHost host; private final Godot godot; private final GodotInputHandler mInputHandler; private final VkRenderer mRenderer; private final SparseArray<PointerIcon> customPointerIcons = new SparseArray<>(); - public GodotVulkanRenderView(Context context, Godot godot) { - super(context); + public GodotVulkanRenderView(GodotHost host, Godot godot) { + super(host.getActivity()); + this.host = host; this.godot = godot; mInputHandler = new GodotInputHandler(this); mRenderer = new VkRenderer(); @@ -67,6 +68,10 @@ public class GodotVulkanRenderView extends VkSurfaceView implements GodotRenderV setPointerIcon(PointerIcon.getSystemIcon(getContext(), PointerIcon.TYPE_DEFAULT)); } setFocusableInTouchMode(true); + } + + @Override + public void startRenderer() { startRenderer(mRenderer); } @@ -97,7 +102,7 @@ public class GodotVulkanRenderView extends VkSurfaceView implements GodotRenderV @Override public void onBackPressed() { - godot.onBackPressed(); + godot.onBackPressed(host); } @Override diff --git a/platform/android/java/lib/src/org/godotengine/godot/tts/GodotTTS.java b/platform/android/java/lib/src/org/godotengine/godot/tts/GodotTTS.java index edace53e7f..dce6753b7a 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/tts/GodotTTS.java +++ b/platform/android/java/lib/src/org/godotengine/godot/tts/GodotTTS.java @@ -33,6 +33,7 @@ package org.godotengine.godot.tts; import org.godotengine.godot.GodotLib; import android.app.Activity; +import android.content.Context; import android.os.Bundle; import android.speech.tts.TextToSpeech; import android.speech.tts.UtteranceProgressListener; @@ -62,7 +63,7 @@ public class GodotTTS extends UtteranceProgressListener { final private static int EVENT_CANCEL = 2; final private static int EVENT_BOUNDARY = 3; - final private Activity activity; + private final Context context; private TextToSpeech synth; private LinkedList<GodotUtterance> queue; final private Object lock = new Object(); @@ -71,8 +72,8 @@ public class GodotTTS extends UtteranceProgressListener { private boolean speaking; private boolean paused; - public GodotTTS(Activity p_activity) { - activity = p_activity; + public GodotTTS(Context context) { + this.context = context; } private void updateTTS() { @@ -188,7 +189,7 @@ public class GodotTTS extends UtteranceProgressListener { * Initialize synth and query. */ public void init() { - synth = new TextToSpeech(activity, null); + synth = new TextToSpeech(context, null); queue = new LinkedList<GodotUtterance>(); synth.setOnUtteranceProgressListener(this); diff --git a/platform/android/java/lib/src/org/godotengine/godot/utils/GLUtils.java b/platform/android/java/lib/src/org/godotengine/godot/utils/GLUtils.java index 7db02968bb..2c7b73ae4d 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/utils/GLUtils.java +++ b/platform/android/java/lib/src/org/godotengine/godot/utils/GLUtils.java @@ -44,8 +44,6 @@ public class GLUtils { public static final boolean DEBUG = false; - public static boolean use_debug_opengl = false; - private static final String[] ATTRIBUTES_NAMES = new String[] { "EGL_BUFFER_SIZE", "EGL_ALPHA_SIZE", diff --git a/platform/android/java/lib/src/org/godotengine/godot/utils/GodotNetUtils.java b/platform/android/java/lib/src/org/godotengine/godot/utils/GodotNetUtils.java index c31d56a3e1..dca190a2fc 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/utils/GodotNetUtils.java +++ b/platform/android/java/lib/src/org/godotengine/godot/utils/GodotNetUtils.java @@ -36,7 +36,8 @@ import android.net.wifi.WifiManager; import android.util.Base64; import android.util.Log; -import java.io.StringWriter; +import androidx.annotation.NonNull; + import java.security.KeyStore; import java.security.cert.X509Certificate; import java.util.Enumeration; @@ -50,9 +51,9 @@ public class GodotNetUtils { /* A single, reference counted, multicast lock, or null if permission CHANGE_WIFI_MULTICAST_STATE is missing */ private WifiManager.MulticastLock multicastLock; - public GodotNetUtils(Activity p_activity) { - if (PermissionsUtil.hasManifestPermission(p_activity, "android.permission.CHANGE_WIFI_MULTICAST_STATE")) { - WifiManager wifi = (WifiManager)p_activity.getApplicationContext().getSystemService(Context.WIFI_SERVICE); + public GodotNetUtils(Context context) { + if (PermissionsUtil.hasManifestPermission(context, "android.permission.CHANGE_WIFI_MULTICAST_STATE")) { + WifiManager wifi = (WifiManager)context.getApplicationContext().getSystemService(Context.WIFI_SERVICE); multicastLock = wifi.createMulticastLock("GodotMulticastLock"); multicastLock.setReferenceCounted(true); } @@ -91,7 +92,7 @@ public class GodotNetUtils { * @see https://developer.android.com/reference/java/security/KeyStore . * @return A string of concatenated X509 certificates in PEM format. */ - public static String getCACertificates() { + public static @NonNull String getCACertificates() { try { KeyStore ks = KeyStore.getInstance("AndroidCAStore"); StringBuilder writer = new StringBuilder(); diff --git a/platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java b/platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java index a94188c405..8353fc8dc6 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java +++ b/platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java @@ -32,6 +32,7 @@ package org.godotengine.godot.utils; import android.Manifest; import android.app.Activity; +import android.content.Context; import android.content.Intent; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; @@ -52,7 +53,6 @@ import java.util.Set; /** * This class includes utility functions for Android permissions related operations. */ - public final class PermissionsUtil { private static final String TAG = PermissionsUtil.class.getSimpleName(); @@ -193,13 +193,13 @@ public final class PermissionsUtil { /** * With this function you can get the list of dangerous permissions that have been granted to the Android application. - * @param activity the caller activity for this method. + * @param context the caller context for this method. * @return granted permissions list */ - public static String[] getGrantedPermissions(Activity activity) { + public static String[] getGrantedPermissions(Context context) { String[] manifestPermissions; try { - manifestPermissions = getManifestPermissions(activity); + manifestPermissions = getManifestPermissions(context); } catch (PackageManager.NameNotFoundException e) { e.printStackTrace(); return new String[0]; @@ -215,9 +215,9 @@ public final class PermissionsUtil { grantedPermissions.add(manifestPermission); } } else { - PermissionInfo permissionInfo = getPermissionInfo(activity, manifestPermission); + PermissionInfo permissionInfo = getPermissionInfo(context, manifestPermission); int protectionLevel = Build.VERSION.SDK_INT >= Build.VERSION_CODES.P ? permissionInfo.getProtection() : permissionInfo.protectionLevel; - if (protectionLevel == PermissionInfo.PROTECTION_DANGEROUS && ContextCompat.checkSelfPermission(activity, manifestPermission) == PackageManager.PERMISSION_GRANTED) { + if (protectionLevel == PermissionInfo.PROTECTION_DANGEROUS && ContextCompat.checkSelfPermission(context, manifestPermission) == PackageManager.PERMISSION_GRANTED) { grantedPermissions.add(manifestPermission); } } @@ -232,13 +232,13 @@ public final class PermissionsUtil { /** * Check if the given permission is in the AndroidManifest.xml file. - * @param activity the caller activity for this method. + * @param context the caller context for this method. * @param permission the permession to look for in the manifest file. * @return "true" if the permission is in the manifest file of the activity, "false" otherwise. */ - public static boolean hasManifestPermission(Activity activity, String permission) { + public static boolean hasManifestPermission(Context context, String permission) { try { - for (String p : getManifestPermissions(activity)) { + for (String p : getManifestPermissions(context)) { if (permission.equals(p)) return true; } @@ -250,13 +250,13 @@ public final class PermissionsUtil { /** * Returns the permissions defined in the AndroidManifest.xml file. - * @param activity the caller activity for this method. + * @param context the caller context for this method. * @return manifest permissions list * @throws PackageManager.NameNotFoundException the exception is thrown when a given package, application, or component name cannot be found. */ - private static String[] getManifestPermissions(Activity activity) throws PackageManager.NameNotFoundException { - PackageManager packageManager = activity.getPackageManager(); - PackageInfo packageInfo = packageManager.getPackageInfo(activity.getPackageName(), PackageManager.GET_PERMISSIONS); + private static String[] getManifestPermissions(Context context) throws PackageManager.NameNotFoundException { + PackageManager packageManager = context.getPackageManager(); + PackageInfo packageInfo = packageManager.getPackageInfo(context.getPackageName(), PackageManager.GET_PERMISSIONS); if (packageInfo.requestedPermissions == null) return new String[0]; return packageInfo.requestedPermissions; @@ -264,13 +264,13 @@ public final class PermissionsUtil { /** * Returns the information of the desired permission. - * @param activity the caller activity for this method. + * @param context the caller context for this method. * @param permission the name of the permission. * @return permission info object * @throws PackageManager.NameNotFoundException the exception is thrown when a given package, application, or component name cannot be found. */ - private static PermissionInfo getPermissionInfo(Activity activity, String permission) throws PackageManager.NameNotFoundException { - PackageManager packageManager = activity.getPackageManager(); + private static PermissionInfo getPermissionInfo(Context context, String permission) throws PackageManager.NameNotFoundException { + PackageManager packageManager = context.getPackageManager(); return packageManager.getPermissionInfo(permission, 0); } } diff --git a/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularContextFactory.java b/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularContextFactory.java index 1a126ff765..01ee41e30b 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularContextFactory.java +++ b/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularContextFactory.java @@ -51,12 +51,22 @@ public class RegularContextFactory implements GLSurfaceView.EGLContextFactory { private static int EGL_CONTEXT_CLIENT_VERSION = 0x3098; + private final boolean mUseDebugOpengl; + + public RegularContextFactory() { + this(false); + } + + public RegularContextFactory(boolean useDebugOpengl) { + this.mUseDebugOpengl = useDebugOpengl; + } + public EGLContext createContext(EGL10 egl, EGLDisplay display, EGLConfig eglConfig) { Log.w(TAG, "creating OpenGL ES 3.0 context :"); GLUtils.checkEglError(TAG, "Before eglCreateContext", egl); EGLContext context; - if (GLUtils.use_debug_opengl) { + if (mUseDebugOpengl) { int[] attrib_list = { EGL_CONTEXT_CLIENT_VERSION, 3, _EGL_CONTEXT_FLAGS_KHR, _EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR, EGL10.EGL_NONE }; context = egl.eglCreateContext(display, eglConfig, EGL10.EGL_NO_CONTEXT, attrib_list); } else { diff --git a/platform/android/java_godot_lib_jni.cpp b/platform/android/java_godot_lib_jni.cpp index b54491e0e1..74605e3377 100644 --- a/platform/android/java_godot_lib_jni.cpp +++ b/platform/android/java_godot_lib_jni.cpp @@ -135,7 +135,7 @@ JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_GodotLib_initialize(JNIEnv os_android = new OS_Android(godot_java, godot_io_java, p_use_apk_expansion); - return godot_java->on_video_init(env); + return true; } JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_ondestroy(JNIEnv *env, jclass clazz) { diff --git a/platform/android/java_godot_wrapper.cpp b/platform/android/java_godot_wrapper.cpp index 862d9f0436..79ba2528ba 100644 --- a/platform/android/java_godot_wrapper.cpp +++ b/platform/android/java_godot_wrapper.cpp @@ -58,12 +58,10 @@ GodotJavaWrapper::GodotJavaWrapper(JNIEnv *p_env, jobject p_activity, jobject p_ } // get some Godot method pointers... - _on_video_init = p_env->GetMethodID(godot_class, "onVideoInit", "()Z"); _restart = p_env->GetMethodID(godot_class, "restart", "()V"); _finish = p_env->GetMethodID(godot_class, "forceQuit", "(I)Z"); _set_keep_screen_on = p_env->GetMethodID(godot_class, "setKeepScreenOn", "(Z)V"); _alert = p_env->GetMethodID(godot_class, "alert", "(Ljava/lang/String;Ljava/lang/String;)V"); - _get_GLES_version_code = p_env->GetMethodID(godot_class, "getGLESVersionCode", "()I"); _get_clipboard = p_env->GetMethodID(godot_class, "getClipboard", "()Ljava/lang/String;"); _set_clipboard = p_env->GetMethodID(godot_class, "setClipboard", "(Ljava/lang/String;)V"); _has_clipboard = p_env->GetMethodID(godot_class, "hasClipboard", "()Z"); @@ -72,20 +70,15 @@ GodotJavaWrapper::GodotJavaWrapper(JNIEnv *p_env, jobject p_activity, jobject p_ _get_granted_permissions = p_env->GetMethodID(godot_class, "getGrantedPermissions", "()[Ljava/lang/String;"); _get_ca_certificates = p_env->GetMethodID(godot_class, "getCACertificates", "()Ljava/lang/String;"); _init_input_devices = p_env->GetMethodID(godot_class, "initInputDevices", "()V"); - _get_surface = p_env->GetMethodID(godot_class, "getSurface", "()Landroid/view/Surface;"); - _is_activity_resumed = p_env->GetMethodID(godot_class, "isActivityResumed", "()Z"); _vibrate = p_env->GetMethodID(godot_class, "vibrate", "(I)V"); _get_input_fallback_mapping = p_env->GetMethodID(godot_class, "getInputFallbackMapping", "()Ljava/lang/String;"); _on_godot_setup_completed = p_env->GetMethodID(godot_class, "onGodotSetupCompleted", "()V"); _on_godot_main_loop_started = p_env->GetMethodID(godot_class, "onGodotMainLoopStarted", "()V"); _create_new_godot_instance = p_env->GetMethodID(godot_class, "createNewGodotInstance", "([Ljava/lang/String;)I"); _get_render_view = p_env->GetMethodID(godot_class, "getRenderView", "()Lorg/godotengine/godot/GodotRenderView;"); - _begin_benchmark_measure = p_env->GetMethodID(godot_class, "beginBenchmarkMeasure", "(Ljava/lang/String;)V"); - _end_benchmark_measure = p_env->GetMethodID(godot_class, "endBenchmarkMeasure", "(Ljava/lang/String;)V"); - _dump_benchmark = p_env->GetMethodID(godot_class, "dumpBenchmark", "(Ljava/lang/String;)V"); - - // get some Activity method pointers... - _get_class_loader = p_env->GetMethodID(activity_class, "getClassLoader", "()Ljava/lang/ClassLoader;"); + _begin_benchmark_measure = p_env->GetMethodID(godot_class, "nativeBeginBenchmarkMeasure", "(Ljava/lang/String;)V"); + _end_benchmark_measure = p_env->GetMethodID(godot_class, "nativeEndBenchmarkMeasure", "(Ljava/lang/String;)V"); + _dump_benchmark = p_env->GetMethodID(godot_class, "nativeDumpBenchmark", "(Ljava/lang/String;)V"); } GodotJavaWrapper::~GodotJavaWrapper() { @@ -105,29 +98,6 @@ jobject GodotJavaWrapper::get_activity() { return activity; } -jobject GodotJavaWrapper::get_member_object(const char *p_name, const char *p_class, JNIEnv *p_env) { - if (godot_class) { - if (p_env == nullptr) { - p_env = get_jni_env(); - } - ERR_FAIL_NULL_V(p_env, nullptr); - jfieldID fid = p_env->GetStaticFieldID(godot_class, p_name, p_class); - return p_env->GetStaticObjectField(godot_class, fid); - } else { - return nullptr; - } -} - -jobject GodotJavaWrapper::get_class_loader() { - if (_get_class_loader) { - JNIEnv *env = get_jni_env(); - ERR_FAIL_NULL_V(env, nullptr); - return env->CallObjectMethod(activity, _get_class_loader); - } else { - return nullptr; - } -} - GodotJavaViewWrapper *GodotJavaWrapper::get_godot_view() { if (godot_view != nullptr) { return godot_view; @@ -143,17 +113,6 @@ GodotJavaViewWrapper *GodotJavaWrapper::get_godot_view() { return godot_view; } -bool GodotJavaWrapper::on_video_init(JNIEnv *p_env) { - if (_on_video_init) { - if (p_env == nullptr) { - p_env = get_jni_env(); - } - ERR_FAIL_NULL_V(p_env, false); - return p_env->CallBooleanMethod(godot_instance, _on_video_init); - } - return false; -} - void GodotJavaWrapper::on_godot_setup_completed(JNIEnv *p_env) { if (_on_godot_setup_completed) { if (p_env == nullptr) { @@ -212,15 +171,6 @@ void GodotJavaWrapper::alert(const String &p_message, const String &p_title) { } } -int GodotJavaWrapper::get_gles_version_code() { - JNIEnv *env = get_jni_env(); - ERR_FAIL_NULL_V(env, 0); - if (_get_GLES_version_code) { - return env->CallIntMethod(godot_instance, _get_GLES_version_code); - } - return 0; -} - bool GodotJavaWrapper::has_get_clipboard() { return _get_clipboard != nullptr; } @@ -333,26 +283,6 @@ void GodotJavaWrapper::init_input_devices() { } } -jobject GodotJavaWrapper::get_surface() { - if (_get_surface) { - JNIEnv *env = get_jni_env(); - ERR_FAIL_NULL_V(env, nullptr); - return env->CallObjectMethod(godot_instance, _get_surface); - } else { - return nullptr; - } -} - -bool GodotJavaWrapper::is_activity_resumed() { - if (_is_activity_resumed) { - JNIEnv *env = get_jni_env(); - ERR_FAIL_NULL_V(env, false); - return env->CallBooleanMethod(godot_instance, _is_activity_resumed); - } else { - return false; - } -} - void GodotJavaWrapper::vibrate(int p_duration_ms) { if (_vibrate) { JNIEnv *env = get_jni_env(); diff --git a/platform/android/java_godot_wrapper.h b/platform/android/java_godot_wrapper.h index 1efdffd71b..ba42d5dccd 100644 --- a/platform/android/java_godot_wrapper.h +++ b/platform/android/java_godot_wrapper.h @@ -49,12 +49,10 @@ private: GodotJavaViewWrapper *godot_view = nullptr; - jmethodID _on_video_init = nullptr; jmethodID _restart = nullptr; jmethodID _finish = nullptr; jmethodID _set_keep_screen_on = nullptr; jmethodID _alert = nullptr; - jmethodID _get_GLES_version_code = nullptr; jmethodID _get_clipboard = nullptr; jmethodID _set_clipboard = nullptr; jmethodID _has_clipboard = nullptr; @@ -63,13 +61,10 @@ private: jmethodID _get_granted_permissions = nullptr; jmethodID _get_ca_certificates = nullptr; jmethodID _init_input_devices = nullptr; - jmethodID _get_surface = nullptr; - jmethodID _is_activity_resumed = nullptr; jmethodID _vibrate = nullptr; jmethodID _get_input_fallback_mapping = nullptr; jmethodID _on_godot_setup_completed = nullptr; jmethodID _on_godot_main_loop_started = nullptr; - jmethodID _get_class_loader = nullptr; jmethodID _create_new_godot_instance = nullptr; jmethodID _get_render_view = nullptr; jmethodID _begin_benchmark_measure = nullptr; @@ -81,19 +76,15 @@ public: ~GodotJavaWrapper(); jobject get_activity(); - jobject get_member_object(const char *p_name, const char *p_class, JNIEnv *p_env = nullptr); - jobject get_class_loader(); GodotJavaViewWrapper *get_godot_view(); - bool on_video_init(JNIEnv *p_env = nullptr); void on_godot_setup_completed(JNIEnv *p_env = nullptr); void on_godot_main_loop_started(JNIEnv *p_env = nullptr); void restart(JNIEnv *p_env = nullptr); bool force_quit(JNIEnv *p_env = nullptr, int p_instance_id = 0); void set_keep_screen_on(bool p_enabled); void alert(const String &p_message, const String &p_title); - int get_gles_version_code(); bool has_get_clipboard(); String get_clipboard(); bool has_set_clipboard(); @@ -105,8 +96,6 @@ public: Vector<String> get_granted_permissions() const; String get_ca_certificates() const; void init_input_devices(); - jobject get_surface(); - bool is_activity_resumed(); void vibrate(int p_duration_ms); String get_input_fallback_mapping(); int create_new_godot_instance(List<String> args); diff --git a/platform/ios/doc_classes/EditorExportPlatformIOS.xml b/platform/ios/doc_classes/EditorExportPlatformIOS.xml index 346cc9bf35..84bc0e1277 100644 --- a/platform/ios/doc_classes/EditorExportPlatformIOS.xml +++ b/platform/ios/doc_classes/EditorExportPlatformIOS.xml @@ -27,6 +27,9 @@ <member name="application/export_method_release" type="int" setter="" getter=""> Application distribution target (release export). </member> + <member name="application/export_project_only" type="bool" setter="" getter=""> + If [code]true[/code], exports iOS project files without building an XCArchive or [code].ipa[/code] file. If [code]false[/code], exports iOS project files and builds an XCArchive and [code].ipa[/code] file at the same time. When combining Godot with Fastlane or other build pipelines, you may want to set this to [code]true[/code]. + </member> <member name="application/icon_interpolation" type="int" setter="" getter=""> Interpolation method used to resize application icon. </member> diff --git a/platform/ios/export/export_plugin.cpp b/platform/ios/export/export_plugin.cpp index aab46a7854..544bfb71e0 100644 --- a/platform/ios/export/export_plugin.cpp +++ b/platform/ios/export/export_plugin.cpp @@ -182,6 +182,8 @@ void EditorExportPlatformIOS::get_export_options(List<ExportOption> *r_options) r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "application/icon_interpolation", PROPERTY_HINT_ENUM, "Nearest neighbor,Bilinear,Cubic,Trilinear,Lanczos"), 4)); r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "application/launch_screens_interpolation", PROPERTY_HINT_ENUM, "Nearest neighbor,Bilinear,Cubic,Trilinear,Lanczos"), 4)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "application/export_project_only"), false)); + Vector<PluginConfigIOS> found_plugins = get_plugins(); for (int i = 0; i < found_plugins.size(); i++) { r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, vformat("%s/%s", PNAME("plugins"), found_plugins[i].name)), false)); @@ -1489,7 +1491,9 @@ Error EditorExportPlatformIOS::_export_project_helper(const Ref<EditorExportPres String dest_dir = p_path.get_base_dir() + "/"; String binary_name = p_path.get_file().get_basename(); - EditorProgress ep("export", "Exporting for iOS", 5, true); + bool export_project_only = p_preset->get("application/export_project_only"); + + EditorProgress ep("export", export_project_only ? TTR("Exporting for iOS (Project Files Only)") : TTR("Exporting for iOS"), export_project_only ? 2 : 5, true); String team_id = p_preset->get("application/app_store_team_id"); ERR_FAIL_COND_V_MSG(team_id.length() == 0, ERR_CANT_OPEN, "App Store Team ID not specified - cannot configure the project."); @@ -1851,6 +1855,10 @@ Error EditorExportPlatformIOS::_export_project_helper(const Ref<EditorExportPres } } + if (export_project_only) { + return OK; + } + if (ep.step("Making .xcarchive", 3)) { return ERR_SKIP; } @@ -1917,16 +1925,15 @@ Error EditorExportPlatformIOS::_export_project_helper(const Ref<EditorExportPres } bool EditorExportPlatformIOS::has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug) const { - String err; - bool valid = false; - #ifdef MODULE_MONO_ENABLED - err += TTR("Exporting to iOS is currently not supported in Godot 4 when using C#/.NET. Use Godot 3 to target iOS with C#/Mono instead.") + "\n"; - err += TTR("If this project does not use C#, use a non-C# editor build to export the project.") + "\n"; // Don't check for additional errors, as this particular error cannot be resolved. - r_error = err; + r_error += TTR("Exporting to iOS is currently not supported in Godot 4 when using C#/.NET. Use Godot 3 to target iOS with C#/Mono instead.") + "\n"; + r_error += TTR("If this project does not use C#, use a non-C# editor build to export the project.") + "\n"; return false; -#endif +#else + + String err; + bool valid = false; // Look for export templates (first official, and if defined custom templates). @@ -1954,6 +1961,7 @@ bool EditorExportPlatformIOS::has_valid_export_configuration(const Ref<EditorExp } return valid; +#endif // !MODULE_MONO_ENABLED } bool EditorExportPlatformIOS::has_valid_project_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error) const { diff --git a/platform/ios/export/export_plugin.h b/platform/ios/export/export_plugin.h index a780669f18..7de4c0b69d 100644 --- a/platform/ios/export/export_plugin.h +++ b/platform/ios/export/export_plugin.h @@ -45,6 +45,7 @@ #include "editor/editor_settings.h" #include "editor/export/editor_export_platform.h" #include "main/splash.gen.h" +#include "scene/resources/image_texture.h" #include <string.h> #include <sys/stat.h> diff --git a/platform/linuxbsd/export/export_plugin.h b/platform/linuxbsd/export/export_plugin.h index cef714e86e..21bd81ed2f 100644 --- a/platform/linuxbsd/export/export_plugin.h +++ b/platform/linuxbsd/export/export_plugin.h @@ -34,7 +34,7 @@ #include "core/io/file_access.h" #include "editor/editor_settings.h" #include "editor/export/editor_export_platform_pc.h" -#include "scene/resources/texture.h" +#include "scene/resources/image_texture.h" class EditorExportPlatformLinuxBSD : public EditorExportPlatformPC { GDCLASS(EditorExportPlatformLinuxBSD, EditorExportPlatformPC); diff --git a/platform/linuxbsd/x11/display_server_x11.cpp b/platform/linuxbsd/x11/display_server_x11.cpp index a7f9475342..7189a1c1c9 100644 --- a/platform/linuxbsd/x11/display_server_x11.cpp +++ b/platform/linuxbsd/x11/display_server_x11.cpp @@ -40,7 +40,7 @@ #include "core/string/print_string.h" #include "core/string/ustring.h" #include "main/main.h" -#include "scene/resources/texture.h" +#include "scene/resources/atlas_texture.h" #if defined(VULKAN_ENABLED) #include "servers/rendering/renderer_rd/renderer_compositor_rd.h" diff --git a/platform/macos/display_server_macos.h b/platform/macos/display_server_macos.h index 2a7b2ce2a9..aef45e534f 100644 --- a/platform/macos/display_server_macos.h +++ b/platform/macos/display_server_macos.h @@ -315,6 +315,8 @@ public: virtual Error dialog_show(String p_title, String p_description, Vector<String> p_buttons, const Callable &p_callback) override; virtual Error dialog_input_text(String p_title, String p_description, String p_partial, const Callable &p_callback) override; + virtual Error file_dialog_show(const String &p_title, const String &p_current_directory, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const Callable &p_callback) override; + virtual void mouse_set_mode(MouseMode p_mode) override; virtual MouseMode mouse_get_mode() const override; diff --git a/platform/macos/display_server_macos.mm b/platform/macos/display_server_macos.mm index a4db78b697..ac0659ee7f 100644 --- a/platform/macos/display_server_macos.mm +++ b/platform/macos/display_server_macos.mm @@ -45,7 +45,8 @@ #include "core/math/geometry_2d.h" #include "core/os/keyboard.h" #include "main/main.h" -#include "scene/resources/texture.h" +#include "scene/resources/atlas_texture.h" +#include "scene/resources/image_texture.h" #if defined(GLES3_ENABLED) #include "drivers/gles3/rasterizer_gles3.h" @@ -1847,6 +1848,176 @@ Error DisplayServerMacOS::dialog_show(String p_title, String p_description, Vect return OK; } +Error DisplayServerMacOS::file_dialog_show(const String &p_title, const String &p_current_directory, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const Callable &p_callback) { + _THREAD_SAFE_METHOD_ + + NSString *url = [NSString stringWithUTF8String:p_current_directory.utf8().get_data()]; + NSMutableArray *allowed_types = [[NSMutableArray alloc] init]; + bool allow_other = false; + for (int i = 0; i < p_filters.size(); i++) { + Vector<String> tokens = p_filters[i].split(";"); + if (tokens.size() > 0) { + if (tokens[0].strip_edges() == "*.*") { + allow_other = true; + } else { + [allowed_types addObject:[NSString stringWithUTF8String:tokens[0].replace("*.", "").strip_edges().utf8().get_data()]]; + } + } + } + + Callable callback = p_callback; // Make a copy for async completion handler. + switch (p_mode) { + case FILE_DIALOG_MODE_SAVE_FILE: { + NSSavePanel *panel = [NSSavePanel savePanel]; + + [panel setDirectoryURL:[NSURL fileURLWithPath:url]]; + if ([allowed_types count]) { + [panel setAllowedFileTypes:allowed_types]; + } + [panel setAllowsOtherFileTypes:allow_other]; + [panel setExtensionHidden:YES]; + [panel setCanSelectHiddenExtension:YES]; + [panel setCanCreateDirectories:YES]; + [panel setShowsHiddenFiles:p_show_hidden]; + if (p_filename != "") { + NSString *fileurl = [NSString stringWithUTF8String:p_filename.utf8().get_data()]; + [panel setNameFieldStringValue:fileurl]; + } + + [panel beginSheetModalForWindow:[[NSApplication sharedApplication] mainWindow] + completionHandler:^(NSInteger ret) { + if (ret == NSModalResponseOK) { + // Save bookmark for folder. + if (OS::get_singleton()->is_sandboxed()) { + NSArray *bookmarks = [[NSUserDefaults standardUserDefaults] arrayForKey:@"sec_bookmarks"]; + bool skip = false; + for (id bookmark in bookmarks) { + NSError *error = nil; + BOOL isStale = NO; + NSURL *exurl = [NSURL URLByResolvingBookmarkData:bookmark options:NSURLBookmarkResolutionWithSecurityScope relativeToURL:nil bookmarkDataIsStale:&isStale error:&error]; + if (!error && !isStale && ([[exurl path] compare:[[panel directoryURL] path]] == NSOrderedSame)) { + skip = true; + break; + } + } + if (!skip) { + NSError *error = nil; + NSData *bookmark = [[panel directoryURL] bookmarkDataWithOptions:NSURLBookmarkCreationWithSecurityScope includingResourceValuesForKeys:nil relativeToURL:nil error:&error]; + if (!error) { + NSArray *new_bookmarks = [bookmarks arrayByAddingObject:bookmark]; + [[NSUserDefaults standardUserDefaults] setObject:new_bookmarks forKey:@"sec_bookmarks"]; + } + } + } + // Callback. + Vector<String> files; + String url; + url.parse_utf8([[[panel URL] path] UTF8String]); + files.push_back(url); + if (!callback.is_null()) { + Variant v_status = true; + Variant v_files = files; + Variant *v_args[2] = { &v_status, &v_files }; + Variant ret; + Callable::CallError ce; + callback.callp((const Variant **)&v_args, 2, ret, ce); + } + } else { + if (!callback.is_null()) { + Variant v_status = false; + Variant v_files = Vector<String>(); + Variant *v_args[2] = { &v_status, &v_files }; + Variant ret; + Callable::CallError ce; + callback.callp((const Variant **)&v_args, 2, ret, ce); + } + } + }]; + } break; + case FILE_DIALOG_MODE_OPEN_ANY: + case FILE_DIALOG_MODE_OPEN_FILE: + case FILE_DIALOG_MODE_OPEN_FILES: + case FILE_DIALOG_MODE_OPEN_DIR: { + NSOpenPanel *panel = [NSOpenPanel openPanel]; + + [panel setDirectoryURL:[NSURL fileURLWithPath:url]]; + if ([allowed_types count]) { + [panel setAllowedFileTypes:allowed_types]; + } + [panel setAllowsOtherFileTypes:allow_other]; + [panel setExtensionHidden:YES]; + [panel setCanSelectHiddenExtension:YES]; + [panel setCanCreateDirectories:YES]; + [panel setCanChooseFiles:(p_mode != FILE_DIALOG_MODE_OPEN_DIR)]; + [panel setCanChooseDirectories:(p_mode == FILE_DIALOG_MODE_OPEN_DIR || p_mode == FILE_DIALOG_MODE_OPEN_ANY)]; + [panel setShowsHiddenFiles:p_show_hidden]; + if (p_filename != "") { + NSString *fileurl = [NSString stringWithUTF8String:p_filename.utf8().get_data()]; + [panel setNameFieldStringValue:fileurl]; + } + [panel setAllowsMultipleSelection:(p_mode == FILE_DIALOG_MODE_OPEN_FILES)]; + + [panel beginSheetModalForWindow:[[NSApplication sharedApplication] mainWindow] + completionHandler:^(NSInteger ret) { + if (ret == NSModalResponseOK) { + // Save bookmark for folder. + NSArray *urls = [(NSOpenPanel *)panel URLs]; + if (OS::get_singleton()->is_sandboxed()) { + NSArray *bookmarks = [[NSUserDefaults standardUserDefaults] arrayForKey:@"sec_bookmarks"]; + NSMutableArray *new_bookmarks = [bookmarks mutableCopy]; + for (NSUInteger i = 0; i != [urls count]; ++i) { + bool skip = false; + for (id bookmark in bookmarks) { + NSError *error = nil; + BOOL isStale = NO; + NSURL *exurl = [NSURL URLByResolvingBookmarkData:bookmark options:NSURLBookmarkResolutionWithSecurityScope relativeToURL:nil bookmarkDataIsStale:&isStale error:&error]; + if (!error && !isStale && ([[exurl path] compare:[[urls objectAtIndex:i] path]] == NSOrderedSame)) { + skip = true; + break; + } + } + if (!skip) { + NSError *error = nil; + NSData *bookmark = [[urls objectAtIndex:i] bookmarkDataWithOptions:NSURLBookmarkCreationWithSecurityScope includingResourceValuesForKeys:nil relativeToURL:nil error:&error]; + if (!error) { + [new_bookmarks addObject:bookmark]; + } + } + } + [[NSUserDefaults standardUserDefaults] setObject:new_bookmarks forKey:@"sec_bookmarks"]; + } + // Callback. + Vector<String> files; + for (NSUInteger i = 0; i != [urls count]; ++i) { + String url; + url.parse_utf8([[[urls objectAtIndex:i] path] UTF8String]); + files.push_back(url); + } + if (!callback.is_null()) { + Variant v_status = true; + Variant v_files = files; + Variant *v_args[2] = { &v_status, &v_files }; + Variant ret; + Callable::CallError ce; + callback.callp((const Variant **)&v_args, 2, ret, ce); + } + } else { + if (!callback.is_null()) { + Variant v_status = false; + Variant v_files = Vector<String>(); + Variant *v_args[2] = { &v_status, &v_files }; + Variant ret; + Callable::CallError ce; + callback.callp((const Variant **)&v_args, 2, ret, ce); + } + } + }]; + } break; + } + + return OK; +} + Error DisplayServerMacOS::dialog_input_text(String p_title, String p_description, String p_partial, const Callable &p_callback) { _THREAD_SAFE_METHOD_ @@ -3085,14 +3256,14 @@ bool DisplayServerMacOS::window_is_focused(WindowID p_window) const { } bool DisplayServerMacOS::window_can_draw(WindowID p_window) const { - return window_get_mode(p_window) != WINDOW_MODE_MINIMIZED; + return (window_get_mode(p_window) != WINDOW_MODE_MINIMIZED) && [windows[p_window].window_object isOnActiveSpace]; } bool DisplayServerMacOS::can_any_window_draw() const { _THREAD_SAFE_METHOD_ for (const KeyValue<WindowID, WindowData> &E : windows) { - if (window_get_mode(E.key) != WINDOW_MODE_MINIMIZED) { + if ((window_get_mode(E.key) != WINDOW_MODE_MINIMIZED) && [E.value.window_object isOnActiveSpace]) { return true; } } diff --git a/platform/macos/doc_classes/EditorExportPlatformMacOS.xml b/platform/macos/doc_classes/EditorExportPlatformMacOS.xml index 9199701eb3..6af816989d 100644 --- a/platform/macos/doc_classes/EditorExportPlatformMacOS.xml +++ b/platform/macos/doc_classes/EditorExportPlatformMacOS.xml @@ -96,6 +96,9 @@ <member name="codesign/entitlements/app_sandbox/files_pictures" type="int" setter="" getter=""> Allows read or write access to the user's "Pictures" folder. See [url=https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_assets_pictures_read-write]com.apple.security.files.pictures.read-write[/url]. </member> + <member name="codesign/entitlements/app_sandbox/files_user_selected" type="int" setter="" getter=""> + Allows read or write access to the locations the user has selected using a native file dialog. See [url=https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_files_user-selected_read-write]com.apple.security.files.user-selected.read-write[/url]. + </member> <member name="codesign/entitlements/app_sandbox/helper_executables" type="Array" setter="" getter=""> List of helper executables to embedded to the app bundle. Sandboxed app are limited to execute only these executable. See [url=https://developer.apple.com/documentation/xcode/embedding-a-helper-tool-in-a-sandboxed-app]Embedding a command-line tool in a sandboxed app[/url]. </member> diff --git a/platform/macos/export/export_plugin.cpp b/platform/macos/export/export_plugin.cpp index 0dc6d0dcee..81f9707f6b 100644 --- a/platform/macos/export/export_plugin.cpp +++ b/platform/macos/export/export_plugin.cpp @@ -41,6 +41,7 @@ #include "editor/editor_node.h" #include "editor/editor_paths.h" #include "editor/editor_scale.h" +#include "scene/resources/image_texture.h" #include "modules/modules_enabled.gen.h" // For svg and regex. #ifdef MODULE_SVG_ENABLED @@ -425,6 +426,7 @@ void EditorExportPlatformMacOS::get_export_options(List<ExportOption> *r_options r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "codesign/entitlements/app_sandbox/files_pictures", PROPERTY_HINT_ENUM, "No,Read-only,Read-write"), 0)); r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "codesign/entitlements/app_sandbox/files_music", PROPERTY_HINT_ENUM, "No,Read-only,Read-write"), 0)); r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "codesign/entitlements/app_sandbox/files_movies", PROPERTY_HINT_ENUM, "No,Read-only,Read-write"), 0)); + r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "codesign/entitlements/app_sandbox/files_user_selected", PROPERTY_HINT_ENUM, "No,Read-only,Read-write"), 0)); r_options->push_back(ExportOption(PropertyInfo(Variant::ARRAY, "codesign/entitlements/app_sandbox/helper_executables", PROPERTY_HINT_ARRAY_TYPE, itos(Variant::STRING) + "/" + itos(PROPERTY_HINT_GLOBAL_FILE) + ":"), Array())); r_options->push_back(ExportOption(PropertyInfo(Variant::PACKED_STRING_ARRAY, "codesign/custom_options"), PackedStringArray())); @@ -1359,7 +1361,7 @@ Error EditorExportPlatformMacOS::export_project(const Ref<EditorExportPreset> &p String src_pkg_name; - EditorProgress ep("export", "Exporting for macOS", 3, true); + EditorProgress ep("export", TTR("Exporting for macOS"), 3, true); if (p_debug) { src_pkg_name = p_preset->get("custom_template/debug"); @@ -1922,6 +1924,14 @@ Error EditorExportPlatformMacOS::export_project(const Ref<EditorExportPreset> &p ent_f->store_line("<key>com.apple.security.files.movies.read-write</key>"); ent_f->store_line("<true/>"); } + if ((int)p_preset->get("codesign/entitlements/app_sandbox/files_user_selected") == 1) { + ent_f->store_line("<key>com.apple.security.files.user-selected.read-only</key>"); + ent_f->store_line("<true/>"); + } + if ((int)p_preset->get("codesign/entitlements/app_sandbox/files_user_selected") == 2) { + ent_f->store_line("<key>com.apple.security.files.user-selected.read-write</key>"); + ent_f->store_line("<true/>"); + } } ent_f->store_line("</dict>"); diff --git a/platform/macos/os_macos.h b/platform/macos/os_macos.h index ab61649d19..ae94b6296d 100644 --- a/platform/macos/os_macos.h +++ b/platform/macos/os_macos.h @@ -113,6 +113,10 @@ public: virtual String get_unique_id() const override; virtual String get_processor_name() const override; + virtual bool is_sandboxed() const override; + virtual Vector<String> get_granted_permissions() const override; + virtual void revoke_granted_permissions() override; + virtual bool _check_internal_feature_support(const String &p_feature) override; virtual void disable_crash_handler() override; diff --git a/platform/macos/os_macos.mm b/platform/macos/os_macos.mm index 69062c5920..c17ea95f4f 100644 --- a/platform/macos/os_macos.mm +++ b/platform/macos/os_macos.mm @@ -76,6 +76,36 @@ String OS_MacOS::get_processor_name() const { ERR_FAIL_V_MSG("", String("Couldn't get the CPU model name. Returning an empty string.")); } +bool OS_MacOS::is_sandboxed() const { + return has_environment("APP_SANDBOX_CONTAINER_ID"); +} + +Vector<String> OS_MacOS::get_granted_permissions() const { + Vector<String> ret; + + if (is_sandboxed()) { + NSArray *bookmarks = [[NSUserDefaults standardUserDefaults] arrayForKey:@"sec_bookmarks"]; + for (id bookmark in bookmarks) { + NSError *error = nil; + BOOL isStale = NO; + NSURL *url = [NSURL URLByResolvingBookmarkData:bookmark options:NSURLBookmarkResolutionWithSecurityScope relativeToURL:nil bookmarkDataIsStale:&isStale error:&error]; + if (!error && !isStale) { + String url_string; + url_string.parse_utf8([[url path] UTF8String]); + ret.push_back(url_string); + } + } + } + + return ret; +} + +void OS_MacOS::revoke_granted_permissions() { + if (is_sandboxed()) { + [[NSUserDefaults standardUserDefaults] setObject:nil forKey:@"sec_bookmarks"]; + } +} + void OS_MacOS::initialize_core() { OS_Unix::initialize_core(); @@ -85,6 +115,18 @@ void OS_MacOS::initialize_core() { } void OS_MacOS::finalize() { + if (is_sandboxed()) { + NSArray *bookmarks = [[NSUserDefaults standardUserDefaults] arrayForKey:@"sec_bookmarks"]; + for (id bookmark in bookmarks) { + NSError *error = nil; + BOOL isStale = NO; + NSURL *url = [NSURL URLByResolvingBookmarkData:bookmark options:NSURLBookmarkResolutionWithSecurityScope relativeToURL:nil bookmarkDataIsStale:&isStale error:&error]; + if (!error && !isStale) { + [url stopAccessingSecurityScopedResource]; + } + } + } + #ifdef COREMIDI_ENABLED midi_driver.close(); #endif @@ -733,6 +775,23 @@ void OS_MacOS::run() { } OS_MacOS::OS_MacOS() { + if (is_sandboxed()) { + // Load security-scoped bookmarks, request access, remove stale or invalid bookmarks. + NSArray *bookmarks = [[NSUserDefaults standardUserDefaults] arrayForKey:@"sec_bookmarks"]; + NSMutableArray *new_bookmarks = [[NSMutableArray alloc] init]; + for (id bookmark in bookmarks) { + NSError *error = nil; + BOOL isStale = NO; + NSURL *url = [NSURL URLByResolvingBookmarkData:bookmark options:NSURLBookmarkResolutionWithSecurityScope relativeToURL:nil bookmarkDataIsStale:&isStale error:&error]; + if (!error && !isStale) { + if ([url startAccessingSecurityScopedResource]) { + [new_bookmarks addObject:bookmark]; + } + } + } + [[NSUserDefaults standardUserDefaults] setObject:new_bookmarks forKey:@"sec_bookmarks"]; + } + main_loop = nullptr; Vector<Logger *> loggers; diff --git a/platform/uwp/export/export_plugin.cpp b/platform/uwp/export/export_plugin.cpp index 45b484c39e..c92520b755 100644 --- a/platform/uwp/export/export_plugin.cpp +++ b/platform/uwp/export/export_plugin.cpp @@ -34,6 +34,7 @@ #include "editor/editor_scale.h" #include "editor/editor_settings.h" +#include "scene/resources/image_texture.h" #include "modules/modules_enabled.gen.h" // For svg and regex. #ifdef MODULE_SVG_ENABLED @@ -135,7 +136,7 @@ bool EditorExportPlatformUWP::has_valid_export_configuration(const Ref<EditorExp // contributors can still test the pipeline if/when we can build it again. r_error = "The UWP platform is currently not supported in Godot 4.\n"; return false; -#endif +#else String err; bool valid = false; @@ -175,6 +176,7 @@ bool EditorExportPlatformUWP::has_valid_export_configuration(const Ref<EditorExp } return valid; +#endif // DEV_ENABLED } bool EditorExportPlatformUWP::has_valid_project_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error) const { @@ -184,7 +186,7 @@ bool EditorExportPlatformUWP::has_valid_project_configuration(const Ref<EditorEx // contributors can still test the pipeline if/when we can build it again. r_error = "The UWP platform is currently not supported in Godot 4.\n"; return false; -#endif +#else String err; bool valid = true; @@ -258,6 +260,7 @@ bool EditorExportPlatformUWP::has_valid_project_configuration(const Ref<EditorEx r_error = err; return valid; +#endif // DEV_ENABLED } Error EditorExportPlatformUWP::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) { @@ -265,7 +268,7 @@ Error EditorExportPlatformUWP::export_project(const Ref<EditorExportPreset> &p_p String src_appx; - EditorProgress ep("export", "Exporting for UWP", 7, true); + EditorProgress ep("export", TTR("Exporting for UWP"), 7, true); if (p_debug) { src_appx = p_preset->get("custom_template/debug"); diff --git a/platform/uwp/export/export_plugin.h b/platform/uwp/export/export_plugin.h index cc86bdb280..147279e5c5 100644 --- a/platform/uwp/export/export_plugin.h +++ b/platform/uwp/export/export_plugin.h @@ -44,6 +44,7 @@ #include "editor/editor_node.h" #include "editor/editor_paths.h" #include "editor/export/editor_export_platform.h" +#include "scene/resources/compressed_texture.h" #include "thirdparty/minizip/unzip.h" #include "thirdparty/minizip/zip.h" diff --git a/platform/web/display_server_web.cpp b/platform/web/display_server_web.cpp index 941f2c7ef3..93b0496d74 100644 --- a/platform/web/display_server_web.cpp +++ b/platform/web/display_server_web.cpp @@ -35,6 +35,7 @@ #include "os_web.h" #include "core/config/project_settings.h" +#include "scene/resources/atlas_texture.h" #include "servers/rendering/dummy/rasterizer_dummy.h" #ifdef GLES3_ENABLED diff --git a/platform/web/export/export_plugin.cpp b/platform/web/export/export_plugin.cpp index 255aefe6e7..38e2714d9f 100644 --- a/platform/web/export/export_plugin.cpp +++ b/platform/web/export/export_plugin.cpp @@ -37,6 +37,7 @@ #include "editor/editor_scale.h" #include "editor/editor_settings.h" #include "editor/export/editor_export.h" +#include "scene/resources/image_texture.h" #include "modules/modules_enabled.gen.h" // For mono and svg. #ifdef MODULE_SVG_ENABLED @@ -359,17 +360,16 @@ Ref<Texture2D> EditorExportPlatformWeb::get_logo() const { } bool EditorExportPlatformWeb::has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug) const { - String err; - bool valid = false; - bool extensions = (bool)p_preset->get("variant/extensions_support"); - #ifdef MODULE_MONO_ENABLED - err += TTR("Exporting to Web is currently not supported in Godot 4 when using C#/.NET. Use Godot 3 to target Web with C#/Mono instead.") + "\n"; - err += TTR("If this project does not use C#, use a non-C# editor build to export the project.") + "\n"; // Don't check for additional errors, as this particular error cannot be resolved. - r_error = err; + r_error += TTR("Exporting to Web is currently not supported in Godot 4 when using C#/.NET. Use Godot 3 to target Web with C#/Mono instead.") + "\n"; + r_error += TTR("If this project does not use C#, use a non-C# editor build to export the project.") + "\n"; return false; -#endif +#else + + String err; + bool valid = false; + bool extensions = (bool)p_preset->get("variant/extensions_support"); // Look for export templates (first official, and if defined custom templates). bool dvalid = exists_export_template(_get_template_name(extensions, true), &err); @@ -396,6 +396,7 @@ bool EditorExportPlatformWeb::has_valid_export_configuration(const Ref<EditorExp } return valid; +#endif // !MODULE_MONO_ENABLED } bool EditorExportPlatformWeb::has_valid_project_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error) const { diff --git a/platform/web/http_client_web.cpp b/platform/web/http_client_web.cpp index 3e4ba5a2ae..ea9226a5a4 100644 --- a/platform/web/http_client_web.cpp +++ b/platform/web/http_client_web.cpp @@ -149,7 +149,15 @@ Error HTTPClientWeb::get_response_headers(List<String> *r_response) { } int64_t HTTPClientWeb::get_response_body_length() const { - return godot_js_fetch_body_length_get(js_id); + // Body length cannot be consistently retrieved from the web. + // Reading the "content-length" value will return a meaningless value when the response is compressed, + // as reading will return uncompressed chunks in any case, resulting in a mismatch between the detected + // body size and the actual size returned by repeatedly calling read_response_body_chunk. + // Additionally, while "content-length" is considered a safe CORS header, "content-encoding" is not, + // so using the "content-encoding" to decide if "content-length" is meaningful is not an option either. + // We simply must accept the fact that browsers are awful when it comes to networking APIs. + // See GH-47597, and GH-79327. + return -1; } PackedByteArray HTTPClientWeb::read_response_body_chunk() { diff --git a/platform/web/http_client_web.h b/platform/web/http_client_web.h index bb9672ab82..4d3c457a7d 100644 --- a/platform/web/http_client_web.h +++ b/platform/web/http_client_web.h @@ -51,7 +51,6 @@ extern int godot_js_fetch_read_headers(int p_id, void (*parse_callback)(int p_si extern int godot_js_fetch_read_chunk(int p_id, uint8_t *p_buf, int p_buf_size); extern void godot_js_fetch_free(int p_id); extern godot_js_fetch_state_t godot_js_fetch_state_get(int p_id); -extern int godot_js_fetch_body_length_get(int p_id); extern int godot_js_fetch_http_status_get(int p_id); extern int godot_js_fetch_is_chunked(int p_id); diff --git a/platform/web/js/libs/library_godot_fetch.js b/platform/web/js/libs/library_godot_fetch.js index 1bb48bfd6a..4ef24903e3 100644 --- a/platform/web/js/libs/library_godot_fetch.js +++ b/platform/web/js/libs/library_godot_fetch.js @@ -50,22 +50,17 @@ const GodotFetch = { return; } let chunked = false; - let bodySize = -1; response.headers.forEach(function (value, header) { const v = value.toLowerCase().trim(); const h = header.toLowerCase().trim(); if (h === 'transfer-encoding' && v === 'chunked') { chunked = true; } - if (h === 'content-length') { - bodySize = parseInt(v, 10); - } }); obj.status = response.status; obj.response = response; obj.reader = response.body.getReader(); obj.chunked = chunked; - obj.bodySize = bodySize; }, onerror: function (id, err) { @@ -87,7 +82,6 @@ const GodotFetch = { reading: false, status: 0, chunks: [], - bodySize: -1, }; const id = IDHandler.add(obj); const init = { @@ -224,15 +218,6 @@ const GodotFetch = { return p_buf_size - to_read; }, - godot_js_fetch_body_length_get__sig: 'ii', - godot_js_fetch_body_length_get: function (p_id) { - const obj = IDHandler.get(p_id); - if (!obj || !obj.response) { - return -1; - } - return obj.bodySize; - }, - godot_js_fetch_is_chunked__sig: 'ii', godot_js_fetch_is_chunked: function (p_id) { const obj = IDHandler.get(p_id); diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp index f88cbd44b7..c664752c2f 100644 --- a/platform/windows/display_server_windows.cpp +++ b/platform/windows/display_server_windows.cpp @@ -32,9 +32,10 @@ #include "os_windows.h" +#include "core/config/project_settings.h" #include "core/io/marshalls.h" #include "main/main.h" -#include "scene/resources/texture.h" +#include "scene/resources/atlas_texture.h" #if defined(GLES3_ENABLED) #include "drivers/gles3/rasterizer_gles3.h" @@ -42,6 +43,8 @@ #include <avrt.h> #include <dwmapi.h> +#include <shlwapi.h> +#include <shobjidl.h> #ifndef DWMWA_USE_IMMERSIVE_DARK_MODE #define DWMWA_USE_IMMERSIVE_DARK_MODE 20 @@ -87,6 +90,7 @@ bool DisplayServerWindows::has_feature(Feature p_feature) const { case FEATURE_HIDPI: case FEATURE_ICON: case FEATURE_NATIVE_ICON: + case FEATURE_NATIVE_DIALOG: case FEATURE_SWAP_BUFFERS: case FEATURE_KEEP_SCREEN_ON: case FEATURE_TEXT_TO_SPEECH: @@ -213,6 +217,129 @@ void DisplayServerWindows::tts_stop() { tts->stop(); } +Error DisplayServerWindows::file_dialog_show(const String &p_title, const String &p_current_directory, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const Callable &p_callback) { + _THREAD_SAFE_METHOD_ + + Vector<Char16String> filter_names; + Vector<Char16String> filter_exts; + for (const String &E : p_filters) { + Vector<String> tokens = E.split(";"); + if (tokens.size() == 2) { + filter_exts.push_back(tokens[0].strip_edges().utf16()); + filter_names.push_back(tokens[1].strip_edges().utf16()); + } else if (tokens.size() == 1) { + filter_exts.push_back(tokens[0].strip_edges().utf16()); + filter_names.push_back(tokens[0].strip_edges().utf16()); + } + } + + Vector<COMDLG_FILTERSPEC> filters; + for (int i = 0; i < filter_names.size(); i++) { + filters.push_back({ (LPCWSTR)filter_names[i].ptr(), (LPCWSTR)filter_exts[i].ptr() }); + } + + HRESULT hr = S_OK; + IFileDialog *pfd = nullptr; + if (p_mode == FILE_DIALOG_MODE_SAVE_FILE) { + hr = CoCreateInstance(CLSID_FileSaveDialog, nullptr, CLSCTX_INPROC_SERVER, IID_IFileSaveDialog, (void **)&pfd); + } else { + hr = CoCreateInstance(CLSID_FileOpenDialog, nullptr, CLSCTX_INPROC_SERVER, IID_IFileOpenDialog, (void **)&pfd); + } + if (SUCCEEDED(hr)) { + DWORD flags; + pfd->GetOptions(&flags); + if (p_mode == FILE_DIALOG_MODE_OPEN_FILES) { + flags |= FOS_ALLOWMULTISELECT; + } + if (p_mode == FILE_DIALOG_MODE_OPEN_DIR) { + flags |= FOS_PICKFOLDERS; + } + if (p_show_hidden) { + flags |= FOS_FORCESHOWHIDDEN; + } + pfd->SetOptions(flags | FOS_FORCEFILESYSTEM); + pfd->SetTitle((LPCWSTR)p_title.utf16().ptr()); + + String dir = ProjectSettings::get_singleton()->globalize_path(p_current_directory); + if (dir == ".") { + dir = OS::get_singleton()->get_executable_path().get_base_dir(); + } + dir = dir.replace("/", "\\"); + + IShellItem *shellitem = nullptr; + hr = SHCreateItemFromParsingName((LPCWSTR)dir.utf16().ptr(), nullptr, IID_IShellItem, (void **)&shellitem); + if (SUCCEEDED(hr)) { + pfd->SetDefaultFolder(shellitem); + pfd->SetFolder(shellitem); + } + + pfd->SetFileName((LPCWSTR)p_filename.utf16().ptr()); + pfd->SetFileTypes(filters.size(), filters.ptr()); + pfd->SetFileTypeIndex(0); + + hr = pfd->Show(nullptr); + if (SUCCEEDED(hr)) { + Vector<String> file_names; + + if (p_mode == FILE_DIALOG_MODE_OPEN_FILES) { + IShellItemArray *results; + hr = static_cast<IFileOpenDialog *>(pfd)->GetResults(&results); + if (SUCCEEDED(hr)) { + DWORD count = 0; + results->GetCount(&count); + for (DWORD i = 0; i < count; i++) { + IShellItem *result; + results->GetItemAt(i, &result); + + PWSTR file_path = nullptr; + hr = result->GetDisplayName(SIGDN_FILESYSPATH, &file_path); + if (SUCCEEDED(hr)) { + file_names.push_back(String::utf16((const char16_t *)file_path)); + CoTaskMemFree(file_path); + } + result->Release(); + } + results->Release(); + } + } else { + IShellItem *result; + hr = pfd->GetResult(&result); + if (SUCCEEDED(hr)) { + PWSTR file_path = nullptr; + hr = result->GetDisplayName(SIGDN_FILESYSPATH, &file_path); + if (SUCCEEDED(hr)) { + file_names.push_back(String::utf16((const char16_t *)file_path)); + CoTaskMemFree(file_path); + } + result->Release(); + } + } + if (!p_callback.is_null()) { + Variant v_status = true; + Variant v_files = file_names; + Variant *v_args[2] = { &v_status, &v_files }; + Variant ret; + Callable::CallError ce; + p_callback.callp((const Variant **)&v_args, 2, ret, ce); + } + } else { + if (!p_callback.is_null()) { + Variant v_status = false; + Variant v_files = Vector<String>(); + Variant *v_args[2] = { &v_status, &v_files }; + Variant ret; + Callable::CallError ce; + p_callback.callp((const Variant **)&v_args, 2, ret, ce); + } + } + pfd->Release(); + + return OK; + } else { + return ERR_CANT_OPEN; + } +} + void DisplayServerWindows::mouse_set_mode(MouseMode p_mode) { _THREAD_SAFE_METHOD_ @@ -618,7 +745,7 @@ Color DisplayServerWindows::screen_get_pixel(const Point2i &p_position) const { COLORREF col = GetPixel(dc, p.x, p.y); if (col != CLR_INVALID) { ReleaseDC(NULL, dc); - return Color(float(col & 0x000000FF) / 256.0, float((col & 0x0000FF00) >> 8) / 256.0, float((col & 0x00FF0000) >> 16) / 256.0, 1.0); + return Color(float(col & 0x000000FF) / 255.0f, float((col & 0x0000FF00) >> 8) / 255.0f, float((col & 0x00FF0000) >> 16) / 255.0f, 1.0f); } ReleaseDC(NULL, dc); } @@ -2282,8 +2409,8 @@ void DisplayServerWindows::set_icon(const Ref<Image> &p_icon) { SendMessage(windows[MAIN_WINDOW_ID].hWnd, WM_SETICON, ICON_BIG, (LPARAM)hicon); } else { icon = Ref<Image>(); - SendMessage(windows[MAIN_WINDOW_ID].hWnd, WM_SETICON, ICON_SMALL, NULL); - SendMessage(windows[MAIN_WINDOW_ID].hWnd, WM_SETICON, ICON_BIG, NULL); + SendMessage(windows[MAIN_WINDOW_ID].hWnd, WM_SETICON, ICON_SMALL, 0); + SendMessage(windows[MAIN_WINDOW_ID].hWnd, WM_SETICON, ICON_BIG, 0); } } diff --git a/platform/windows/display_server_windows.h b/platform/windows/display_server_windows.h index 9d1088675b..bd47dee9ec 100644 --- a/platform/windows/display_server_windows.h +++ b/platform/windows/display_server_windows.h @@ -511,6 +511,8 @@ public: virtual bool is_dark_mode() const override; virtual Color get_accent_color() const override; + virtual Error file_dialog_show(const String &p_title, const String &p_current_directory, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const Callable &p_callback) override; + virtual void mouse_set_mode(MouseMode p_mode) override; virtual MouseMode mouse_get_mode() const override; diff --git a/platform/windows/gl_manager_windows.cpp b/platform/windows/gl_manager_windows.cpp index 0334bdd973..d3972c7bbc 100644 --- a/platform/windows/gl_manager_windows.cpp +++ b/platform/windows/gl_manager_windows.cpp @@ -53,6 +53,7 @@ #if defined(__GNUC__) // Workaround GCC warning from -Wcast-function-type. #define wglGetProcAddress (void *)wglGetProcAddress +#define GetProcAddress (void *)GetProcAddress #endif typedef HGLRC(APIENTRY *PFNWGLCREATECONTEXTATTRIBSARBPROC)(HDC, HGLRC, const int *); diff --git a/scene/2d/camera_2d.cpp b/scene/2d/camera_2d.cpp index 48445496aa..e7003ab98b 100644 --- a/scene/2d/camera_2d.cpp +++ b/scene/2d/camera_2d.cpp @@ -41,7 +41,7 @@ void Camera2D::_update_scroll() { if (Engine::get_singleton()->is_editor_hint()) { queue_redraw(); // Only set viewport transform when not bound to the main viewport. - if (get_viewport() == get_tree()->get_edited_scene_root()->get_viewport()) { + if (get_tree()->get_edited_scene_root() && get_viewport() == get_tree()->get_edited_scene_root()->get_viewport()) { return; } } diff --git a/scene/2d/collision_shape_2d.cpp b/scene/2d/collision_shape_2d.cpp index 10fc7ef5b2..70ec57aa7a 100644 --- a/scene/2d/collision_shape_2d.cpp +++ b/scene/2d/collision_shape_2d.cpp @@ -142,7 +142,7 @@ void CollisionShape2D::set_shape(const Ref<Shape2D> &p_shape) { return; } if (shape.is_valid()) { - shape->disconnect("changed", callable_mp(this, &CollisionShape2D::_shape_changed)); + shape->disconnect_changed(callable_mp(this, &CollisionShape2D::_shape_changed)); } shape = p_shape; queue_redraw(); @@ -155,7 +155,7 @@ void CollisionShape2D::set_shape(const Ref<Shape2D> &p_shape) { } if (shape.is_valid()) { - shape->connect("changed", callable_mp(this, &CollisionShape2D::_shape_changed)); + shape->connect_changed(callable_mp(this, &CollisionShape2D::_shape_changed)); } update_configuration_warnings(); diff --git a/scene/2d/cpu_particles_2d.cpp b/scene/2d/cpu_particles_2d.cpp index 115104adff..1c193f991e 100644 --- a/scene/2d/cpu_particles_2d.cpp +++ b/scene/2d/cpu_particles_2d.cpp @@ -30,9 +30,12 @@ #include "cpu_particles_2d.h" -#include "core/core_string_names.h" #include "scene/2d/gpu_particles_2d.h" +#include "scene/resources/atlas_texture.h" +#include "scene/resources/curve_texture.h" +#include "scene/resources/gradient_texture.h" #include "scene/resources/particle_process_material.h" +#include "scene/scene_string_names.h" void CPUParticles2D::set_emitting(bool p_emitting) { if (emitting == p_emitting) { @@ -41,6 +44,7 @@ void CPUParticles2D::set_emitting(bool p_emitting) { emitting = p_emitting; if (emitting) { + active = true; set_process_internal(true); } } @@ -202,13 +206,13 @@ void CPUParticles2D::set_texture(const Ref<Texture2D> &p_texture) { } if (texture.is_valid()) { - texture->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(this, &CPUParticles2D::_texture_changed)); + texture->disconnect_changed(callable_mp(this, &CPUParticles2D::_texture_changed)); } texture = p_texture; if (texture.is_valid()) { - texture->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &CPUParticles2D::_texture_changed)); + texture->connect_changed(callable_mp(this, &CPUParticles2D::_texture_changed)); } queue_redraw(); @@ -259,7 +263,6 @@ PackedStringArray CPUParticles2D::get_configuration_warnings() const { void CPUParticles2D::restart() { time = 0; - inactive_time = 0; frame_remainder = 0; cycle = 0; emitting = false; @@ -561,21 +564,15 @@ void CPUParticles2D::_update_internal() { } double delta = get_process_delta_time(); - if (emitting) { - inactive_time = 0; - } else { - inactive_time += delta; - if (inactive_time > lifetime * 1.2) { - set_process_internal(false); - _set_do_redraw(false); + if (!active && !emitting) { + set_process_internal(false); + _set_do_redraw(false); - //reset variables - time = 0; - inactive_time = 0; - frame_remainder = 0; - cycle = 0; - return; - } + //reset variables + time = 0; + frame_remainder = 0; + cycle = 0; + return; } _set_do_redraw(true); @@ -650,6 +647,7 @@ void CPUParticles2D::_particles_process(double p_delta) { double system_phase = time / lifetime; + bool should_be_active = false; for (int i = 0; i < pcount; i++) { Particle &p = parray[i]; @@ -994,6 +992,12 @@ void CPUParticles2D::_particles_process(double p_delta) { p.transform.columns[1] *= base_scale.y; p.transform[2] += p.velocity * local_delta; + + should_be_active = true; + } + if (!Math::is_equal_approx(time, 0.0) && active && !should_be_active) { + active = false; + emit_signal(SceneStringNames::get_singleton()->finished); } } @@ -1364,6 +1368,8 @@ void CPUParticles2D::_bind_methods() { ClassDB::bind_method(D_METHOD("convert_from_particles", "particles"), &CPUParticles2D::convert_from_particles); + ADD_SIGNAL(MethodInfo("finished")); + ADD_GROUP("Emission Shape", "emission_"); ADD_PROPERTY(PropertyInfo(Variant::INT, "emission_shape", PROPERTY_HINT_ENUM, "Point,Sphere,Sphere Surface,Rectangle,Points,Directed Points", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), "set_emission_shape", "get_emission_shape"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_sphere_radius", PROPERTY_HINT_RANGE, "0.01,128,0.01,suffix:px"), "set_emission_sphere_radius", "get_emission_sphere_radius"); diff --git a/scene/2d/cpu_particles_2d.h b/scene/2d/cpu_particles_2d.h index d4ff999459..3f858c3277 100644 --- a/scene/2d/cpu_particles_2d.h +++ b/scene/2d/cpu_particles_2d.h @@ -78,6 +78,7 @@ public: private: bool emitting = false; + bool active = false; struct Particle { Transform2D transform; @@ -99,7 +100,6 @@ private: }; double time = 0.0; - double inactive_time = 0.0; double frame_remainder = 0.0; int cycle = 0; bool do_redraw = false; diff --git a/scene/2d/gpu_particles_2d.cpp b/scene/2d/gpu_particles_2d.cpp index 735159c660..8c5782dc41 100644 --- a/scene/2d/gpu_particles_2d.cpp +++ b/scene/2d/gpu_particles_2d.cpp @@ -30,21 +30,39 @@ #include "gpu_particles_2d.h" -#include "core/core_string_names.h" +#include "scene/resources/atlas_texture.h" #include "scene/resources/particle_process_material.h" +#include "scene/scene_string_names.h" #ifdef TOOLS_ENABLED #include "core/config/engine.h" #endif void GPUParticles2D::set_emitting(bool p_emitting) { - RS::get_singleton()->particles_set_emitting(particles, p_emitting); + // Do not return even if `p_emitting == emitting` because `emitting` is just an approximation. if (p_emitting && one_shot) { + if (!active && !emitting) { + // Last cycle ended. + active = true; + time = 0; + signal_cancled = false; + emission_time = lifetime; + active_time = lifetime * (2 - explosiveness_ratio); + } else { + signal_cancled = true; + } set_process_internal(true); } else if (!p_emitting) { - set_process_internal(false); + if (one_shot) { + set_process_internal(true); + } else { + set_process_internal(false); + } } + + emitting = p_emitting; + RS::get_singleton()->particles_set_emitting(particles, p_emitting); } void GPUParticles2D::set_amount(int p_amount) { @@ -211,7 +229,7 @@ void GPUParticles2D::set_speed_scale(double p_scale) { } bool GPUParticles2D::is_emitting() const { - return RS::get_singleton()->particles_get_emitting(particles); + return emitting; } int GPUParticles2D::get_amount() const { @@ -338,13 +356,13 @@ Rect2 GPUParticles2D::capture_rect() const { void GPUParticles2D::set_texture(const Ref<Texture2D> &p_texture) { if (texture.is_valid()) { - texture->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(this, &GPUParticles2D::_texture_changed)); + texture->disconnect_changed(callable_mp(this, &GPUParticles2D::_texture_changed)); } texture = p_texture; if (texture.is_valid()) { - texture->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &GPUParticles2D::_texture_changed)); + texture->connect_changed(callable_mp(this, &GPUParticles2D::_texture_changed)); } _update_collision_size(); queue_redraw(); @@ -405,6 +423,16 @@ NodePath GPUParticles2D::get_sub_emitter() const { void GPUParticles2D::restart() { RS::get_singleton()->particles_restart(particles); RS::get_singleton()->particles_set_emitting(particles, true); + + emitting = true; + active = true; + signal_cancled = false; + time = 0; + emission_time = lifetime; + active_time = lifetime * (2 - explosiveness_ratio); + if (one_shot) { + set_process_internal(true); + } } void GPUParticles2D::_notification(int p_what) { @@ -570,9 +598,23 @@ void GPUParticles2D::_notification(int p_what) { } break; case NOTIFICATION_INTERNAL_PROCESS: { - if (one_shot && !is_emitting()) { - notify_property_list_changed(); - set_process_internal(false); + if (one_shot) { + time += get_process_delta_time(); + if (time > emission_time) { + emitting = false; + if (!active) { + set_process_internal(false); + } + } + if (time > active_time) { + if (active && !signal_cancled) { + emit_signal(SceneStringNames::get_singleton()->finished); + } + active = false; + if (!emitting) { + set_process_internal(false); + } + } } } break; } @@ -638,6 +680,8 @@ void GPUParticles2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_trail_section_subdivisions", "subdivisions"), &GPUParticles2D::set_trail_section_subdivisions); ClassDB::bind_method(D_METHOD("get_trail_section_subdivisions"), &GPUParticles2D::get_trail_section_subdivisions); + ADD_SIGNAL(MethodInfo("finished")); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "emitting"), "set_emitting", "is_emitting"); ADD_PROPERTY_DEFAULT("emitting", true); // Workaround for doctool in headless mode, as dummy rasterizer always returns false. ADD_PROPERTY(PropertyInfo(Variant::INT, "amount", PROPERTY_HINT_RANGE, "1,1000000,1,exp"), "set_amount", "get_amount"); diff --git a/scene/2d/gpu_particles_2d.h b/scene/2d/gpu_particles_2d.h index e518ffec6f..3131698e5c 100644 --- a/scene/2d/gpu_particles_2d.h +++ b/scene/2d/gpu_particles_2d.h @@ -47,6 +47,9 @@ public: private: RID particles; + bool emitting = false; + bool active = false; + bool signal_cancled = false; bool one_shot = false; int amount = 0; double lifetime = 0.0; @@ -78,6 +81,10 @@ private: int trail_sections = 8; int trail_section_subdivisions = 4; + double time = 0.0; + double emission_time = 0.0; + double active_time = 0.0; + RID mesh; void _attach_sub_emitter(); diff --git a/scene/2d/light_occluder_2d.cpp b/scene/2d/light_occluder_2d.cpp index 4c3161d049..61f5d5cd46 100644 --- a/scene/2d/light_occluder_2d.cpp +++ b/scene/2d/light_occluder_2d.cpp @@ -215,7 +215,7 @@ bool LightOccluder2D::_edit_is_selected_on_click(const Point2 &p_point, double p void LightOccluder2D::set_occluder_polygon(const Ref<OccluderPolygon2D> &p_polygon) { #ifdef DEBUG_ENABLED if (occluder_polygon.is_valid()) { - occluder_polygon->disconnect("changed", callable_mp(this, &LightOccluder2D::_poly_changed)); + occluder_polygon->disconnect_changed(callable_mp(this, &LightOccluder2D::_poly_changed)); } #endif occluder_polygon = p_polygon; @@ -228,7 +228,7 @@ void LightOccluder2D::set_occluder_polygon(const Ref<OccluderPolygon2D> &p_polyg #ifdef DEBUG_ENABLED if (occluder_polygon.is_valid()) { - occluder_polygon->connect("changed", callable_mp(this, &LightOccluder2D::_poly_changed)); + occluder_polygon->connect_changed(callable_mp(this, &LightOccluder2D::_poly_changed)); } queue_redraw(); #endif diff --git a/scene/2d/line_2d.cpp b/scene/2d/line_2d.cpp index 58dad40403..3a9473d76c 100644 --- a/scene/2d/line_2d.cpp +++ b/scene/2d/line_2d.cpp @@ -30,7 +30,6 @@ #include "line_2d.h" -#include "core/core_string_names.h" #include "core/math/geometry_2d.h" #include "line_builder.h" @@ -89,14 +88,14 @@ float Line2D::get_width() const { void Line2D::set_curve(const Ref<Curve> &p_curve) { // Cleanup previous connection if any if (_curve.is_valid()) { - _curve->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(this, &Line2D::_curve_changed)); + _curve->disconnect_changed(callable_mp(this, &Line2D::_curve_changed)); } _curve = p_curve; // Connect to the curve so the line will update when it is changed if (_curve.is_valid()) { - _curve->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &Line2D::_curve_changed)); + _curve->connect_changed(callable_mp(this, &Line2D::_curve_changed)); } queue_redraw(); @@ -159,14 +158,14 @@ Color Line2D::get_default_color() const { void Line2D::set_gradient(const Ref<Gradient> &p_gradient) { // Cleanup previous connection if any if (_gradient.is_valid()) { - _gradient->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(this, &Line2D::_gradient_changed)); + _gradient->disconnect_changed(callable_mp(this, &Line2D::_gradient_changed)); } _gradient = p_gradient; // Connect to the gradient so the line will update when the Gradient is changed if (_gradient.is_valid()) { - _gradient->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &Line2D::_gradient_changed)); + _gradient->connect_changed(callable_mp(this, &Line2D::_gradient_changed)); } queue_redraw(); diff --git a/scene/2d/multimesh_instance_2d.cpp b/scene/2d/multimesh_instance_2d.cpp index f347eb6520..9631b2cc4e 100644 --- a/scene/2d/multimesh_instance_2d.cpp +++ b/scene/2d/multimesh_instance_2d.cpp @@ -30,7 +30,6 @@ #include "multimesh_instance_2d.h" -#include "core/core_string_names.h" #include "scene/scene_string_names.h" void MultiMeshInstance2D::_notification(int p_what) { @@ -59,13 +58,13 @@ void MultiMeshInstance2D::_bind_methods() { void MultiMeshInstance2D::set_multimesh(const Ref<MultiMesh> &p_multimesh) { // Cleanup previous connection if any. if (multimesh.is_valid()) { - multimesh->disconnect(CoreStringNames::get_singleton()->changed, callable_mp((CanvasItem *)this, &CanvasItem::queue_redraw)); + multimesh->disconnect_changed(callable_mp((CanvasItem *)this, &CanvasItem::queue_redraw)); } multimesh = p_multimesh; // Connect to the multimesh so the AABB can update when instance transforms are changed. if (multimesh.is_valid()) { - multimesh->connect(CoreStringNames::get_singleton()->changed, callable_mp((CanvasItem *)this, &CanvasItem::queue_redraw)); + multimesh->connect_changed(callable_mp((CanvasItem *)this, &CanvasItem::queue_redraw)); } queue_redraw(); } diff --git a/scene/2d/navigation_link_2d.cpp b/scene/2d/navigation_link_2d.cpp index 3664040e7b..95798b6856 100644 --- a/scene/2d/navigation_link_2d.cpp +++ b/scene/2d/navigation_link_2d.cpp @@ -182,15 +182,7 @@ void NavigationLink2D::set_enabled(bool p_enabled) { enabled = p_enabled; - if (!is_inside_tree()) { - return; - } - - if (!enabled) { - NavigationServer2D::get_singleton()->link_set_map(link, RID()); - } else { - NavigationServer2D::get_singleton()->link_set_map(link, get_world_2d()->get_navigation_map()); - } + NavigationServer3D::get_singleton()->link_set_enabled(link, enabled); #ifdef DEBUG_ENABLED if (Engine::get_singleton()->is_editor_hint() || NavigationServer2D::get_singleton()->get_debug_enabled()) { diff --git a/scene/2d/navigation_obstacle_2d.cpp b/scene/2d/navigation_obstacle_2d.cpp index 8e96f8265c..d993b8a400 100644 --- a/scene/2d/navigation_obstacle_2d.cpp +++ b/scene/2d/navigation_obstacle_2d.cpp @@ -79,7 +79,7 @@ void NavigationObstacle2D::_notification(int p_what) { previous_transform = get_global_transform(); // need to trigger map controlled agent assignment somehow for the fake_agent since obstacles use no callback like regular agents NavigationServer2D::get_singleton()->obstacle_set_avoidance_enabled(obstacle, avoidance_enabled); - _update_position(get_global_transform().get_origin()); + _update_position(get_global_position()); set_physics_process_internal(true); } break; @@ -112,7 +112,7 @@ void NavigationObstacle2D::_notification(int p_what) { case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { if (is_inside_tree()) { - _update_position(get_global_transform().get_origin()); + _update_position(get_global_position()); if (velocity_submitted) { velocity_submitted = false; @@ -164,9 +164,9 @@ NavigationObstacle2D::~NavigationObstacle2D() { void NavigationObstacle2D::set_vertices(const Vector<Vector2> &p_vertices) { vertices = p_vertices; NavigationServer2D::get_singleton()->obstacle_set_vertices(obstacle, vertices); - if (is_inside_tree() && (Engine::get_singleton()->is_editor_hint() || get_tree()->is_debugging_navigation_hint())) { - queue_redraw(); - } +#ifdef DEBUG_ENABLED + queue_redraw(); +#endif // DEBUG_ENABLED } void NavigationObstacle2D::set_navigation_map(RID p_navigation_map) { @@ -195,9 +195,9 @@ void NavigationObstacle2D::set_radius(real_t p_radius) { radius = p_radius; NavigationServer2D::get_singleton()->obstacle_set_radius(obstacle, radius); - if (is_inside_tree() && (Engine::get_singleton()->is_editor_hint() || get_tree()->is_debugging_navigation_hint())) { - queue_redraw(); - } +#ifdef DEBUG_ENABLED + queue_redraw(); +#endif // DEBUG_ENABLED } void NavigationObstacle2D::set_avoidance_layers(uint32_t p_layers) { @@ -237,6 +237,9 @@ void NavigationObstacle2D::set_avoidance_enabled(bool p_enabled) { avoidance_enabled = p_enabled; NavigationServer2D::get_singleton()->obstacle_set_avoidance_enabled(obstacle, avoidance_enabled); +#ifdef DEBUG_ENABLED + queue_redraw(); +#endif // DEBUG_ENABLED } bool NavigationObstacle2D::get_avoidance_enabled() const { @@ -255,13 +258,16 @@ void NavigationObstacle2D::_update_map(RID p_map) { void NavigationObstacle2D::_update_position(const Vector2 p_position) { NavigationServer2D::get_singleton()->obstacle_set_position(obstacle, p_position); +#ifdef DEBUG_ENABLED + queue_redraw(); +#endif // DEBUG_ENABLED } #ifdef DEBUG_ENABLED void NavigationObstacle2D::_update_fake_agent_radius_debug() { if (radius > 0.0 && NavigationServer2D::get_singleton()->get_debug_navigation_avoidance_enable_obstacles_radius()) { Color debug_radius_color = NavigationServer2D::get_singleton()->get_debug_navigation_avoidance_obstacles_radius_color(); - draw_circle(get_global_transform().get_origin(), radius, debug_radius_color); + RS::get_singleton()->canvas_item_add_circle(get_canvas_item(), Vector2(), radius, debug_radius_color); } } #endif // DEBUG_ENABLED diff --git a/scene/2d/navigation_region_2d.cpp b/scene/2d/navigation_region_2d.cpp index 529545de97..670a2c641c 100644 --- a/scene/2d/navigation_region_2d.cpp +++ b/scene/2d/navigation_region_2d.cpp @@ -30,7 +30,6 @@ #include "navigation_region_2d.h" -#include "core/core_string_names.h" #include "core/math/geometry_2d.h" #include "scene/2d/navigation_obstacle_2d.h" #include "scene/resources/world_2d.h" @@ -43,15 +42,7 @@ void NavigationRegion2D::set_enabled(bool p_enabled) { enabled = p_enabled; - if (!is_inside_tree()) { - return; - } - - if (!enabled) { - _region_enter_navigation_map(); - } else { - _region_exit_navigation_map(); - } + NavigationServer2D::get_singleton()->region_set_enabled(region, enabled); #ifdef DEBUG_ENABLED if (Engine::get_singleton()->is_editor_hint() || NavigationServer2D::get_singleton()->get_debug_navigation_enabled()) { @@ -193,14 +184,14 @@ void NavigationRegion2D::set_navigation_polygon(const Ref<NavigationPolygon> &p_ } if (navigation_polygon.is_valid()) { - navigation_polygon->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(this, &NavigationRegion2D::_navigation_polygon_changed)); + navigation_polygon->disconnect_changed(callable_mp(this, &NavigationRegion2D::_navigation_polygon_changed)); } navigation_polygon = p_navigation_polygon; NavigationServer2D::get_singleton()->region_set_navigation_polygon(region, p_navigation_polygon); if (navigation_polygon.is_valid()) { - navigation_polygon->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &NavigationRegion2D::_navigation_polygon_changed)); + navigation_polygon->connect_changed(callable_mp(this, &NavigationRegion2D::_navigation_polygon_changed)); } _navigation_polygon_changed(); diff --git a/scene/2d/path_2d.cpp b/scene/2d/path_2d.cpp index 3e6a484e72..ee0ccc42ff 100644 --- a/scene/2d/path_2d.cpp +++ b/scene/2d/path_2d.cpp @@ -182,13 +182,13 @@ void Path2D::_curve_changed() { void Path2D::set_curve(const Ref<Curve2D> &p_curve) { if (curve.is_valid()) { - curve->disconnect("changed", callable_mp(this, &Path2D::_curve_changed)); + curve->disconnect_changed(callable_mp(this, &Path2D::_curve_changed)); } curve = p_curve; if (curve.is_valid()) { - curve->connect("changed", callable_mp(this, &Path2D::_curve_changed)); + curve->connect_changed(callable_mp(this, &Path2D::_curve_changed)); } _curve_changed(); diff --git a/scene/2d/physics_body_2d.cpp b/scene/2d/physics_body_2d.cpp index b3acc1849b..b9bde47507 100644 --- a/scene/2d/physics_body_2d.cpp +++ b/scene/2d/physics_body_2d.cpp @@ -30,7 +30,6 @@ #include "physics_body_2d.h" -#include "core/core_string_names.h" #include "scene/scene_string_names.h" void PhysicsBody2D::_bind_methods() { @@ -195,15 +194,13 @@ real_t StaticBody2D::get_constant_angular_velocity() const { void StaticBody2D::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, &StaticBody2D::_reload_physics_characteristics))) { - physics_material_override->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(this, &StaticBody2D::_reload_physics_characteristics)); - } + physics_material_override->disconnect_changed(callable_mp(this, &StaticBody2D::_reload_physics_characteristics)); } physics_material_override = p_physics_material_override; if (physics_material_override.is_valid()) { - physics_material_override->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &StaticBody2D::_reload_physics_characteristics)); + physics_material_override->connect_changed(callable_mp(this, &StaticBody2D::_reload_physics_characteristics)); } _reload_physics_characteristics(); } @@ -651,15 +648,13 @@ const Vector2 &RigidBody2D::get_center_of_mass() const { void RigidBody2D::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, &RigidBody2D::_reload_physics_characteristics))) { - physics_material_override->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(this, &RigidBody2D::_reload_physics_characteristics)); - } + physics_material_override->disconnect_changed(callable_mp(this, &RigidBody2D::_reload_physics_characteristics)); } physics_material_override = p_physics_material_override; if (physics_material_override.is_valid()) { - physics_material_override->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &RigidBody2D::_reload_physics_characteristics)); + physics_material_override->connect_changed(callable_mp(this, &RigidBody2D::_reload_physics_characteristics)); } _reload_physics_characteristics(); } diff --git a/scene/2d/shape_cast_2d.cpp b/scene/2d/shape_cast_2d.cpp index f1a9119458..90d80d7549 100644 --- a/scene/2d/shape_cast_2d.cpp +++ b/scene/2d/shape_cast_2d.cpp @@ -31,7 +31,6 @@ #include "shape_cast_2d.h" #include "core/config/engine.h" -#include "core/core_string_names.h" #include "scene/2d/collision_object_2d.h" #include "scene/2d/physics_body_2d.h" #include "scene/resources/circle_shape_2d.h" @@ -155,11 +154,11 @@ void ShapeCast2D::set_shape(const Ref<Shape2D> &p_shape) { return; } if (shape.is_valid()) { - shape->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(this, &ShapeCast2D::_shape_changed)); + shape->disconnect_changed(callable_mp(this, &ShapeCast2D::_shape_changed)); } shape = p_shape; if (shape.is_valid()) { - shape->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &ShapeCast2D::_shape_changed)); + shape->connect_changed(callable_mp(this, &ShapeCast2D::_shape_changed)); shape_rid = shape->get_rid(); } diff --git a/scene/2d/skeleton_2d.cpp b/scene/2d/skeleton_2d.cpp index c31b42beba..350fdad7d7 100644 --- a/scene/2d/skeleton_2d.cpp +++ b/scene/2d/skeleton_2d.cpp @@ -309,8 +309,8 @@ void Bone2D::_notification(int p_what) { #ifdef TOOLS_ENABLED bool Bone2D::_editor_get_bone_shape(Vector<Vector2> *p_shape, Vector<Vector2> *p_outline_shape, Bone2D *p_other_bone) { - int bone_width = EDITOR_GET("editors/2d/bone_width"); - int bone_outline_width = EDITOR_GET("editors/2d/bone_outline_size"); + float bone_width = EDITOR_GET("editors/2d/bone_width"); + float bone_outline_width = EDITOR_GET("editors/2d/bone_outline_size"); if (!is_inside_tree()) { return false; //may have been removed diff --git a/scene/2d/sprite_2d.cpp b/scene/2d/sprite_2d.cpp index 6e314160fd..7e6b43559c 100644 --- a/scene/2d/sprite_2d.cpp +++ b/scene/2d/sprite_2d.cpp @@ -30,7 +30,6 @@ #include "sprite_2d.h" -#include "core/core_string_names.h" #include "scene/main/window.h" #include "scene/scene_string_names.h" @@ -137,13 +136,13 @@ void Sprite2D::set_texture(const Ref<Texture2D> &p_texture) { } if (texture.is_valid()) { - texture->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(this, &Sprite2D::_texture_changed)); + texture->disconnect_changed(callable_mp(this, &Sprite2D::_texture_changed)); } texture = p_texture; if (texture.is_valid()) { - texture->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &Sprite2D::_texture_changed)); + texture->connect_changed(callable_mp(this, &Sprite2D::_texture_changed)); } queue_redraw(); diff --git a/scene/2d/tile_map.compat.inc b/scene/2d/tile_map.compat.inc new file mode 100644 index 0000000000..49e2bf6f0b --- /dev/null +++ b/scene/2d/tile_map.compat.inc @@ -0,0 +1,45 @@ +/**************************************************************************/ +/* object.compat.inc */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef DISABLE_DEPRECATED + +#include "core/object/object.h" + +#include "core/object/class_db.h" + +Rect2i TileMap::_get_used_rect_bind_compat_78328() { + return get_used_rect(); +} + +void TileMap::_bind_compatibility_methods() { + ClassDB::bind_compatibility_method(D_METHOD("get_used_rect"), &TileMap::_get_used_rect_bind_compat_78328); +} + +#endif diff --git a/scene/2d/tile_map.cpp b/scene/2d/tile_map.cpp index c5ce45091e..3c37fc7775 100644 --- a/scene/2d/tile_map.cpp +++ b/scene/2d/tile_map.cpp @@ -29,7 +29,9 @@ /**************************************************************************/ #include "tile_map.h" +#include "tile_map.compat.inc" +#include "core/core_string_names.h" #include "core/io/marshalls.h" #include "scene/resources/world_2d.h" #include "servers/navigation_server_2d.h" @@ -38,793 +40,17 @@ #include "servers/navigation_server_3d.h" #endif // DEBUG_ENABLED -HashMap<Vector2i, TileSet::CellNeighbor> TileMap::TerrainConstraint::get_overlapping_coords_and_peering_bits() const { - HashMap<Vector2i, TileSet::CellNeighbor> output; - - ERR_FAIL_COND_V(is_center_bit(), output); - - Ref<TileSet> ts = tile_map->get_tileset(); - ERR_FAIL_COND_V(!ts.is_valid(), output); - - TileSet::TileShape shape = ts->get_tile_shape(); - if (shape == TileSet::TILE_SHAPE_SQUARE) { - switch (bit) { - case 1: - output[base_cell_coords] = TileSet::CELL_NEIGHBOR_RIGHT_SIDE; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_LEFT_SIDE; - break; - case 2: - output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER; - break; - case 3: - output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_SIDE; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_SIDE; - break; - default: - ERR_FAIL_V(output); - } - } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC) { - switch (bit) { - case 1: - output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE; - break; - case 2: - output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_CORNER; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_LEFT_CORNER; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_CORNER)] = TileSet::CELL_NEIGHBOR_TOP_CORNER; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE)] = TileSet::CELL_NEIGHBOR_RIGHT_CORNER; - break; - case 3: - output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE; - break; - default: - ERR_FAIL_V(output); - } - } else { - // Half offset shapes. - TileSet::TileOffsetAxis offset_axis = ts->get_tile_offset_axis(); - if (offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { - switch (bit) { - case 1: - output[base_cell_coords] = TileSet::CELL_NEIGHBOR_RIGHT_SIDE; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_LEFT_SIDE; - break; - case 2: - output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_CORNER; - break; - case 3: - output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE; - break; - case 4: - output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_CORNER; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER; - break; - case 5: - output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE; - break; - default: - ERR_FAIL_V(output); - } - } else { - switch (bit) { - case 1: - output[base_cell_coords] = TileSet::CELL_NEIGHBOR_RIGHT_CORNER; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER; - break; - case 2: - output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE; - break; - case 3: - output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_LEFT_CORNER; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER; - break; - case 4: - output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_SIDE; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_SIDE; - break; - case 5: - output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE; - break; - default: - ERR_FAIL_V(output); - } - } - } - return output; -} - -TileMap::TerrainConstraint::TerrainConstraint(const TileMap *p_tile_map, const Vector2i &p_position, int p_terrain) { - tile_map = p_tile_map; - - Ref<TileSet> ts = tile_map->get_tileset(); - ERR_FAIL_COND(!ts.is_valid()); - - bit = 0; - base_cell_coords = p_position; - terrain = p_terrain; -} - -TileMap::TerrainConstraint::TerrainConstraint(const TileMap *p_tile_map, const Vector2i &p_position, const TileSet::CellNeighbor &p_bit, int p_terrain) { - // The way we build the constraint make it easy to detect conflicting constraints. - tile_map = p_tile_map; - - Ref<TileSet> ts = tile_map->get_tileset(); - ERR_FAIL_COND(!ts.is_valid()); - - TileSet::TileShape shape = ts->get_tile_shape(); - if (shape == TileSet::TILE_SHAPE_SQUARE) { - switch (p_bit) { - case TileSet::CELL_NEIGHBOR_RIGHT_SIDE: - bit = 1; - base_cell_coords = p_position; - break; - case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER: - bit = 2; - base_cell_coords = p_position; - break; - case TileSet::CELL_NEIGHBOR_BOTTOM_SIDE: - bit = 3; - base_cell_coords = p_position; - break; - case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER: - bit = 2; - base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_LEFT_SIDE); - break; - case TileSet::CELL_NEIGHBOR_LEFT_SIDE: - bit = 1; - base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_LEFT_SIDE); - break; - case TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER: - bit = 2; - base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER); - break; - case TileSet::CELL_NEIGHBOR_TOP_SIDE: - bit = 3; - base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_SIDE); - break; - case TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER: - bit = 2; - base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_SIDE); - break; - default: - ERR_FAIL(); - break; - } - } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC) { - switch (p_bit) { - case TileSet::CELL_NEIGHBOR_RIGHT_CORNER: - bit = 2; - base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE); - break; - case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE: - bit = 1; - base_cell_coords = p_position; - break; - case TileSet::CELL_NEIGHBOR_BOTTOM_CORNER: - bit = 2; - base_cell_coords = p_position; - break; - case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE: - bit = 3; - base_cell_coords = p_position; - break; - case TileSet::CELL_NEIGHBOR_LEFT_CORNER: - bit = 2; - base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE); - break; - case TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE: - bit = 1; - base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE); - break; - case TileSet::CELL_NEIGHBOR_TOP_CORNER: - bit = 2; - base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_CORNER); - break; - case TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE: - bit = 3; - base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE); - break; - default: - ERR_FAIL(); - break; - } - } else { - // Half-offset shapes - TileSet::TileOffsetAxis offset_axis = ts->get_tile_offset_axis(); - if (offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { - switch (p_bit) { - case TileSet::CELL_NEIGHBOR_RIGHT_SIDE: - bit = 1; - base_cell_coords = p_position; - break; - case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER: - bit = 2; - base_cell_coords = p_position; - break; - case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE: - bit = 3; - base_cell_coords = p_position; - break; - case TileSet::CELL_NEIGHBOR_BOTTOM_CORNER: - bit = 4; - base_cell_coords = p_position; - break; - case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE: - bit = 5; - base_cell_coords = p_position; - break; - case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER: - bit = 2; - base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_LEFT_SIDE); - break; - case TileSet::CELL_NEIGHBOR_LEFT_SIDE: - bit = 1; - base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_LEFT_SIDE); - break; - case TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER: - bit = 4; - base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE); - break; - case TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE: - bit = 3; - base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE); - break; - case TileSet::CELL_NEIGHBOR_TOP_CORNER: - bit = 2; - base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE); - break; - case TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE: - bit = 5; - base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE); - break; - case TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER: - bit = 4; - base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE); - break; - default: - ERR_FAIL(); - break; - } - } else { - switch (p_bit) { - case TileSet::CELL_NEIGHBOR_RIGHT_CORNER: - bit = 1; - base_cell_coords = p_position; - break; - case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE: - bit = 2; - base_cell_coords = p_position; - break; - case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER: - bit = 3; - base_cell_coords = p_position; - break; - case TileSet::CELL_NEIGHBOR_BOTTOM_SIDE: - bit = 4; - base_cell_coords = p_position; - break; - case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER: - bit = 1; - base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE); - break; - case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE: - bit = 5; - base_cell_coords = p_position; - break; - case TileSet::CELL_NEIGHBOR_LEFT_CORNER: - bit = 3; - base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE); - break; - case TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE: - bit = 2; - base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE); - break; - case TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER: - bit = 1; - base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE); - break; - case TileSet::CELL_NEIGHBOR_TOP_SIDE: - bit = 4; - base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_SIDE); - break; - case TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER: - bit = 3; - base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_SIDE); - break; - case TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE: - bit = 5; - base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE); - break; - default: - ERR_FAIL(); - break; - } - } - } - terrain = p_terrain; -} - -Vector2i TileMap::transform_coords_layout(const Vector2i &p_coords, TileSet::TileOffsetAxis p_offset_axis, TileSet::TileLayout p_from_layout, TileSet::TileLayout p_to_layout) { - // Transform to stacked layout. - Vector2i output = p_coords; - if (p_offset_axis == TileSet::TILE_OFFSET_AXIS_VERTICAL) { - SWAP(output.x, output.y); - } - switch (p_from_layout) { - case TileSet::TILE_LAYOUT_STACKED: - break; - case TileSet::TILE_LAYOUT_STACKED_OFFSET: - if (output.y % 2) { - output.x -= 1; - } - break; - case TileSet::TILE_LAYOUT_STAIRS_RIGHT: - case TileSet::TILE_LAYOUT_STAIRS_DOWN: - if ((p_from_layout == TileSet::TILE_LAYOUT_STAIRS_RIGHT) ^ (p_offset_axis == TileSet::TILE_OFFSET_AXIS_VERTICAL)) { - if (output.y < 0 && bool(output.y % 2)) { - output = Vector2i(output.x + output.y / 2 - 1, output.y); - } else { - output = Vector2i(output.x + output.y / 2, output.y); - } - } else { - if (output.x < 0 && bool(output.x % 2)) { - output = Vector2i(output.x / 2 - 1, output.x + output.y * 2); - } else { - output = Vector2i(output.x / 2, output.x + output.y * 2); - } - } - break; - case TileSet::TILE_LAYOUT_DIAMOND_RIGHT: - case TileSet::TILE_LAYOUT_DIAMOND_DOWN: - if ((p_from_layout == TileSet::TILE_LAYOUT_DIAMOND_RIGHT) ^ (p_offset_axis == TileSet::TILE_OFFSET_AXIS_VERTICAL)) { - if ((output.x + output.y) < 0 && (output.x - output.y) % 2) { - output = Vector2i((output.x + output.y) / 2 - 1, output.y - output.x); - } else { - output = Vector2i((output.x + output.y) / 2, -output.x + output.y); - } - } else { - if ((output.x - output.y) < 0 && (output.x + output.y) % 2) { - output = Vector2i((output.x - output.y) / 2 - 1, output.x + output.y); - } else { - output = Vector2i((output.x - output.y) / 2, output.x + output.y); - } - } - break; - } - - switch (p_to_layout) { - case TileSet::TILE_LAYOUT_STACKED: - break; - case TileSet::TILE_LAYOUT_STACKED_OFFSET: - if (output.y % 2) { - output.x += 1; - } - break; - case TileSet::TILE_LAYOUT_STAIRS_RIGHT: - case TileSet::TILE_LAYOUT_STAIRS_DOWN: - if ((p_to_layout == TileSet::TILE_LAYOUT_STAIRS_RIGHT) ^ (p_offset_axis == TileSet::TILE_OFFSET_AXIS_VERTICAL)) { - if (output.y < 0 && (output.y % 2)) { - output = Vector2i(output.x - output.y / 2 + 1, output.y); - } else { - output = Vector2i(output.x - output.y / 2, output.y); - } - } else { - if (output.y % 2) { - if (output.y < 0) { - output = Vector2i(2 * output.x + 1, -output.x + output.y / 2 - 1); - } else { - output = Vector2i(2 * output.x + 1, -output.x + output.y / 2); - } - } else { - output = Vector2i(2 * output.x, -output.x + output.y / 2); - } - } - break; - case TileSet::TILE_LAYOUT_DIAMOND_RIGHT: - case TileSet::TILE_LAYOUT_DIAMOND_DOWN: - if ((p_to_layout == TileSet::TILE_LAYOUT_DIAMOND_RIGHT) ^ (p_offset_axis == TileSet::TILE_OFFSET_AXIS_VERTICAL)) { - if (output.y % 2) { - if (output.y > 0) { - output = Vector2i(output.x - output.y / 2, output.x + output.y / 2 + 1); - } else { - output = Vector2i(output.x - output.y / 2 + 1, output.x + output.y / 2); - } - } else { - output = Vector2i(output.x - output.y / 2, output.x + output.y / 2); - } - } else { - if (output.y % 2) { - if (output.y < 0) { - output = Vector2i(output.x + output.y / 2, -output.x + output.y / 2 - 1); - } else { - output = Vector2i(output.x + output.y / 2 + 1, -output.x + output.y / 2); - } - } else { - output = Vector2i(output.x + output.y / 2, -output.x + output.y / 2); - } - } - break; - } - - if (p_offset_axis == TileSet::TILE_OFFSET_AXIS_VERTICAL) { - SWAP(output.x, output.y); - } - - return output; -} - -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; - } else { - return quadrant_size; - } -} - -void TileMap::set_selected_layer(int p_layer_id) { - ERR_FAIL_COND(p_layer_id < -1 || p_layer_id >= (int)layers.size()); - selected_layer = p_layer_id; - emit_signal(SNAME("changed")); - - // Update the layers modulation. - for (unsigned int layer = 0; layer < layers.size(); layer++) { - _rendering_update_layer(layer); - } -} - -int TileMap::get_selected_layer() const { - return selected_layer; -} - -void TileMap::_notification(int p_what) { - switch (p_what) { - case NOTIFICATION_ENTER_TREE: { - _clear_internals(); - _recreate_internals(); - } break; - - case NOTIFICATION_EXIT_TREE: { - _clear_internals(); - } break; - } - - // Transfers the notification to tileset plugins. - if (tile_set.is_valid()) { - _rendering_notification(p_what); - _physics_notification(p_what); - _navigation_notification(p_what); - } -} - -Ref<TileSet> TileMap::get_tileset() const { - return tile_set; -} - -void TileMap::set_tileset(const Ref<TileSet> &p_tileset) { - if (p_tileset == tile_set) { - return; - } - - // Set the tileset, registering to its changes. - if (tile_set.is_valid()) { - tile_set->disconnect("changed", callable_mp(this, &TileMap::_tile_set_changed)); - } - - if (!p_tileset.is_valid()) { - _clear_internals(); - } - - tile_set = p_tileset; - - if (tile_set.is_valid()) { - tile_set->connect("changed", callable_mp(this, &TileMap::_tile_set_changed)); - _clear_internals(); - _recreate_internals(); - } - - emit_signal(SNAME("changed")); -} - -void TileMap::set_quadrant_size(int p_size) { - ERR_FAIL_COND_MSG(p_size < 1, "TileMapQuadrant size cannot be smaller than 1."); - - quadrant_size = p_size; - _clear_internals(); - _recreate_internals(); - emit_signal(SNAME("changed")); -} - -int TileMap::get_quadrant_size() const { - return quadrant_size; -} - -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() + p_to_pos + 1; - } - - ERR_FAIL_INDEX(p_to_pos, (int)layers.size() + 1); - - // Must clear before adding the layer. - _clear_internals(); - - 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); +Vector2i TileMapLayer::_coords_to_quadrant_coords(const Vector2i &p_coords) const { + int quad_size = get_effective_quadrant_size(); - // Clear before shuffling layers. - _clear_internals(); - - TileMapLayer tl = layers[p_layer]; - layers.insert(p_to_pos, tl); - layers.remove_at(p_to_pos < p_layer ? p_layer + 1 : p_layer); - _recreate_internals(); - notify_property_list_changed(); - - if (selected_layer == p_layer) { - selected_layer = p_to_pos < p_layer ? p_to_pos - 1 : p_to_pos; - } - - emit_signal(SNAME("changed")); - - update_configuration_warnings(); -} - -void TileMap::remove_layer(int p_layer) { - ERR_FAIL_INDEX(p_layer, (int)layers.size()); - - // Clear before removing the layer. - _clear_internals(); - - layers.remove_at(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) { - if (p_layer < 0) { - p_layer = layers.size() + p_layer; - } - ERR_FAIL_INDEX(p_layer, (int)layers.size()); - - if (layers[p_layer].name == p_name) { - return; - } - layers[p_layer].name = p_name; - emit_signal(SNAME("changed")); -} - -String TileMap::get_layer_name(int p_layer) const { - ERR_FAIL_INDEX_V(p_layer, (int)layers.size(), String()); - return layers[p_layer].name; -} - -void TileMap::set_layer_enabled(int p_layer, bool p_enabled) { - if (p_layer < 0) { - p_layer = layers.size() + p_layer; - } - ERR_FAIL_INDEX(p_layer, (int)layers.size()); - - if (layers[p_layer].enabled == p_enabled) { - return; - } - layers[p_layer].enabled = p_enabled; - _clear_layer_internals(p_layer); - _recreate_layer_internals(p_layer); - emit_signal(SNAME("changed")); - - update_configuration_warnings(); -} - -bool TileMap::is_layer_enabled(int p_layer) const { - ERR_FAIL_INDEX_V(p_layer, (int)layers.size(), false); - return layers[p_layer].enabled; -} - -void TileMap::set_layer_modulate(int p_layer, Color p_modulate) { - if (p_layer < 0) { - p_layer = layers.size() + p_layer; - } - ERR_FAIL_INDEX(p_layer, (int)layers.size()); - - if (layers[p_layer].modulate == p_modulate) { - return; - } - layers[p_layer].modulate = p_modulate; - _rendering_update_layer(p_layer); - emit_signal(SNAME("changed")); -} - -Color TileMap::get_layer_modulate(int p_layer) const { - ERR_FAIL_INDEX_V(p_layer, (int)layers.size(), Color()); - return layers[p_layer].modulate; -} - -void TileMap::set_layer_y_sort_enabled(int p_layer, bool p_y_sort_enabled) { - if (p_layer < 0) { - p_layer = layers.size() + p_layer; - } - ERR_FAIL_INDEX(p_layer, (int)layers.size()); - - if (layers[p_layer].y_sort_enabled == p_y_sort_enabled) { - return; - } - layers[p_layer].y_sort_enabled = p_y_sort_enabled; - _clear_layer_internals(p_layer); - _recreate_layer_internals(p_layer); - emit_signal(SNAME("changed")); - - update_configuration_warnings(); -} - -bool TileMap::is_layer_y_sort_enabled(int p_layer) const { - ERR_FAIL_INDEX_V(p_layer, (int)layers.size(), false); - return layers[p_layer].y_sort_enabled; -} - -void TileMap::set_layer_y_sort_origin(int p_layer, int p_y_sort_origin) { - if (p_layer < 0) { - p_layer = layers.size() + p_layer; - } - ERR_FAIL_INDEX(p_layer, (int)layers.size()); - - if (layers[p_layer].y_sort_origin == p_y_sort_origin) { - return; - } - layers[p_layer].y_sort_origin = p_y_sort_origin; - _clear_layer_internals(p_layer); - _recreate_layer_internals(p_layer); - emit_signal(SNAME("changed")); -} - -int TileMap::get_layer_y_sort_origin(int p_layer) const { - ERR_FAIL_INDEX_V(p_layer, (int)layers.size(), false); - return layers[p_layer].y_sort_origin; -} - -void TileMap::set_layer_z_index(int p_layer, int p_z_index) { - if (p_layer < 0) { - p_layer = layers.size() + p_layer; - } - ERR_FAIL_INDEX(p_layer, (int)layers.size()); - - if (layers[p_layer].z_index == p_z_index) { - return; - } - layers[p_layer].z_index = p_z_index; - _rendering_update_layer(p_layer); - emit_signal(SNAME("changed")); - - update_configuration_warnings(); -} - -int TileMap::get_layer_z_index(int p_layer) const { - ERR_FAIL_INDEX_V(p_layer, (int)layers.size(), false); - return layers[p_layer].z_index; -} - -void TileMap::set_collision_animatable(bool p_enabled) { - if (collision_animatable == p_enabled) { - return; - } - collision_animatable = p_enabled; - _clear_internals(); - set_notify_local_transform(p_enabled); - set_physics_process_internal(p_enabled); - _recreate_internals(); - emit_signal(SNAME("changed")); -} - -bool TileMap::is_collision_animatable() const { - return collision_animatable; -} - -void TileMap::set_collision_visibility_mode(TileMap::VisibilityMode p_show_collision) { - if (collision_visibility_mode == p_show_collision) { - return; - } - collision_visibility_mode = p_show_collision; - _clear_internals(); - _recreate_internals(); - emit_signal(SNAME("changed")); -} - -TileMap::VisibilityMode TileMap::get_collision_visibility_mode() { - return collision_visibility_mode; -} - -void TileMap::set_navigation_visibility_mode(TileMap::VisibilityMode p_show_navigation) { - if (navigation_visibility_mode == p_show_navigation) { - return; - } - navigation_visibility_mode = p_show_navigation; - _clear_internals(); - _recreate_internals(); - emit_signal(SNAME("changed")); -} - -TileMap::VisibilityMode TileMap::get_navigation_visibility_mode() { - return navigation_visibility_mode; -} - -void TileMap::set_navigation_map(int p_layer, RID p_map) { - ERR_FAIL_INDEX(p_layer, (int)layers.size()); - ERR_FAIL_COND_MSG(!is_inside_tree(), "A TileMap navigation map can only be changed while inside the SceneTree."); - layers[p_layer].navigation_map = p_map; - layers[p_layer].uses_world_navigation_map = p_map == get_world_2d()->get_navigation_map(); -} - -RID TileMap::get_navigation_map(int p_layer) const { - ERR_FAIL_INDEX_V(p_layer, (int)layers.size(), RID()); - if (layers[p_layer].navigation_map.is_valid()) { - return layers[p_layer].navigation_map; - } - return RID(); -} - -void TileMap::set_y_sort_enabled(bool p_enable) { - if (is_y_sort_enabled() == p_enable) { - return; - } - Node2D::set_y_sort_enabled(p_enable); - _clear_internals(); - _recreate_internals(); - emit_signal(SNAME("changed")); - update_configuration_warnings(); -} - -Vector2i TileMap::_coords_to_quadrant_coords(int p_layer, const Vector2i &p_coords) const { - int quad_size = get_effective_quadrant_size(p_layer); - - // Rounding down, instead of simply rounding towards zero (truncating) + // Rounding down, instead of simply rounding towards zero (truncating). return Vector2i( p_coords.x > 0 ? p_coords.x / quad_size : (p_coords.x - (quad_size - 1)) / quad_size, p_coords.y > 0 ? p_coords.y / quad_size : (p_coords.y - (quad_size - 1)) / quad_size); } -HashMap<Vector2i, TileMapQuadrant>::Iterator TileMap::_create_quadrant(int p_layer, const Vector2i &p_qk) { - ERR_FAIL_INDEX_V(p_layer, (int)layers.size(), nullptr); - +HashMap<Vector2i, TileMapQuadrant>::Iterator TileMapLayer::_create_quadrant(const Vector2i &p_qk) { TileMapQuadrant q; - q.layer = p_layer; q.coords = p_qk; rect_cache_dirty = true; @@ -833,156 +59,32 @@ HashMap<Vector2i, TileMapQuadrant>::Iterator TileMap::_create_quadrant(int p_lay RenderingServer *rs = RenderingServer::get_singleton(); q.debug_canvas_item = rs->canvas_item_create(); rs->canvas_item_set_z_index(q.debug_canvas_item, RS::CANVAS_ITEM_Z_MAX - 1); - rs->canvas_item_set_parent(q.debug_canvas_item, get_canvas_item()); + rs->canvas_item_set_parent(q.debug_canvas_item, tile_map_node->get_canvas_item()); - // Call the create_quadrant method on plugins + // Call the create_quadrant method on plugins. + const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); if (tile_set.is_valid()) { _rendering_create_quadrant(&q); } - return layers[p_layer].quadrant_map.insert(p_qk, q); + return quadrant_map.insert(p_qk, q); } -void TileMap::_make_quadrant_dirty(HashMap<Vector2i, TileMapQuadrant>::Iterator Q) { +void TileMapLayer::_make_quadrant_dirty(HashMap<Vector2i, TileMapQuadrant>::Iterator Q) { // Make the given quadrant dirty, then trigger an update later. TileMapQuadrant &q = Q->value; if (!q.dirty_list_element.in_list()) { - layers[q.layer].dirty_quadrant_list.add(&q.dirty_list_element); - } - _queue_update_dirty_quadrants(); -} - -void TileMap::_make_all_quadrants_dirty() { - // Make all quandrants dirty, then trigger an update later. - for (TileMapLayer &layer : layers) { - for (KeyValue<Vector2i, TileMapQuadrant> &E : layer.quadrant_map) { - if (!E.value.dirty_list_element.in_list()) { - layer.dirty_quadrant_list.add(&E.value.dirty_list_element); - } - } + dirty_quadrant_list.add(&q.dirty_list_element); } - _queue_update_dirty_quadrants(); + tile_map_node->queue_update_dirty_quadrants(); } -void TileMap::_queue_update_dirty_quadrants() { - if (pending_update || !is_inside_tree()) { - return; - } - pending_update = true; - call_deferred(SNAME("_update_dirty_quadrants")); -} - -void TileMap::_update_dirty_quadrants() { - if (!pending_update) { - return; - } - if (!is_inside_tree() || !tile_set.is_valid()) { - pending_update = false; - return; - } - - for (unsigned int layer = 0; layer < layers.size(); layer++) { - SelfList<TileMapQuadrant>::List &dirty_quadrant_list = layers[layer].dirty_quadrant_list; - - // Update the coords cache. - for (SelfList<TileMapQuadrant> *q = dirty_quadrant_list.first(); q; q = q->next()) { - q->self()->map_to_local.clear(); - q->self()->local_to_map.clear(); - for (const Vector2i &E : q->self()->cells) { - Vector2i pk = E; - Vector2 pk_local_coords = map_to_local(pk); - q->self()->map_to_local[pk] = pk_local_coords; - q->self()->local_to_map[pk_local_coords] = pk; - } - } - - // Find TileData that need a runtime modification. - _build_runtime_update_tile_data(dirty_quadrant_list); - - // Call the update_dirty_quadrant method on plugins. - _rendering_update_dirty_quadrants(dirty_quadrant_list); - _physics_update_dirty_quadrants(dirty_quadrant_list); - _navigation_update_dirty_quadrants(dirty_quadrant_list); - _scenes_update_dirty_quadrants(dirty_quadrant_list); - - // Redraw the debug canvas_items. - RenderingServer *rs = RenderingServer::get_singleton(); - for (SelfList<TileMapQuadrant> *q = dirty_quadrant_list.first(); q; q = q->next()) { - rs->canvas_item_clear(q->self()->debug_canvas_item); - Transform2D xform; - xform.set_origin(map_to_local(q->self()->coords * get_effective_quadrant_size(layer))); - rs->canvas_item_set_transform(q->self()->debug_canvas_item, xform); - - _rendering_draw_quadrant_debug(q->self()); - _physics_draw_quadrant_debug(q->self()); - _navigation_draw_quadrant_debug(q->self()); - _scenes_draw_quadrant_debug(q->self()); - } - - // Clear the list - while (dirty_quadrant_list.first()) { - // Clear the runtime tile data. - for (const KeyValue<Vector2i, TileData *> &kv : dirty_quadrant_list.first()->self()->runtime_tile_data_cache) { - memdelete(kv.value); - } - - dirty_quadrant_list.remove(dirty_quadrant_list.first()); - } - } - - pending_update = false; - - _recompute_rect_cache(); -} - -void TileMap::_recreate_layer_internals(int p_layer) { - ERR_FAIL_INDEX(p_layer, (int)layers.size()); - - // Make sure that _clear_internals() was called prior. - ERR_FAIL_COND_MSG(layers[p_layer].quadrant_map.size() > 0, "TileMap layer " + itos(p_layer) + " had a non-empty quadrant map."); - - if (!layers[p_layer].enabled) { - return; - } - - // Update the layer internals. - _rendering_update_layer(p_layer); - - // Update the layer internal navigation maps. - _navigation_update_layer(p_layer); - - // Recreate the quadrants. - const HashMap<Vector2i, TileMapCell> &tile_map = layers[p_layer].tile_map; - for (const KeyValue<Vector2i, TileMapCell> &E : tile_map) { - Vector2i qk = _coords_to_quadrant_coords(p_layer, Vector2i(E.key.x, E.key.y)); - - HashMap<Vector2i, TileMapQuadrant>::Iterator Q = layers[p_layer].quadrant_map.find(qk); - if (!Q) { - Q = _create_quadrant(p_layer, qk); - layers[p_layer].dirty_quadrant_list.add(&Q->value.dirty_list_element); - } - - Vector2i pk = E.key; - Q->value.cells.insert(pk); - - _make_quadrant_dirty(Q); - } - - _queue_update_dirty_quadrants(); -} - -void TileMap::_recreate_internals() { - for (unsigned int layer = 0; layer < layers.size(); layer++) { - _recreate_layer_internals(layer); - } -} - -void TileMap::_erase_quadrant(HashMap<Vector2i, TileMapQuadrant>::Iterator Q) { +void TileMapLayer::_erase_quadrant(HashMap<Vector2i, TileMapQuadrant>::Iterator Q) { // Remove a quadrant. TileMapQuadrant *q = &(Q->value); // Call the cleanup_quadrant method on plugins. - if (tile_set.is_valid()) { + if (tile_map_node->get_tileset().is_valid()) { _rendering_cleanup_quadrant(q); _physics_cleanup_quadrant(q); _navigation_cleanup_quadrant(q); @@ -991,217 +93,42 @@ void TileMap::_erase_quadrant(HashMap<Vector2i, TileMapQuadrant>::Iterator Q) { // Remove the quadrant from the dirty_list if it is there. if (q->dirty_list_element.in_list()) { - layers[q->layer].dirty_quadrant_list.remove(&(q->dirty_list_element)); + dirty_quadrant_list.remove(&(q->dirty_list_element)); } // Free the debug canvas item. RenderingServer *rs = RenderingServer::get_singleton(); rs->free(q->debug_canvas_item); - layers[q->layer].quadrant_map.remove(Q); + quadrant_map.remove(Q); rect_cache_dirty = true; } -void TileMap::_clear_layer_internals(int p_layer) { - ERR_FAIL_INDEX(p_layer, (int)layers.size()); - - // Clear quadrants. - while (layers[p_layer].quadrant_map.size()) { - _erase_quadrant(layers[p_layer].quadrant_map.begin()); - } - - // Clear the layers internals. - _rendering_cleanup_layer(p_layer); - - // Clear the layers internal navigation maps. - _navigation_cleanup_layer(p_layer); - - // Clear the dirty quadrants list. - while (layers[p_layer].dirty_quadrant_list.first()) { - layers[p_layer].dirty_quadrant_list.remove(layers[p_layer].dirty_quadrant_list.first()); - } -} - -void TileMap::_clear_internals() { - // Clear quadrants. - for (unsigned int layer = 0; layer < layers.size(); layer++) { - _clear_layer_internals(layer); - } -} - -void TileMap::_recompute_rect_cache() { - // Compute the displayed area of the tilemap. -#ifdef DEBUG_ENABLED - - if (!rect_cache_dirty) { - return; - } - - Rect2 r_total; - bool first = true; - for (unsigned int layer = 0; layer < layers.size(); layer++) { - for (const KeyValue<Vector2i, TileMapQuadrant> &E : layers[layer].quadrant_map) { - Rect2 r; - r.position = map_to_local(E.key * get_effective_quadrant_size(layer)); - r.expand_to(map_to_local((E.key + Vector2i(1, 0)) * get_effective_quadrant_size(layer))); - r.expand_to(map_to_local((E.key + Vector2i(1, 1)) * get_effective_quadrant_size(layer))); - r.expand_to(map_to_local((E.key + Vector2i(0, 1)) * get_effective_quadrant_size(layer))); - if (first) { - r_total = r; - first = false; - } else { - r_total = r_total.merge(r); - } - } - } - - bool changed = rect_cache != r_total; - - rect_cache = r_total; - - item_rect_changed(changed); - - rect_cache_dirty = false; -#endif -} - /////////////////////////////// Rendering ////////////////////////////////////// -void TileMap::_rendering_notification(int p_what) { - switch (p_what) { - case NOTIFICATION_ENTER_CANVAS: { - bool node_visible = is_visible_in_tree(); - for (TileMapLayer &layer : layers) { - for (KeyValue<Vector2i, TileMapQuadrant> &E_quadrant : layer.quadrant_map) { - TileMapQuadrant &q = E_quadrant.value; - for (const KeyValue<Vector2i, RID> &kv : q.occluders) { - Transform2D xform; - xform.set_origin(map_to_local(kv.key)); - RS::get_singleton()->canvas_light_occluder_attach_to_canvas(kv.value, get_canvas()); - RS::get_singleton()->canvas_light_occluder_set_transform(kv.value, get_global_transform() * xform); - RS::get_singleton()->canvas_light_occluder_set_enabled(kv.value, node_visible); - } - } - } - } break; - - case NOTIFICATION_VISIBILITY_CHANGED: { - bool node_visible = is_visible_in_tree(); - for (TileMapLayer &layer : layers) { - for (KeyValue<Vector2i, TileMapQuadrant> &E_quadrant : layer.quadrant_map) { - TileMapQuadrant &q = E_quadrant.value; - - // Update occluders transform. - for (const KeyValue<Vector2, Vector2i> &E_cell : q.local_to_map) { - Transform2D xform; - xform.set_origin(E_cell.key); - for (const KeyValue<Vector2i, RID> &kv : q.occluders) { - RS::get_singleton()->canvas_light_occluder_set_enabled(kv.value, node_visible); - } - } - } - } - } break; - - case NOTIFICATION_TRANSFORM_CHANGED: { - if (!is_inside_tree()) { - return; - } - for (TileMapLayer &layer : layers) { - for (KeyValue<Vector2i, TileMapQuadrant> &E_quadrant : layer.quadrant_map) { - TileMapQuadrant &q = E_quadrant.value; - - // Update occluders transform. - for (const KeyValue<Vector2i, RID> &kv : q.occluders) { - Transform2D xform; - xform.set_origin(map_to_local(kv.key)); - RenderingServer::get_singleton()->canvas_light_occluder_set_transform(kv.value, get_global_transform() * xform); - } - } - } - } break; - - case NOTIFICATION_DRAW: { - if (tile_set.is_valid()) { - RenderingServer::get_singleton()->canvas_item_set_sort_children_by_y(get_canvas_item(), is_y_sort_enabled()); - } - } break; - - case NOTIFICATION_EXIT_CANVAS: { - for (TileMapLayer &layer : layers) { - for (KeyValue<Vector2i, TileMapQuadrant> &E_quadrant : layer.quadrant_map) { - TileMapQuadrant &q = E_quadrant.value; - for (const KeyValue<Vector2i, RID> &kv : q.occluders) { - RS::get_singleton()->canvas_light_occluder_attach_to_canvas(kv.value, RID()); - } - } - } - } break; - } -} - -void TileMap::_navigation_update_layer(int p_layer) { - ERR_FAIL_INDEX(p_layer, (int)layers.size()); - ERR_FAIL_NULL(NavigationServer2D::get_singleton()); - - if (!layers[p_layer].navigation_map.is_valid()) { - if (p_layer == 0 && is_inside_tree()) { - // Use the default World2D navigation map for the first layer when empty. - layers[p_layer].navigation_map = get_world_2d()->get_navigation_map(); - layers[p_layer].uses_world_navigation_map = true; - } else { - RID new_layer_map = NavigationServer2D::get_singleton()->map_create(); - NavigationServer2D::get_singleton()->map_set_active(new_layer_map, true); - layers[p_layer].navigation_map = new_layer_map; - layers[p_layer].uses_world_navigation_map = false; - } - } -} - -void TileMap::_navigation_cleanup_layer(int p_layer) { - ERR_FAIL_INDEX(p_layer, (int)layers.size()); - ERR_FAIL_NULL(NavigationServer2D::get_singleton()); - - if (layers[p_layer].navigation_map.is_valid()) { - if (layers[p_layer].uses_world_navigation_map) { - // Do not delete the World2D default navigation map. - return; - } - NavigationServer2D::get_singleton()->free(layers[p_layer].navigation_map); - layers[p_layer].navigation_map = RID(); - } -} - -void TileMap::_rendering_update_layer(int p_layer) { - ERR_FAIL_INDEX(p_layer, (int)layers.size()); - +void TileMapLayer::_rendering_update() { RenderingServer *rs = RenderingServer::get_singleton(); - if (!layers[p_layer].canvas_item.is_valid()) { + if (!canvas_item.is_valid()) { RID ci = rs->canvas_item_create(); - rs->canvas_item_set_parent(ci, get_canvas_item()); - - /*Transform2D xform; - xform.set_origin(Vector2(0, p_layer)); - rs->canvas_item_set_transform(ci, xform);*/ - rs->canvas_item_set_draw_index(ci, p_layer - (int64_t)0x80000000); - - layers[p_layer].canvas_item = ci; - } - RID &ci = layers[p_layer].canvas_item; - rs->canvas_item_set_sort_children_by_y(ci, layers[p_layer].y_sort_enabled); - rs->canvas_item_set_use_parent_material(ci, get_use_parent_material() || get_material().is_valid()); - rs->canvas_item_set_z_index(ci, layers[p_layer].z_index); - rs->canvas_item_set_default_texture_filter(ci, RS::CanvasItemTextureFilter(get_texture_filter_in_tree())); - rs->canvas_item_set_default_texture_repeat(ci, RS::CanvasItemTextureRepeat(get_texture_repeat_in_tree())); - rs->canvas_item_set_light_mask(ci, get_light_mask()); - - Color layer_modulate = get_layer_modulate(p_layer); - if (selected_layer >= 0 && p_layer != selected_layer) { - int z1 = get_layer_z_index(p_layer); - int z2 = get_layer_z_index(selected_layer); - if (z1 < z2 || (z1 == z2 && p_layer < selected_layer)) { + rs->canvas_item_set_parent(ci, tile_map_node->get_canvas_item()); + rs->canvas_item_set_draw_index(ci, layer_index_in_tile_map_node - (int64_t)0x80000000); + canvas_item = ci; + } + RID &ci = canvas_item; + rs->canvas_item_set_sort_children_by_y(ci, y_sort_enabled); + rs->canvas_item_set_use_parent_material(ci, tile_map_node->get_use_parent_material() || tile_map_node->get_material().is_valid()); + rs->canvas_item_set_z_index(ci, z_index); + rs->canvas_item_set_default_texture_filter(ci, RS::CanvasItemTextureFilter(tile_map_node->get_texture_filter_in_tree())); + rs->canvas_item_set_default_texture_repeat(ci, RS::CanvasItemTextureRepeat(tile_map_node->get_texture_repeat_in_tree())); + rs->canvas_item_set_light_mask(ci, tile_map_node->get_light_mask()); + + Color layer_modulate = modulate; + int selected_layer = tile_map_node->get_selected_layer(); + if (selected_layer >= 0 && layer_index_in_tile_map_node != selected_layer) { + int z_selected = tile_map_node->get_layer_z_index(selected_layer); + if (z_index < z_selected || (z_index == z_selected && layer_index_in_tile_map_node < selected_layer)) { layer_modulate = layer_modulate.darkened(0.5); - } else if (z1 > z2 || (z1 == z2 && p_layer > selected_layer)) { + } else if (z_index > z_selected || (z_index == z_selected && layer_index_in_tile_map_node > selected_layer)) { layer_modulate = layer_modulate.darkened(0.5); layer_modulate.a *= 0.3; } @@ -1209,22 +136,21 @@ void TileMap::_rendering_update_layer(int p_layer) { rs->canvas_item_set_modulate(ci, layer_modulate); } -void TileMap::_rendering_cleanup_layer(int p_layer) { - ERR_FAIL_INDEX(p_layer, (int)layers.size()); - +void TileMapLayer::_rendering_cleanup() { ERR_FAIL_NULL(RenderingServer::get_singleton()); RenderingServer *rs = RenderingServer::get_singleton(); - if (layers[p_layer].canvas_item.is_valid()) { - rs->free(layers[p_layer].canvas_item); - layers[p_layer].canvas_item = RID(); + if (canvas_item.is_valid()) { + rs->free(canvas_item); + canvas_item = RID(); } } -void TileMap::_rendering_update_dirty_quadrants(SelfList<TileMapQuadrant>::List &r_dirty_quadrant_list) { - ERR_FAIL_COND(!is_inside_tree()); +void TileMapLayer::_rendering_update_dirty_quadrants(SelfList<TileMapQuadrant>::List &r_dirty_quadrant_list) { + ERR_FAIL_COND(!tile_map_node->is_inside_tree()); + const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); ERR_FAIL_COND(!tile_set.is_valid()); - bool node_visible = is_visible_in_tree(); + bool node_visible = tile_map_node->is_visible_in_tree(); SelfList<TileMapQuadrant> *q_list_element = r_dirty_quadrant_list.first(); while (q_list_element) { @@ -1251,7 +177,7 @@ void TileMap::_rendering_update_dirty_quadrants(SelfList<TileMapQuadrant>::List // Iterate over the cells of the quadrant. for (const KeyValue<Vector2, Vector2i> &E_cell : q.local_to_map) { - TileMapCell c = get_cell(q.layer, E_cell.value, true); + TileMapCell c = get_cell(E_cell.value, true); TileSetSource *source; if (tile_set->has_source(c.source_id)) { @@ -1275,10 +201,10 @@ void TileMap::_rendering_update_dirty_quadrants(SelfList<TileMapQuadrant>::List int tile_z_index = tile_data->get_z_index(); // Quandrant pos. - Vector2 tile_position = map_to_local(q.coords * get_effective_quadrant_size(q.layer)); - if (is_y_sort_enabled() && layers[q.layer].y_sort_enabled) { + Vector2 tile_position = tile_map_node->map_to_local(q.coords * get_effective_quadrant_size()); + if (tile_map_node->is_y_sort_enabled() && y_sort_enabled) { // When Y-sorting, the quandrant size is sure to be 1, we can thus offset the CanvasItem. - tile_position.y += layers[q.layer].y_sort_origin + tile_data->get_y_sort_origin(); + tile_position.y += y_sort_origin + tile_data->get_y_sort_origin(); } // --- CanvasItems --- @@ -1292,19 +218,19 @@ void TileMap::_rendering_update_dirty_quadrants(SelfList<TileMapQuadrant>::List if (mat.is_valid()) { rs->canvas_item_set_material(ci, mat->get_rid()); } - rs->canvas_item_set_parent(ci, layers[q.layer].canvas_item); - rs->canvas_item_set_use_parent_material(ci, get_use_parent_material() || get_material().is_valid()); + rs->canvas_item_set_parent(ci, canvas_item); + rs->canvas_item_set_use_parent_material(ci, tile_map_node->get_use_parent_material() || tile_map_node->get_material().is_valid()); Transform2D xform; xform.set_origin(tile_position); rs->canvas_item_set_transform(ci, xform); - rs->canvas_item_set_light_mask(ci, get_light_mask()); + rs->canvas_item_set_light_mask(ci, tile_map_node->get_light_mask()); rs->canvas_item_set_z_as_relative_to_parent(ci, true); rs->canvas_item_set_z_index(ci, tile_z_index); - rs->canvas_item_set_default_texture_filter(ci, RS::CanvasItemTextureFilter(get_texture_filter_in_tree())); - rs->canvas_item_set_default_texture_repeat(ci, RS::CanvasItemTextureRepeat(get_texture_repeat_in_tree())); + rs->canvas_item_set_default_texture_filter(ci, RS::CanvasItemTextureFilter(tile_map_node->get_texture_filter_in_tree())); + rs->canvas_item_set_default_texture_repeat(ci, RS::CanvasItemTextureRepeat(tile_map_node->get_texture_repeat_in_tree())); q.canvas_items.push_back(ci); @@ -1317,21 +243,17 @@ void TileMap::_rendering_update_dirty_quadrants(SelfList<TileMapQuadrant>::List ci = prev_ci; } - Vector2 p_position = E_cell.key - tile_position; - Vector2 p_atlas_coords = c.get_atlas_coords(); - // Random animation offset. - real_t p_random_animation_offset = 0.0; - if (atlas_source->get_tile_animation_mode(p_atlas_coords) != TileSetAtlasSource::TILE_ANIMATION_MODE_DEFAULT) { + real_t random_animation_offset = 0.0; + if (atlas_source->get_tile_animation_mode(c.get_atlas_coords()) != TileSetAtlasSource::TILE_ANIMATION_MODE_DEFAULT) { Array to_hash; - to_hash.push_back(p_position); - to_hash.push_back(q.layer); - to_hash.push_back(Variant(this)); - p_random_animation_offset = RandomPCG(to_hash.hash()).randf(); + to_hash.push_back(E_cell.key); + to_hash.push_back(get_instance_id()); // Use instance id as a random hash + random_animation_offset = RandomPCG(to_hash.hash()).randf(); } // Drawing the tile in the canvas item. - draw_tile(ci, p_position, tile_set, c.source_id, p_atlas_coords, c.alternative_tile, -1, get_self_modulate(), tile_data, p_random_animation_offset); + tile_map_node->draw_tile(ci, E_cell.key - tile_position, tile_set, c.source_id, c.get_atlas_coords(), c.alternative_tile, -1, tile_map_node->get_self_modulate(), tile_data, random_animation_offset); // --- Occluders --- for (int i = 0; i < tile_set->get_occlusion_layers_count(); i++) { @@ -1340,9 +262,9 @@ void TileMap::_rendering_update_dirty_quadrants(SelfList<TileMapQuadrant>::List if (tile_data->get_occluder(i).is_valid()) { RID occluder_id = rs->canvas_light_occluder_create(); rs->canvas_light_occluder_set_enabled(occluder_id, node_visible); - rs->canvas_light_occluder_set_transform(occluder_id, get_global_transform() * xform); + rs->canvas_light_occluder_set_transform(occluder_id, tile_map_node->get_global_transform() * xform); rs->canvas_light_occluder_set_polygon(occluder_id, tile_data->get_occluder(i)->get_rid()); - rs->canvas_light_occluder_attach_to_canvas(occluder_id, get_canvas()); + rs->canvas_light_occluder_attach_to_canvas(occluder_id, tile_map_node->get_canvas()); rs->canvas_light_occluder_set_light_mask(occluder_id, tile_set->get_occlusion_layer_light_mask(i)); q.occluders[E_cell.value] = occluder_id; } @@ -1355,36 +277,35 @@ void TileMap::_rendering_update_dirty_quadrants(SelfList<TileMapQuadrant>::List q_list_element = q_list_element->next(); } - // Reset the drawing indices + // Reset the drawing indices. if (_rendering_quadrant_order_dirty) { int index = -(int64_t)0x80000000; //always must be drawn below children. - for (TileMapLayer &layer : layers) { - // Sort the quadrants coords per local coordinates. - RBMap<Vector2, Vector2i, TileMapQuadrant::CoordsWorldComparator> local_to_map; - for (const KeyValue<Vector2i, TileMapQuadrant> &E : layer.quadrant_map) { - local_to_map[map_to_local(E.key)] = E.key; - } + // Sort the quadrants coords per local coordinates. + RBMap<Vector2, Vector2i, TileMapQuadrant::CoordsWorldComparator> local_to_map; + for (const KeyValue<Vector2i, TileMapQuadrant> &E : quadrant_map) { + local_to_map[tile_map_node->map_to_local(E.key)] = E.key; + } - // Sort the quadrants. - for (const KeyValue<Vector2, Vector2i> &E : local_to_map) { - TileMapQuadrant &q = layer.quadrant_map[E.value]; - for (const RID &ci : q.canvas_items) { - RS::get_singleton()->canvas_item_set_draw_index(ci, index++); - } + // Sort the quadrants. + for (const KeyValue<Vector2, Vector2i> &E : local_to_map) { + TileMapQuadrant &q = quadrant_map[E.value]; + for (const RID &ci : q.canvas_items) { + RS::get_singleton()->canvas_item_set_draw_index(ci, index++); } } _rendering_quadrant_order_dirty = false; } } -void TileMap::_rendering_create_quadrant(TileMapQuadrant *p_quadrant) { +void TileMapLayer::_rendering_create_quadrant(TileMapQuadrant *p_quadrant) { + const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); ERR_FAIL_COND(!tile_set.is_valid()); _rendering_quadrant_order_dirty = true; } -void TileMap::_rendering_cleanup_quadrant(TileMapQuadrant *p_quadrant) { +void TileMapLayer::_rendering_cleanup_quadrant(TileMapQuadrant *p_quadrant) { ERR_FAIL_NULL(RenderingServer::get_singleton()); // Free the canvas items. for (const RID &ci : p_quadrant->canvas_items) { @@ -1399,7 +320,8 @@ void TileMap::_rendering_cleanup_quadrant(TileMapQuadrant *p_quadrant) { p_quadrant->occluders.clear(); } -void TileMap::_rendering_draw_quadrant_debug(TileMapQuadrant *p_quadrant) { +void TileMapLayer::_rendering_draw_quadrant_debug(TileMapQuadrant *p_quadrant) { + const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); ERR_FAIL_COND(!tile_set.is_valid()); if (!Engine::get_singleton()->is_editor_hint()) { @@ -1408,9 +330,9 @@ void TileMap::_rendering_draw_quadrant_debug(TileMapQuadrant *p_quadrant) { // Draw a placeholder for tiles needing one. RenderingServer *rs = RenderingServer::get_singleton(); - Vector2 quadrant_pos = map_to_local(p_quadrant->coords * get_effective_quadrant_size(p_quadrant->layer)); + Vector2 quadrant_pos = tile_map_node->map_to_local(p_quadrant->coords * get_effective_quadrant_size()); for (const Vector2i &E_cell : p_quadrant->cells) { - const TileMapCell &c = get_cell(p_quadrant->layer, E_cell, true); + const TileMapCell &c = get_cell(E_cell, true); TileSetSource *source; if (tile_set->has_source(c.source_id)) { @@ -1440,7 +362,7 @@ void TileMap::_rendering_draw_quadrant_debug(TileMapQuadrant *p_quadrant) { // Draw a placeholder tile. Transform2D cell_to_quadrant; - cell_to_quadrant.set_origin(map_to_local(E_cell) - quadrant_pos); + cell_to_quadrant.set_origin(tile_map_node->map_to_local(E_cell) - quadrant_pos); rs->canvas_item_add_set_transform(p_quadrant->debug_canvas_item, cell_to_quadrant); rs->canvas_item_add_circle(p_quadrant->debug_canvas_item, Vector2(), MIN(tile_set->get_tile_size().x, tile_set->get_tile_size().y) / 4.0, color); } @@ -1449,167 +371,17 @@ void TileMap::_rendering_draw_quadrant_debug(TileMapQuadrant *p_quadrant) { } } -void TileMap::draw_tile(RID p_canvas_item, const Vector2 &p_position, const Ref<TileSet> p_tile_set, int p_atlas_source_id, const Vector2i &p_atlas_coords, int p_alternative_tile, int p_frame, Color p_modulation, const TileData *p_tile_data_override, real_t p_animation_offset) { - ERR_FAIL_COND(!p_tile_set.is_valid()); - ERR_FAIL_COND(!p_tile_set->has_source(p_atlas_source_id)); - ERR_FAIL_COND(!p_tile_set->get_source(p_atlas_source_id)->has_tile(p_atlas_coords)); - ERR_FAIL_COND(!p_tile_set->get_source(p_atlas_source_id)->has_alternative_tile(p_atlas_coords, p_alternative_tile)); - TileSetSource *source = *p_tile_set->get_source(p_atlas_source_id); - TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); - if (atlas_source) { - // Check for the frame. - if (p_frame >= 0) { - ERR_FAIL_INDEX(p_frame, atlas_source->get_tile_animation_frames_count(p_atlas_coords)); - } - - // Get the texture. - Ref<Texture2D> tex = atlas_source->get_runtime_texture(); - if (!tex.is_valid()) { - return; - } - - // Check if we are in the texture, return otherwise. - Vector2i grid_size = atlas_source->get_atlas_grid_size(); - if (p_atlas_coords.x >= grid_size.x || p_atlas_coords.y >= grid_size.y) { - return; - } - - // Get tile data. - const TileData *tile_data = p_tile_data_override ? p_tile_data_override : atlas_source->get_tile_data(p_atlas_coords, p_alternative_tile); - - // Get the tile modulation. - Color modulate = tile_data->get_modulate() * p_modulation; - - // Compute the offset. - Vector2 tile_offset = tile_data->get_texture_origin(); - - // Get destination rect. - Rect2 dest_rect; - dest_rect.size = atlas_source->get_runtime_tile_texture_region(p_atlas_coords).size; - dest_rect.size.x += FP_ADJUST; - dest_rect.size.y += FP_ADJUST; - - bool transpose = tile_data->get_transpose(); - if (transpose) { - dest_rect.position = (p_position - Vector2(dest_rect.size.y, dest_rect.size.x) / 2 - tile_offset); - } else { - dest_rect.position = (p_position - dest_rect.size / 2 - tile_offset); - } - - if (tile_data->get_flip_h()) { - dest_rect.size.x = -dest_rect.size.x; - } - - if (tile_data->get_flip_v()) { - dest_rect.size.y = -dest_rect.size.y; - } - - // Draw the tile. - if (p_frame >= 0) { - Rect2i source_rect = atlas_source->get_runtime_tile_texture_region(p_atlas_coords, p_frame); - tex->draw_rect_region(p_canvas_item, dest_rect, source_rect, modulate, transpose, p_tile_set->is_uv_clipping()); - } else if (atlas_source->get_tile_animation_frames_count(p_atlas_coords) == 1) { - Rect2i source_rect = atlas_source->get_runtime_tile_texture_region(p_atlas_coords, 0); - tex->draw_rect_region(p_canvas_item, dest_rect, source_rect, modulate, transpose, p_tile_set->is_uv_clipping()); - } else { - real_t speed = atlas_source->get_tile_animation_speed(p_atlas_coords); - real_t animation_duration = atlas_source->get_tile_animation_total_duration(p_atlas_coords) / speed; - real_t time = 0.0; - for (int frame = 0; frame < atlas_source->get_tile_animation_frames_count(p_atlas_coords); frame++) { - real_t frame_duration = atlas_source->get_tile_animation_frame_duration(p_atlas_coords, frame) / speed; - RenderingServer::get_singleton()->canvas_item_add_animation_slice(p_canvas_item, animation_duration, time, time + frame_duration, p_animation_offset); - - Rect2i source_rect = atlas_source->get_runtime_tile_texture_region(p_atlas_coords, frame); - tex->draw_rect_region(p_canvas_item, dest_rect, source_rect, modulate, transpose, p_tile_set->is_uv_clipping()); - - time += frame_duration; - } - RenderingServer::get_singleton()->canvas_item_add_animation_slice(p_canvas_item, 1.0, 0.0, 1.0, 0.0); - } - } -} - /////////////////////////////// Physics ////////////////////////////////////// -void TileMap::_physics_notification(int p_what) { - switch (p_what) { - case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { - bool in_editor = false; -#ifdef TOOLS_ENABLED - in_editor = Engine::get_singleton()->is_editor_hint(); -#endif - if (is_inside_tree() && collision_animatable && !in_editor) { - // Update transform on the physics tick when in animatable mode. - last_valid_transform = new_transform; - set_notify_local_transform(false); - set_global_transform(new_transform); - set_notify_local_transform(true); - } - } break; - - case NOTIFICATION_TRANSFORM_CHANGED: { - bool in_editor = false; -#ifdef TOOLS_ENABLED - in_editor = Engine::get_singleton()->is_editor_hint(); -#endif - if (is_inside_tree() && (!collision_animatable || in_editor)) { - // Update the new transform directly if we are not in animatable mode. - Transform2D gl_transform = get_global_transform(); - for (TileMapLayer &layer : layers) { - for (KeyValue<Vector2i, TileMapQuadrant> &E : layer.quadrant_map) { - TileMapQuadrant &q = E.value; - - for (RID body : q.bodies) { - Transform2D xform; - xform.set_origin(map_to_local(bodies_coords[body])); - xform = gl_transform * xform; - PhysicsServer2D::get_singleton()->body_set_state(body, PhysicsServer2D::BODY_STATE_TRANSFORM, xform); - } - } - } - } - } break; - - case NOTIFICATION_LOCAL_TRANSFORM_CHANGED: { - bool in_editor = false; -#ifdef TOOLS_ENABLED - in_editor = Engine::get_singleton()->is_editor_hint(); -#endif - if (is_inside_tree() && !in_editor && collision_animatable) { - // Only active when animatable. Send the new transform to the physics... - new_transform = get_global_transform(); - for (TileMapLayer &layer : layers) { - for (KeyValue<Vector2i, TileMapQuadrant> &E : layer.quadrant_map) { - TileMapQuadrant &q = E.value; - - for (RID body : q.bodies) { - Transform2D xform; - xform.set_origin(map_to_local(bodies_coords[body])); - xform = new_transform * xform; - - PhysicsServer2D::get_singleton()->body_set_state(body, PhysicsServer2D::BODY_STATE_TRANSFORM, xform); - } - } - } - - // ... but then revert changes. - set_notify_local_transform(false); - set_global_transform(last_valid_transform); - set_notify_local_transform(true); - } - } break; - } -} - -void TileMap::_physics_update_dirty_quadrants(SelfList<TileMapQuadrant>::List &r_dirty_quadrant_list) { - ERR_FAIL_COND(!is_inside_tree()); +void TileMapLayer::_physics_update_dirty_quadrants(SelfList<TileMapQuadrant>::List &r_dirty_quadrant_list) { + ERR_FAIL_COND(!tile_map_node->is_inside_tree()); + const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); ERR_FAIL_COND(!tile_set.is_valid()); - Transform2D gl_transform = get_global_transform(); - last_valid_transform = gl_transform; - new_transform = gl_transform; + Transform2D gl_transform = tile_map_node->get_global_transform(); + PhysicsServer2D *ps = PhysicsServer2D::get_singleton(); - RID space = get_world_2d()->get_space(); + RID space = tile_map_node->get_world_2d()->get_space(); SelfList<TileMapQuadrant> *q_list_element = r_dirty_quadrant_list.first(); while (q_list_element) { @@ -1624,7 +396,7 @@ void TileMap::_physics_update_dirty_quadrants(SelfList<TileMapQuadrant>::List &r // Recreate bodies and shapes. for (const Vector2i &E_cell : q.cells) { - TileMapCell c = get_cell(q.layer, E_cell, true); + TileMapCell c = get_cell(E_cell, true); TileSetSource *source; if (tile_set->has_source(c.source_id)) { @@ -1650,16 +422,15 @@ void TileMap::_physics_update_dirty_quadrants(SelfList<TileMapQuadrant>::List &r // Create the body. RID body = ps->body_create(); bodies_coords[body] = E_cell; - bodies_layers[body] = q.layer; - ps->body_set_mode(body, collision_animatable ? PhysicsServer2D::BODY_MODE_KINEMATIC : PhysicsServer2D::BODY_MODE_STATIC); + ps->body_set_mode(body, tile_map_node->is_collision_animatable() ? PhysicsServer2D::BODY_MODE_KINEMATIC : PhysicsServer2D::BODY_MODE_STATIC); ps->body_set_space(body, space); Transform2D xform; - xform.set_origin(map_to_local(E_cell)); + xform.set_origin(tile_map_node->map_to_local(E_cell)); xform = gl_transform * xform; ps->body_set_state(body, PhysicsServer2D::BODY_STATE_TRANSFORM, xform); - ps->body_attach_object_instance_id(body, get_instance_id()); + ps->body_attach_object_instance_id(body, tile_map_node->get_instance_id()); ps->body_set_collision_layer(body, physics_layer); ps->body_set_collision_mask(body, physics_mask); ps->body_set_pickable(body, false); @@ -1701,29 +472,29 @@ void TileMap::_physics_update_dirty_quadrants(SelfList<TileMapQuadrant>::List &r } } -void TileMap::_physics_cleanup_quadrant(TileMapQuadrant *p_quadrant) { +void TileMapLayer::_physics_cleanup_quadrant(TileMapQuadrant *p_quadrant) { // Remove a quadrant. ERR_FAIL_NULL(PhysicsServer2D::get_singleton()); for (RID body : p_quadrant->bodies) { bodies_coords.erase(body); - bodies_layers.erase(body); PhysicsServer2D::get_singleton()->free(body); } p_quadrant->bodies.clear(); } -void TileMap::_physics_draw_quadrant_debug(TileMapQuadrant *p_quadrant) { +void TileMapLayer::_physics_draw_quadrant_debug(TileMapQuadrant *p_quadrant) { // Draw the debug collision shapes. + const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); ERR_FAIL_COND(!tile_set.is_valid()); - if (!get_tree()) { + if (!tile_map_node->get_tree()) { return; } bool show_collision = false; - switch (collision_visibility_mode) { + switch (tile_map_node->get_collision_visibility_mode()) { case TileMap::VISIBILITY_MODE_DEFAULT: - show_collision = !Engine::get_singleton()->is_editor_hint() && (get_tree() && get_tree()->is_debugging_collisions_hint()); + show_collision = !Engine::get_singleton()->is_editor_hint() && tile_map_node->get_tree()->is_debugging_collisions_hint(); break; case TileMap::VISIBILITY_MODE_FORCE_HIDE: show_collision = false; @@ -1739,14 +510,14 @@ void TileMap::_physics_draw_quadrant_debug(TileMapQuadrant *p_quadrant) { RenderingServer *rs = RenderingServer::get_singleton(); PhysicsServer2D *ps = PhysicsServer2D::get_singleton(); - Color debug_collision_color = get_tree()->get_debug_collisions_color(); + Color debug_collision_color = tile_map_node->get_tree()->get_debug_collisions_color(); Vector<Color> color; color.push_back(debug_collision_color); - Vector2 quadrant_pos = map_to_local(p_quadrant->coords * get_effective_quadrant_size(p_quadrant->layer)); + Vector2 quadrant_pos = tile_map_node->map_to_local(p_quadrant->coords * get_effective_quadrant_size()); Transform2D quadrant_to_local; quadrant_to_local.set_origin(quadrant_pos); - Transform2D global_to_quadrant = (get_global_transform() * quadrant_to_local).affine_inverse(); + Transform2D global_to_quadrant = (tile_map_node->get_global_transform() * quadrant_to_local).affine_inverse(); for (RID body : p_quadrant->bodies) { Transform2D body_to_quadrant = global_to_quadrant * Transform2D(ps->body_get_state(body, PhysicsServer2D::BODY_STATE_TRANSFORM)); @@ -1767,36 +538,12 @@ void TileMap::_physics_draw_quadrant_debug(TileMapQuadrant *p_quadrant) { /////////////////////////////// Navigation ////////////////////////////////////// -void TileMap::_navigation_notification(int p_what) { - switch (p_what) { - case NOTIFICATION_TRANSFORM_CHANGED: { - if (is_inside_tree()) { - for (TileMapLayer &layer : layers) { - Transform2D tilemap_xform = get_global_transform(); - for (KeyValue<Vector2i, TileMapQuadrant> &E_quadrant : layer.quadrant_map) { - TileMapQuadrant &q = E_quadrant.value; - for (const KeyValue<Vector2i, Vector<RID>> &E_region : q.navigation_regions) { - for (const RID ®ion : E_region.value) { - if (!region.is_valid()) { - continue; - } - Transform2D tile_transform; - tile_transform.set_origin(map_to_local(E_region.key)); - NavigationServer2D::get_singleton()->region_set_transform(region, tilemap_xform * tile_transform); - } - } - } - } - } - } break; - } -} - -void TileMap::_navigation_update_dirty_quadrants(SelfList<TileMapQuadrant>::List &r_dirty_quadrant_list) { - ERR_FAIL_COND(!is_inside_tree()); +void TileMapLayer::_navigation_update_dirty_quadrants(SelfList<TileMapQuadrant>::List &r_dirty_quadrant_list) { + ERR_FAIL_COND(!tile_map_node->is_inside_tree()); + const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); ERR_FAIL_COND(!tile_set.is_valid()); - Transform2D tilemap_xform = get_global_transform(); + Transform2D tilemap_xform = tile_map_node->get_global_transform(); SelfList<TileMapQuadrant> *q_list_element = r_dirty_quadrant_list.first(); while (q_list_element) { TileMapQuadrant &q = *q_list_element->self(); @@ -1815,7 +562,7 @@ void TileMap::_navigation_update_dirty_quadrants(SelfList<TileMapQuadrant>::List // Get the navigation polygons and create regions. for (const Vector2i &E_cell : q.cells) { - TileMapCell c = get_cell(q.layer, E_cell, true); + TileMapCell c = get_cell(E_cell, true); TileSetSource *source; if (tile_set->has_source(c.source_id)) { @@ -1835,24 +582,21 @@ void TileMap::_navigation_update_dirty_quadrants(SelfList<TileMapQuadrant>::List } q.navigation_regions[E_cell].resize(tile_set->get_navigation_layers_count()); - for (int layer_index = 0; layer_index < tile_set->get_navigation_layers_count(); layer_index++) { - if (layer_index >= (int)layers.size() || !layers[layer_index].navigation_map.is_valid()) { - continue; - } + for (int navigation_layer_index = 0; navigation_layer_index < tile_set->get_navigation_layers_count(); navigation_layer_index++) { Ref<NavigationPolygon> navigation_polygon; - navigation_polygon = tile_data->get_navigation_polygon(layer_index); + navigation_polygon = tile_data->get_navigation_polygon(navigation_layer_index); if (navigation_polygon.is_valid()) { Transform2D tile_transform; - tile_transform.set_origin(map_to_local(E_cell)); + tile_transform.set_origin(tile_map_node->map_to_local(E_cell)); RID region = NavigationServer2D::get_singleton()->region_create(); - NavigationServer2D::get_singleton()->region_set_owner_id(region, get_instance_id()); - NavigationServer2D::get_singleton()->region_set_map(region, layers[layer_index].navigation_map); + NavigationServer2D::get_singleton()->region_set_owner_id(region, tile_map_node->get_instance_id()); + NavigationServer2D::get_singleton()->region_set_map(region, navigation_map); NavigationServer2D::get_singleton()->region_set_transform(region, tilemap_xform * tile_transform); - NavigationServer2D::get_singleton()->region_set_navigation_layers(region, tile_set->get_navigation_layer_layers(layer_index)); + NavigationServer2D::get_singleton()->region_set_navigation_layers(region, tile_set->get_navigation_layer_layers(navigation_layer_index)); NavigationServer2D::get_singleton()->region_set_navigation_polygon(region, navigation_polygon); - q.navigation_regions[E_cell].write[layer_index] = region; + q.navigation_regions[E_cell].write[navigation_layer_index] = region; } } } @@ -1863,7 +607,39 @@ void TileMap::_navigation_update_dirty_quadrants(SelfList<TileMapQuadrant>::List } } -void TileMap::_navigation_cleanup_quadrant(TileMapQuadrant *p_quadrant) { +void TileMapLayer::_navigation_update() { + ERR_FAIL_NULL(NavigationServer2D::get_singleton()); + + if (!navigation_map.is_valid()) { + if (layer_index_in_tile_map_node == 0 && tile_map_node->is_inside_tree()) { + // Use the default World2D navigation map for the first layer when empty. + navigation_map = tile_map_node->get_world_2d()->get_navigation_map(); + uses_world_navigation_map = true; + } else { + RID new_layer_map = NavigationServer2D::get_singleton()->map_create(); + // Set the default NavigationPolygon cell_size on the new map as a mismatch causes an error. + NavigationServer2D::get_singleton()->map_set_cell_size(new_layer_map, 1.0); + NavigationServer2D::get_singleton()->map_set_active(new_layer_map, true); + navigation_map = new_layer_map; + uses_world_navigation_map = false; + } + } +} + +void TileMapLayer::_navigation_cleanup() { + ERR_FAIL_NULL(NavigationServer2D::get_singleton()); + + if (navigation_map.is_valid()) { + if (uses_world_navigation_map) { + // Do not delete the World2D default navigation map. + return; + } + NavigationServer2D::get_singleton()->free(navigation_map); + navigation_map = RID(); + } +} + +void TileMapLayer::_navigation_cleanup_quadrant(TileMapQuadrant *p_quadrant) { // Clear navigation shapes in the quadrant. ERR_FAIL_NULL(NavigationServer2D::get_singleton()); for (const KeyValue<Vector2i, Vector<RID>> &E : p_quadrant->navigation_regions) { @@ -1878,18 +654,19 @@ void TileMap::_navigation_cleanup_quadrant(TileMapQuadrant *p_quadrant) { p_quadrant->navigation_regions.clear(); } -void TileMap::_navigation_draw_quadrant_debug(TileMapQuadrant *p_quadrant) { +void TileMapLayer::_navigation_draw_quadrant_debug(TileMapQuadrant *p_quadrant) { // Draw the debug collision shapes. + const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); ERR_FAIL_COND(!tile_set.is_valid()); - if (!get_tree()) { + if (!tile_map_node->get_tree()) { return; } bool show_navigation = false; - switch (navigation_visibility_mode) { + switch (tile_map_node->get_navigation_visibility_mode()) { case TileMap::VISIBILITY_MODE_DEFAULT: - show_navigation = !Engine::get_singleton()->is_editor_hint() && (get_tree() && get_tree()->is_debugging_navigation_hint()); + show_navigation = !Engine::get_singleton()->is_editor_hint() && tile_map_node->get_tree()->is_debugging_navigation_hint(); break; case TileMap::VISIBILITY_MODE_FORCE_HIDE: show_navigation = false; @@ -1914,10 +691,10 @@ void TileMap::_navigation_draw_quadrant_debug(TileMapQuadrant *p_quadrant) { RandomPCG rand; - Vector2 quadrant_pos = map_to_local(p_quadrant->coords * get_effective_quadrant_size(p_quadrant->layer)); + Vector2 quadrant_pos = tile_map_node->map_to_local(p_quadrant->coords * get_effective_quadrant_size()); for (const Vector2i &E_cell : p_quadrant->cells) { - TileMapCell c = get_cell(p_quadrant->layer, E_cell, true); + TileMapCell c = get_cell(E_cell, true); TileSetSource *source; if (tile_set->has_source(c.source_id)) { @@ -1937,7 +714,7 @@ void TileMap::_navigation_draw_quadrant_debug(TileMapQuadrant *p_quadrant) { } Transform2D cell_to_quadrant; - cell_to_quadrant.set_origin(map_to_local(E_cell) - quadrant_pos); + cell_to_quadrant.set_origin(tile_map_node->map_to_local(E_cell) - quadrant_pos); rs->canvas_item_add_set_transform(p_quadrant->debug_canvas_item, cell_to_quadrant); for (int layer_index = 0; layer_index < tile_set->get_navigation_layers_count(); layer_index++) { @@ -1989,7 +766,8 @@ void TileMap::_navigation_draw_quadrant_debug(TileMapQuadrant *p_quadrant) { /////////////////////////////// Scenes ////////////////////////////////////// -void TileMap::_scenes_update_dirty_quadrants(SelfList<TileMapQuadrant>::List &r_dirty_quadrant_list) { +void TileMapLayer::_scenes_update_dirty_quadrants(SelfList<TileMapQuadrant>::List &r_dirty_quadrant_list) { + const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); ERR_FAIL_COND(!tile_set.is_valid()); SelfList<TileMapQuadrant> *q_list_element = r_dirty_quadrant_list.first(); @@ -1999,7 +777,7 @@ void TileMap::_scenes_update_dirty_quadrants(SelfList<TileMapQuadrant>::List &r_ // Clear the scenes if instance cache was cleared. if (instantiated_scenes.is_empty()) { for (const KeyValue<Vector2i, String> &E : q.scenes) { - Node *node = get_node_or_null(E.value); + Node *node = tile_map_node->get_node_or_null(E.value); if (node) { node->queue_free(); } @@ -2010,16 +788,15 @@ void TileMap::_scenes_update_dirty_quadrants(SelfList<TileMapQuadrant>::List &r_ // Recreate the scenes. for (const Vector2i &E_cell : q.cells) { - Vector3i cell_coords = Vector3i(q.layer, E_cell.x, E_cell.y); - if (instantiated_scenes.has(cell_coords)) { + if (instantiated_scenes.has(E_cell)) { // Skip scene if the instance was cached (to avoid recreating scenes unnecessarily). continue; } if (!Engine::get_singleton()->is_editor_hint()) { - instantiated_scenes.insert(cell_coords); + instantiated_scenes.insert(E_cell); } - const TileMapCell &c = get_cell(q.layer, E_cell, true); + const TileMapCell &c = get_cell(E_cell, true); TileSetSource *source; if (tile_set->has_source(c.source_id)) { @@ -2037,13 +814,13 @@ void TileMap::_scenes_update_dirty_quadrants(SelfList<TileMapQuadrant>::List &r_ Control *scene_as_control = Object::cast_to<Control>(scene); Node2D *scene_as_node2d = Object::cast_to<Node2D>(scene); if (scene_as_control) { - scene_as_control->set_position(map_to_local(E_cell) + scene_as_control->get_position()); + scene_as_control->set_position(tile_map_node->map_to_local(E_cell) + scene_as_control->get_position()); } else if (scene_as_node2d) { Transform2D xform; - xform.set_origin(map_to_local(E_cell)); + xform.set_origin(tile_map_node->map_to_local(E_cell)); scene_as_node2d->set_transform(xform * scene_as_node2d->get_transform()); } - add_child(scene); + tile_map_node->add_child(scene); q.scenes[E_cell] = scene->get_name(); } } @@ -2054,11 +831,11 @@ void TileMap::_scenes_update_dirty_quadrants(SelfList<TileMapQuadrant>::List &r_ } } -void TileMap::_scenes_cleanup_quadrant(TileMapQuadrant *p_quadrant) { +void TileMapLayer::_scenes_cleanup_quadrant(TileMapQuadrant *p_quadrant) { // Clear the scenes if instance cache was cleared. if (instantiated_scenes.is_empty()) { for (const KeyValue<Vector2i, String> &E : p_quadrant->scenes) { - Node *node = get_node_or_null(E.value); + Node *node = tile_map_node->get_node_or_null(E.value); if (node) { node->queue_free(); } @@ -2067,7 +844,8 @@ void TileMap::_scenes_cleanup_quadrant(TileMapQuadrant *p_quadrant) { } } -void TileMap::_scenes_draw_quadrant_debug(TileMapQuadrant *p_quadrant) { +void TileMapLayer::_scenes_draw_quadrant_debug(TileMapQuadrant *p_quadrant) { + const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); ERR_FAIL_COND(!tile_set.is_valid()); if (!Engine::get_singleton()->is_editor_hint()) { @@ -2076,9 +854,9 @@ void TileMap::_scenes_draw_quadrant_debug(TileMapQuadrant *p_quadrant) { // Draw a placeholder for scenes needing one. RenderingServer *rs = RenderingServer::get_singleton(); - Vector2 quadrant_pos = map_to_local(p_quadrant->coords * get_effective_quadrant_size(p_quadrant->layer)); + Vector2 quadrant_pos = tile_map_node->map_to_local(p_quadrant->coords * get_effective_quadrant_size()); for (const Vector2i &E_cell : p_quadrant->cells) { - const TileMapCell &c = get_cell(p_quadrant->layer, E_cell, true); + const TileMapCell &c = get_cell(E_cell, true); TileSetSource *source; if (tile_set->has_source(c.source_id)) { @@ -2106,7 +884,7 @@ void TileMap::_scenes_draw_quadrant_debug(TileMapQuadrant *p_quadrant) { // Draw a placeholder tile. Transform2D cell_to_quadrant; - cell_to_quadrant.set_origin(map_to_local(E_cell) - quadrant_pos); + cell_to_quadrant.set_origin(tile_map_node->map_to_local(E_cell) - quadrant_pos); rs->canvas_item_add_set_transform(p_quadrant->debug_canvas_item, cell_to_quadrant); rs->canvas_item_add_circle(p_quadrant->debug_canvas_item, Vector2(), MIN(tile_set->get_tile_size().x, tile_set->get_tile_size().y) / 4.0, color); } @@ -2115,249 +893,51 @@ void TileMap::_scenes_draw_quadrant_debug(TileMapQuadrant *p_quadrant) { } } -void TileMap::set_cell(int p_layer, const Vector2i &p_coords, int p_source_id, const Vector2i p_atlas_coords, int p_alternative_tile) { - ERR_FAIL_INDEX(p_layer, (int)layers.size()); - - // Set the current cell tile (using integer position). - HashMap<Vector2i, TileMapCell> &tile_map = layers[p_layer].tile_map; - Vector2i pk(p_coords); - HashMap<Vector2i, TileMapCell>::Iterator E = tile_map.find(pk); - - int source_id = p_source_id; - Vector2i atlas_coords = p_atlas_coords; - int alternative_tile = p_alternative_tile; - - if ((source_id == TileSet::INVALID_SOURCE || atlas_coords == TileSetSource::INVALID_ATLAS_COORDS || alternative_tile == TileSetSource::INVALID_TILE_ALTERNATIVE) && - (source_id != TileSet::INVALID_SOURCE || atlas_coords != TileSetSource::INVALID_ATLAS_COORDS || alternative_tile != TileSetSource::INVALID_TILE_ALTERNATIVE)) { - source_id = TileSet::INVALID_SOURCE; - atlas_coords = TileSetSource::INVALID_ATLAS_COORDS; - alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE; - } +///////////////////////////////////////////////////////////////////// - if (!E && source_id == TileSet::INVALID_SOURCE) { - return; // Nothing to do, the tile is already empty. - } - - // Get the quadrant - Vector2i qk = _coords_to_quadrant_coords(p_layer, pk); - - HashMap<Vector2i, TileMapQuadrant>::Iterator Q = layers[p_layer].quadrant_map.find(qk); - - if (source_id == TileSet::INVALID_SOURCE) { - // Erase existing cell in the tile map. - tile_map.erase(pk); - - // Erase existing cell in the quadrant. - ERR_FAIL_COND(!Q); - TileMapQuadrant &q = Q->value; - - q.cells.erase(pk); - - // Remove or make the quadrant dirty. - if (q.cells.size() == 0) { - _erase_quadrant(Q); - } else { - _make_quadrant_dirty(Q); - } - - used_rect_cache_dirty = true; - } else { - if (!E) { - // Insert a new cell in the tile map. - E = tile_map.insert(pk, TileMapCell()); - - // Create a new quadrant if needed, then insert the cell if needed. - if (!Q) { - Q = _create_quadrant(p_layer, qk); - } - TileMapQuadrant &q = Q->value; - q.cells.insert(pk); - - } else { - ERR_FAIL_COND(!Q); // TileMapQuadrant should exist... - - if (E->value.source_id == source_id && E->value.get_atlas_coords() == atlas_coords && E->value.alternative_tile == alternative_tile) { - return; // Nothing changed. - } - } - - TileMapCell &c = E->value; - - c.source_id = source_id; - c.set_atlas_coords(atlas_coords); - c.alternative_tile = alternative_tile; - - _make_quadrant_dirty(Q); - used_rect_cache_dirty = true; - } -} - -void TileMap::erase_cell(int p_layer, const Vector2i &p_coords) { - set_cell(p_layer, p_coords, TileSet::INVALID_SOURCE, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE); -} - -int TileMap::get_cell_source_id(int p_layer, const Vector2i &p_coords, bool p_use_proxies) const { - ERR_FAIL_INDEX_V(p_layer, (int)layers.size(), TileSet::INVALID_SOURCE); - - // Get a cell source id from position. - const HashMap<Vector2i, TileMapCell> &tile_map = layers[p_layer].tile_map; - HashMap<Vector2i, TileMapCell>::ConstIterator E = tile_map.find(p_coords); - - if (!E) { - return TileSet::INVALID_SOURCE; - } - - if (p_use_proxies && tile_set.is_valid()) { - Array proxyed = tile_set->map_tile_proxy(E->value.source_id, E->value.get_atlas_coords(), E->value.alternative_tile); - return proxyed[0]; - } - - return E->value.source_id; -} - -Vector2i TileMap::get_cell_atlas_coords(int p_layer, const Vector2i &p_coords, bool p_use_proxies) const { - ERR_FAIL_INDEX_V(p_layer, (int)layers.size(), TileSetSource::INVALID_ATLAS_COORDS); - - // Get a cell source id from position - const HashMap<Vector2i, TileMapCell> &tile_map = layers[p_layer].tile_map; - HashMap<Vector2i, TileMapCell>::ConstIterator E = tile_map.find(p_coords); - - if (!E) { - return TileSetSource::INVALID_ATLAS_COORDS; - } - - if (p_use_proxies && tile_set.is_valid()) { - Array proxyed = tile_set->map_tile_proxy(E->value.source_id, E->value.get_atlas_coords(), E->value.alternative_tile); - return proxyed[1]; - } - - return E->value.get_atlas_coords(); -} - -int TileMap::get_cell_alternative_tile(int p_layer, const Vector2i &p_coords, bool p_use_proxies) const { - ERR_FAIL_INDEX_V(p_layer, (int)layers.size(), TileSetSource::INVALID_TILE_ALTERNATIVE); - - // Get a cell source id from position - const HashMap<Vector2i, TileMapCell> &tile_map = layers[p_layer].tile_map; - HashMap<Vector2i, TileMapCell>::ConstIterator E = tile_map.find(p_coords); - - if (!E) { - return TileSetSource::INVALID_TILE_ALTERNATIVE; - } - - if (p_use_proxies && tile_set.is_valid()) { - Array proxyed = tile_set->map_tile_proxy(E->value.source_id, E->value.get_atlas_coords(), E->value.alternative_tile); - return proxyed[2]; - } - - return E->value.alternative_tile; -} - -TileData *TileMap::get_cell_tile_data(int p_layer, const Vector2i &p_coords, bool p_use_proxies) const { - int source_id = get_cell_source_id(p_layer, p_coords, p_use_proxies); - if (source_id == TileSet::INVALID_SOURCE) { - return nullptr; +void TileMapLayer::_build_runtime_update_tile_data(SelfList<TileMapQuadrant>::List &r_dirty_quadrant_list) { + if (!tile_map_node->GDVIRTUAL_IS_OVERRIDDEN(_use_tile_data_runtime_update) || !tile_map_node->GDVIRTUAL_IS_OVERRIDDEN(_tile_data_runtime_update)) { + return; } - Ref<TileSetAtlasSource> source = tile_set->get_source(source_id); - if (source.is_valid()) { - return source->get_tile_data(get_cell_atlas_coords(p_layer, p_coords, p_use_proxies), get_cell_alternative_tile(p_layer, p_coords, p_use_proxies)); - } + const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); + SelfList<TileMapQuadrant> *q_list_element = r_dirty_quadrant_list.first(); + while (q_list_element) { + TileMapQuadrant &q = *q_list_element->self(); + // Iterate over the cells of the quadrant. + for (const KeyValue<Vector2, Vector2i> &E_cell : q.local_to_map) { + TileMapCell c = get_cell(E_cell.value, true); - return nullptr; -} + TileSetSource *source; + if (tile_set->has_source(c.source_id)) { + source = *tile_set->get_source(c.source_id); -Ref<TileMapPattern> TileMap::get_pattern(int p_layer, TypedArray<Vector2i> p_coords_array) { - ERR_FAIL_INDEX_V(p_layer, (int)layers.size(), nullptr); - ERR_FAIL_COND_V(!tile_set.is_valid(), nullptr); + if (!source->has_tile(c.get_atlas_coords()) || !source->has_alternative_tile(c.get_atlas_coords(), c.alternative_tile)) { + continue; + } - Ref<TileMapPattern> output; - output.instantiate(); - if (p_coords_array.is_empty()) { - return output; - } + TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); + if (atlas_source) { + bool ret = false; + if (tile_map_node->GDVIRTUAL_CALL(_use_tile_data_runtime_update, layer_index_in_tile_map_node, E_cell.value, ret) && ret) { + TileData *tile_data = atlas_source->get_tile_data(c.get_atlas_coords(), c.alternative_tile); - Vector2i min = Vector2i(p_coords_array[0]); - for (int i = 1; i < p_coords_array.size(); i++) { - min = min.min(p_coords_array[i]); - } + // Create the runtime TileData. + TileData *tile_data_runtime_use = tile_data->duplicate(); + tile_data_runtime_use->set_allow_transform(true); + q.runtime_tile_data_cache[E_cell.value] = tile_data_runtime_use; - Vector<Vector2i> coords_in_pattern_array; - coords_in_pattern_array.resize(p_coords_array.size()); - Vector2i ensure_positive_offset; - for (int i = 0; i < p_coords_array.size(); i++) { - Vector2i coords = p_coords_array[i]; - Vector2i coords_in_pattern = coords - min; - if (tile_set->get_tile_shape() != TileSet::TILE_SHAPE_SQUARE) { - if (tile_set->get_tile_layout() == TileSet::TILE_LAYOUT_STACKED) { - if (tile_set->get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_HORIZONTAL && bool(min.y % 2) && bool(coords_in_pattern.y % 2)) { - coords_in_pattern.x -= 1; - if (coords_in_pattern.x < 0) { - ensure_positive_offset.x = 1; + tile_map_node->GDVIRTUAL_CALL(_tile_data_runtime_update, layer_index_in_tile_map_node, E_cell.value, tile_data_runtime_use); } - } else if (tile_set->get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_VERTICAL && bool(min.x % 2) && bool(coords_in_pattern.x % 2)) { - coords_in_pattern.y -= 1; - if (coords_in_pattern.y < 0) { - ensure_positive_offset.y = 1; - } - } - } else if (tile_set->get_tile_layout() == TileSet::TILE_LAYOUT_STACKED_OFFSET) { - if (tile_set->get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_HORIZONTAL && bool(min.y % 2) && bool(coords_in_pattern.y % 2)) { - coords_in_pattern.x += 1; - } else if (tile_set->get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_VERTICAL && bool(min.x % 2) && bool(coords_in_pattern.x % 2)) { - coords_in_pattern.y += 1; } } } - coords_in_pattern_array.write[i] = coords_in_pattern; - } - - for (int i = 0; i < coords_in_pattern_array.size(); i++) { - Vector2i coords = p_coords_array[i]; - Vector2i coords_in_pattern = coords_in_pattern_array[i]; - output->set_cell(coords_in_pattern + ensure_positive_offset, get_cell_source_id(p_layer, coords), get_cell_atlas_coords(p_layer, coords), get_cell_alternative_tile(p_layer, coords)); - } - - return output; -} - -Vector2i TileMap::map_pattern(const Vector2i &p_position_in_tilemap, const Vector2i &p_coords_in_pattern, Ref<TileMapPattern> p_pattern) { - ERR_FAIL_COND_V(p_pattern.is_null(), Vector2i()); - ERR_FAIL_COND_V(!p_pattern->has_cell(p_coords_in_pattern), Vector2i()); - - Vector2i output = p_position_in_tilemap + p_coords_in_pattern; - if (tile_set->get_tile_shape() != TileSet::TILE_SHAPE_SQUARE) { - if (tile_set->get_tile_layout() == TileSet::TILE_LAYOUT_STACKED) { - if (tile_set->get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_HORIZONTAL && bool(p_position_in_tilemap.y % 2) && bool(p_coords_in_pattern.y % 2)) { - output.x += 1; - } else if (tile_set->get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_VERTICAL && bool(p_position_in_tilemap.x % 2) && bool(p_coords_in_pattern.x % 2)) { - output.y += 1; - } - } else if (tile_set->get_tile_layout() == TileSet::TILE_LAYOUT_STACKED_OFFSET) { - if (tile_set->get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_HORIZONTAL && bool(p_position_in_tilemap.y % 2) && bool(p_coords_in_pattern.y % 2)) { - output.x -= 1; - } else if (tile_set->get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_VERTICAL && bool(p_position_in_tilemap.x % 2) && bool(p_coords_in_pattern.x % 2)) { - output.y -= 1; - } - } - } - - return output; -} - -void TileMap::set_pattern(int p_layer, const Vector2i &p_position, const Ref<TileMapPattern> p_pattern) { - ERR_FAIL_INDEX(p_layer, (int)layers.size()); - ERR_FAIL_COND(tile_set.is_null()); - ERR_FAIL_COND(p_pattern.is_null()); - - TypedArray<Vector2i> used_cells = p_pattern->get_used_cells(); - for (int i = 0; i < used_cells.size(); i++) { - Vector2i coords = map_pattern(p_position, used_cells[i], p_pattern); - set_cell(p_layer, coords, p_pattern->get_cell_source_id(used_cells[i]), p_pattern->get_cell_atlas_coords(used_cells[i]), p_pattern->get_cell_alternative_tile(used_cells[i])); + q_list_element = q_list_element->next(); } } -TileSet::TerrainsPattern TileMap::_get_best_terrain_pattern_for_constraints(int p_terrain_set, const Vector2i &p_position, const RBSet<TerrainConstraint> &p_constraints, TileSet::TerrainsPattern p_current_pattern) { +TileSet::TerrainsPattern TileMapLayer::_get_best_terrain_pattern_for_constraints(int p_terrain_set, const Vector2i &p_position, const RBSet<TerrainConstraint> &p_constraints, TileSet::TerrainsPattern p_current_pattern) { + const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); if (!tile_set.is_valid()) { return TileSet::TerrainsPattern(); } @@ -2368,8 +948,8 @@ TileSet::TerrainsPattern TileMap::_get_best_terrain_pattern_for_constraints(int for (TileSet::TerrainsPattern &terrain_pattern : pattern_set) { int score = 0; - // Check the center bit constraint - TerrainConstraint terrain_constraint = TerrainConstraint(this, p_position, terrain_pattern.get_terrain()); + // Check the center bit constraint. + TerrainConstraint terrain_constraint = TerrainConstraint(tile_map_node, p_position, terrain_pattern.get_terrain()); const RBSet<TerrainConstraint>::Element *in_set_constraint_element = p_constraints.find(terrain_constraint); if (in_set_constraint_element) { if (in_set_constraint_element->get().get_terrain() != terrain_constraint.get_terrain()) { @@ -2385,7 +965,7 @@ TileSet::TerrainsPattern TileMap::_get_best_terrain_pattern_for_constraints(int TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); if (tile_set->is_valid_terrain_peering_bit(p_terrain_set, bit)) { // Check if the bit is compatible with the constraints. - TerrainConstraint terrain_bit_constraint = TerrainConstraint(this, p_position, bit, terrain_pattern.get_terrain_peering_bit(bit)); + TerrainConstraint terrain_bit_constraint = TerrainConstraint(tile_map_node, p_position, bit, terrain_pattern.get_terrain_peering_bit(bit)); in_set_constraint_element = p_constraints.find(terrain_bit_constraint); if (in_set_constraint_element) { if (in_set_constraint_element->get().get_terrain() != terrain_bit_constraint.get_terrain()) { @@ -2404,7 +984,7 @@ TileSet::TerrainsPattern TileMap::_get_best_terrain_pattern_for_constraints(int terrain_pattern_score[terrain_pattern] = score; } - // Compute the minimum score + // Compute the minimum score. TileSet::TerrainsPattern min_score_pattern = p_current_pattern; int min_score = INT32_MAX; for (KeyValue<TileSet::TerrainsPattern, int> E : terrain_pattern_score) { @@ -2417,19 +997,20 @@ TileSet::TerrainsPattern TileMap::_get_best_terrain_pattern_for_constraints(int return min_score_pattern; } -RBSet<TileMap::TerrainConstraint> TileMap::_get_terrain_constraints_from_added_pattern(const Vector2i &p_position, int p_terrain_set, TileSet::TerrainsPattern p_terrains_pattern) const { +RBSet<TerrainConstraint> TileMapLayer::_get_terrain_constraints_from_added_pattern(const Vector2i &p_position, int p_terrain_set, TileSet::TerrainsPattern p_terrains_pattern) const { + const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); if (!tile_set.is_valid()) { return RBSet<TerrainConstraint>(); } // Compute the constraints needed from the surrounding tiles. RBSet<TerrainConstraint> output; - output.insert(TerrainConstraint(this, p_position, p_terrains_pattern.get_terrain())); + output.insert(TerrainConstraint(tile_map_node, p_position, p_terrains_pattern.get_terrain())); for (uint32_t i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { TileSet::CellNeighbor side = TileSet::CellNeighbor(i); if (tile_set->is_valid_terrain_peering_bit(p_terrain_set, side)) { - TerrainConstraint c = TerrainConstraint(this, p_position, side, p_terrains_pattern.get_terrain_peering_bit(side)); + TerrainConstraint c = TerrainConstraint(tile_map_node, p_position, side, p_terrains_pattern.get_terrain_peering_bit(side)); output.insert(c); } } @@ -2437,13 +1018,13 @@ RBSet<TileMap::TerrainConstraint> TileMap::_get_terrain_constraints_from_added_p return output; } -RBSet<TileMap::TerrainConstraint> TileMap::_get_terrain_constraints_from_painted_cells_list(int p_layer, const RBSet<Vector2i> &p_painted, int p_terrain_set, bool p_ignore_empty_terrains) const { +RBSet<TerrainConstraint> TileMapLayer::_get_terrain_constraints_from_painted_cells_list(const RBSet<Vector2i> &p_painted, int p_terrain_set, bool p_ignore_empty_terrains) const { + const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); if (!tile_set.is_valid()) { return RBSet<TerrainConstraint>(); } ERR_FAIL_INDEX_V(p_terrain_set, tile_set->get_terrain_sets_count(), RBSet<TerrainConstraint>()); - ERR_FAIL_INDEX_V(p_layer, (int)layers.size(), RBSet<TerrainConstraint>()); // Build a set of dummy constraints to get the constrained points. RBSet<TerrainConstraint> dummy_constraints; @@ -2451,7 +1032,7 @@ RBSet<TileMap::TerrainConstraint> TileMap::_get_terrain_constraints_from_painted for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { // Iterates over neighbor bits. TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); if (tile_set->is_valid_terrain_peering_bit(p_terrain_set, bit)) { - dummy_constraints.insert(TerrainConstraint(this, E, bit, -1)); + dummy_constraints.insert(TerrainConstraint(tile_map_node, E, bit, -1)); } } } @@ -2465,7 +1046,7 @@ RBSet<TileMap::TerrainConstraint> TileMap::_get_terrain_constraints_from_painted HashMap<Vector2i, TileSet::CellNeighbor> overlapping_terrain_bits = E_constraint.get_overlapping_coords_and_peering_bits(); for (const KeyValue<Vector2i, TileSet::CellNeighbor> &E_overlapping : overlapping_terrain_bits) { TileData *neighbor_tile_data = nullptr; - TileMapCell neighbor_cell = get_cell(p_layer, E_overlapping.key); + TileMapCell neighbor_cell = get_cell(E_overlapping.key); if (neighbor_cell.source_id != TileSet::INVALID_SOURCE) { Ref<TileSetSource> source = tile_set->get_source(neighbor_cell.source_id); Ref<TileSetAtlasSource> atlas_source = source; @@ -2504,10 +1085,10 @@ RBSet<TileMap::TerrainConstraint> TileMap::_get_terrain_constraints_from_painted } } - // Add the centers as constraints + // Add the centers as constraints. for (Vector2i E_coords : p_painted) { TileData *tile_data = nullptr; - TileMapCell cell = get_cell(p_layer, E_coords); + TileMapCell cell = get_cell(E_coords); if (cell.source_id != TileSet::INVALID_SOURCE) { Ref<TileSetSource> source = tile_set->get_source(cell.source_id); Ref<TileSetAtlasSource> atlas_source = source; @@ -2518,14 +1099,54 @@ RBSet<TileMap::TerrainConstraint> TileMap::_get_terrain_constraints_from_painted int terrain = (tile_data && tile_data->get_terrain_set() == p_terrain_set) ? tile_data->get_terrain() : -1; if (!p_ignore_empty_terrains || terrain >= 0) { - constraints.insert(TerrainConstraint(this, E_coords, terrain)); + constraints.insert(TerrainConstraint(tile_map_node, E_coords, terrain)); } } return constraints; } -HashMap<Vector2i, TileSet::TerrainsPattern> TileMap::terrain_fill_constraints(int p_layer, const Vector<Vector2i> &p_to_replace, int p_terrain_set, const RBSet<TerrainConstraint> &p_constraints) { +void TileMapLayer::set_tile_map(TileMap *p_tile_map) { + tile_map_node = p_tile_map; +} + +void TileMapLayer::set_layer_index_in_tile_map_node(int p_index) { + layer_index_in_tile_map_node = p_index; +} + +Rect2 TileMapLayer::get_rect(bool &r_changed) const { + // Compute the displayed area of the tilemap. + r_changed = false; +#ifdef DEBUG_ENABLED + + if (rect_cache_dirty) { + Rect2 r_total; + bool first = true; + for (const KeyValue<Vector2i, TileMapQuadrant> &E : quadrant_map) { + Rect2 r; + r.position = tile_map_node->map_to_local(E.key * get_effective_quadrant_size()); + r.expand_to(tile_map_node->map_to_local((E.key + Vector2i(1, 0)) * get_effective_quadrant_size())); + r.expand_to(tile_map_node->map_to_local((E.key + Vector2i(1, 1)) * get_effective_quadrant_size())); + r.expand_to(tile_map_node->map_to_local((E.key + Vector2i(0, 1)) * get_effective_quadrant_size())); + if (first) { + r_total = r; + first = false; + } else { + r_total = r_total.merge(r); + } + } + + r_changed = rect_cache != r_total; + + rect_cache = r_total; + rect_cache_dirty = false; + } +#endif + return rect_cache; +} + +HashMap<Vector2i, TileSet::TerrainsPattern> TileMapLayer::terrain_fill_constraints(const Vector<Vector2i> &p_to_replace, int p_terrain_set, const RBSet<TerrainConstraint> &p_constraints) { + const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); if (!tile_set.is_valid()) { return HashMap<Vector2i, TileSet::TerrainsPattern>(); } @@ -2540,9 +1161,9 @@ HashMap<Vector2i, TileSet::TerrainsPattern> TileMap::terrain_fill_constraints(in for (int i = 0; i < p_to_replace.size(); i++) { const Vector2i &coords = p_to_replace[i]; - // Select the best pattern for the given constraints + // Select the best pattern for the given constraints. TileSet::TerrainsPattern current_pattern = TileSet::TerrainsPattern(*tile_set, p_terrain_set); - TileMapCell cell = get_cell(p_layer, coords); + TileMapCell cell = get_cell(coords); if (cell.source_id != TileSet::INVALID_SOURCE) { TileSetSource *source = *tile_set->get_source(cell.source_id); TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); @@ -2556,7 +1177,7 @@ HashMap<Vector2i, TileSet::TerrainsPattern> TileMap::terrain_fill_constraints(in } TileSet::TerrainsPattern pattern = _get_best_terrain_pattern_for_constraints(p_terrain_set, coords, constraints, current_pattern); - // Update the constraint set with the new ones + // Update the constraint set with the new ones. RBSet<TerrainConstraint> new_constraints = _get_terrain_constraints_from_added_pattern(coords, p_terrain_set, pattern); for (const TerrainConstraint &E_constraint : new_constraints) { if (constraints.has(E_constraint)) { @@ -2572,12 +1193,13 @@ HashMap<Vector2i, TileSet::TerrainsPattern> TileMap::terrain_fill_constraints(in return output; } -HashMap<Vector2i, TileSet::TerrainsPattern> TileMap::terrain_fill_connect(int p_layer, const Vector<Vector2i> &p_coords_array, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains) { +HashMap<Vector2i, TileSet::TerrainsPattern> TileMapLayer::terrain_fill_connect(const Vector<Vector2i> &p_coords_array, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains) { HashMap<Vector2i, TileSet::TerrainsPattern> output; + const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); ERR_FAIL_COND_V(!tile_set.is_valid(), output); ERR_FAIL_INDEX_V(p_terrain_set, tile_set->get_terrain_sets_count(), output); - // Build list and set of tiles that can be modified (painted and their surroundings) + // Build list and set of tiles that can be modified (painted and their surroundings). Vector<Vector2i> can_modify_list; RBSet<Vector2i> can_modify_set; RBSet<Vector2i> painted_set; @@ -2588,11 +1210,11 @@ HashMap<Vector2i, TileSet::TerrainsPattern> TileMap::terrain_fill_connect(int p_ painted_set.insert(coords); } for (Vector2i coords : p_coords_array) { - // Find the adequate neighbor + // Find the adequate neighbor. for (int j = 0; j < TileSet::CELL_NEIGHBOR_MAX; j++) { TileSet::CellNeighbor bit = TileSet::CellNeighbor(j); - if (is_existing_neighbor(bit)) { - Vector2i neighbor = get_neighbor_cell(coords, bit); + if (tile_map_node->is_existing_neighbor(bit)) { + Vector2i neighbor = tile_map_node->get_neighbor_cell(coords, bit); if (!can_modify_set.has(neighbor)) { can_modify_list.push_back(neighbor); can_modify_set.insert(neighbor); @@ -2601,16 +1223,16 @@ HashMap<Vector2i, TileSet::TerrainsPattern> TileMap::terrain_fill_connect(int p_ } } - // Build a set, out of the possibly modified tiles, of the one with a center bit that is set (or will be) to the painted terrain + // Build a set, out of the possibly modified tiles, of the one with a center bit that is set (or will be) to the painted terrain. RBSet<Vector2i> cells_with_terrain_center_bit; for (Vector2i coords : can_modify_set) { bool connect = false; if (painted_set.has(coords)) { connect = true; } else { - // Get the center bit of the cell + // Get the center bit of the cell. TileData *tile_data = nullptr; - TileMapCell cell = get_cell(p_layer, coords); + TileMapCell cell = get_cell(coords); if (cell.source_id != TileSet::INVALID_SOURCE) { Ref<TileSetSource> source = tile_set->get_source(cell.source_id); Ref<TileSetAtlasSource> atlas_source = source; @@ -2633,7 +1255,7 @@ HashMap<Vector2i, TileSet::TerrainsPattern> TileMap::terrain_fill_connect(int p_ // Add new constraints from the path drawn. for (Vector2i coords : p_coords_array) { // Constraints on the center bit. - TerrainConstraint c = TerrainConstraint(this, coords, p_terrain); + TerrainConstraint c = TerrainConstraint(tile_map_node, coords, p_terrain); c.set_priority(10); constraints.insert(c); @@ -2641,16 +1263,16 @@ HashMap<Vector2i, TileSet::TerrainsPattern> TileMap::terrain_fill_connect(int p_ for (int j = 0; j < TileSet::CELL_NEIGHBOR_MAX; j++) { TileSet::CellNeighbor bit = TileSet::CellNeighbor(j); if (tile_set->is_valid_terrain_peering_bit(p_terrain_set, bit)) { - c = TerrainConstraint(this, coords, bit, p_terrain); + c = TerrainConstraint(tile_map_node, coords, bit, p_terrain); c.set_priority(10); if ((int(bit) % 2) == 0) { - // Side peering bits: add the constraint if the center is of the same terrain - Vector2i neighbor = get_neighbor_cell(coords, bit); + // Side peering bits: add the constraint if the center is of the same terrain. + Vector2i neighbor = tile_map_node->get_neighbor_cell(coords, bit); if (cells_with_terrain_center_bit.has(neighbor)) { constraints.insert(c); } } else { - // Corner peering bits: add the constraint if all tiles on the constraint has the same center bit + // Corner peering bits: add the constraint if all tiles on the constraint has the same center bit. HashMap<Vector2i, TileSet::CellNeighbor> overlapping_terrain_bits = c.get_overlapping_coords_and_peering_bits(); bool valid = true; for (KeyValue<Vector2i, TileSet::CellNeighbor> kv : overlapping_terrain_bits) { @@ -2668,54 +1290,55 @@ HashMap<Vector2i, TileSet::TerrainsPattern> TileMap::terrain_fill_connect(int p_ } // Fills in the constraint list from existing tiles. - for (TerrainConstraint c : _get_terrain_constraints_from_painted_cells_list(p_layer, painted_set, p_terrain_set, p_ignore_empty_terrains)) { + for (TerrainConstraint c : _get_terrain_constraints_from_painted_cells_list(painted_set, p_terrain_set, p_ignore_empty_terrains)) { constraints.insert(c); } // Fill the terrains. - output = terrain_fill_constraints(p_layer, can_modify_list, p_terrain_set, constraints); + output = terrain_fill_constraints(can_modify_list, p_terrain_set, constraints); return output; } -HashMap<Vector2i, TileSet::TerrainsPattern> TileMap::terrain_fill_path(int p_layer, const Vector<Vector2i> &p_path, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains) { +HashMap<Vector2i, TileSet::TerrainsPattern> TileMapLayer::terrain_fill_path(const Vector<Vector2i> &p_coords_array, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains) { HashMap<Vector2i, TileSet::TerrainsPattern> output; + const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); ERR_FAIL_COND_V(!tile_set.is_valid(), output); ERR_FAIL_INDEX_V(p_terrain_set, tile_set->get_terrain_sets_count(), output); // Make sure the path is correct and build the peering bit list while doing it. Vector<TileSet::CellNeighbor> neighbor_list; - for (int i = 0; i < p_path.size() - 1; i++) { - // Find the adequate neighbor + for (int i = 0; i < p_coords_array.size() - 1; i++) { + // Find the adequate neighbor. TileSet::CellNeighbor found_bit = TileSet::CELL_NEIGHBOR_MAX; for (int j = 0; j < TileSet::CELL_NEIGHBOR_MAX; j++) { TileSet::CellNeighbor bit = TileSet::CellNeighbor(j); - if (is_existing_neighbor(bit)) { - if (get_neighbor_cell(p_path[i], bit) == p_path[i + 1]) { + if (tile_map_node->is_existing_neighbor(bit)) { + if (tile_map_node->get_neighbor_cell(p_coords_array[i], bit) == p_coords_array[i + 1]) { found_bit = bit; break; } } } - ERR_FAIL_COND_V_MSG(found_bit == TileSet::CELL_NEIGHBOR_MAX, output, vformat("Invalid terrain path, %s is not a neighboring tile of %s", p_path[i + 1], p_path[i])); + ERR_FAIL_COND_V_MSG(found_bit == TileSet::CELL_NEIGHBOR_MAX, output, vformat("Invalid terrain path, %s is not a neighboring tile of %s", p_coords_array[i + 1], p_coords_array[i])); neighbor_list.push_back(found_bit); } - // Build list and set of tiles that can be modified (painted and their surroundings) + // Build list and set of tiles that can be modified (painted and their surroundings). Vector<Vector2i> can_modify_list; RBSet<Vector2i> can_modify_set; RBSet<Vector2i> painted_set; - for (int i = p_path.size() - 1; i >= 0; i--) { - const Vector2i &coords = p_path[i]; + for (int i = p_coords_array.size() - 1; i >= 0; i--) { + const Vector2i &coords = p_coords_array[i]; can_modify_list.push_back(coords); can_modify_set.insert(coords); painted_set.insert(coords); } - for (Vector2i coords : p_path) { - // Find the adequate neighbor + for (Vector2i coords : p_coords_array) { + // Find the adequate neighbor. for (int j = 0; j < TileSet::CELL_NEIGHBOR_MAX; j++) { TileSet::CellNeighbor bit = TileSet::CellNeighbor(j); if (tile_set->is_valid_terrain_peering_bit(p_terrain_set, bit)) { - Vector2i neighbor = get_neighbor_cell(coords, bit); + Vector2i neighbor = tile_map_node->get_neighbor_cell(coords, bit); if (!can_modify_set.has(neighbor)) { can_modify_list.push_back(neighbor); can_modify_set.insert(neighbor); @@ -2727,31 +1350,32 @@ HashMap<Vector2i, TileSet::TerrainsPattern> TileMap::terrain_fill_path(int p_lay RBSet<TerrainConstraint> constraints; // Add new constraints from the path drawn. - for (Vector2i coords : p_path) { - // Constraints on the center bit - TerrainConstraint c = TerrainConstraint(this, coords, p_terrain); + for (Vector2i coords : p_coords_array) { + // Constraints on the center bit. + TerrainConstraint c = TerrainConstraint(tile_map_node, coords, p_terrain); c.set_priority(10); constraints.insert(c); } - for (int i = 0; i < p_path.size() - 1; i++) { + for (int i = 0; i < p_coords_array.size() - 1; i++) { // Constraints on the peering bits. - TerrainConstraint c = TerrainConstraint(this, p_path[i], neighbor_list[i], p_terrain); + TerrainConstraint c = TerrainConstraint(tile_map_node, p_coords_array[i], neighbor_list[i], p_terrain); c.set_priority(10); constraints.insert(c); } // Fills in the constraint list from existing tiles. - for (TerrainConstraint c : _get_terrain_constraints_from_painted_cells_list(p_layer, painted_set, p_terrain_set, p_ignore_empty_terrains)) { + for (TerrainConstraint c : _get_terrain_constraints_from_painted_cells_list(painted_set, p_terrain_set, p_ignore_empty_terrains)) { constraints.insert(c); } // Fill the terrains. - output = terrain_fill_constraints(p_layer, can_modify_list, p_terrain_set, constraints); + output = terrain_fill_constraints(can_modify_list, p_terrain_set, constraints); return output; } -HashMap<Vector2i, TileSet::TerrainsPattern> TileMap::terrain_fill_pattern(int p_layer, const Vector<Vector2i> &p_coords_array, int p_terrain_set, TileSet::TerrainsPattern p_terrains_pattern, bool p_ignore_empty_terrains) { +HashMap<Vector2i, TileSet::TerrainsPattern> TileMapLayer::terrain_fill_pattern(const Vector<Vector2i> &p_coords_array, int p_terrain_set, TileSet::TerrainsPattern p_terrains_pattern, bool p_ignore_empty_terrains) { HashMap<Vector2i, TileSet::TerrainsPattern> output; + const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); ERR_FAIL_COND_V(!tile_set.is_valid(), output); ERR_FAIL_INDEX_V(p_terrain_set, tile_set->get_terrain_sets_count(), output); @@ -2766,11 +1390,11 @@ HashMap<Vector2i, TileSet::TerrainsPattern> TileMap::terrain_fill_pattern(int p_ painted_set.insert(coords); } for (Vector2i coords : p_coords_array) { - // Find the adequate neighbor + // Find the adequate neighbor. for (int j = 0; j < TileSet::CELL_NEIGHBOR_MAX; j++) { TileSet::CellNeighbor bit = TileSet::CellNeighbor(j); if (tile_set->is_valid_terrain_peering_bit(p_terrain_set, bit)) { - Vector2i neighbor = get_neighbor_cell(coords, bit); + Vector2i neighbor = tile_map_node->get_neighbor_cell(coords, bit); if (!can_modify_set.has(neighbor)) { can_modify_list.push_back(neighbor); can_modify_set.insert(neighbor); @@ -2784,7 +1408,7 @@ HashMap<Vector2i, TileSet::TerrainsPattern> TileMap::terrain_fill_pattern(int p_ // Add new constraints from the path drawn. for (Vector2i coords : p_coords_array) { - // Constraints on the center bit + // Constraints on the center bit. RBSet<TerrainConstraint> added_constraints = _get_terrain_constraints_from_added_pattern(coords, p_terrain_set, p_terrains_pattern); for (TerrainConstraint c : added_constraints) { c.set_priority(10); @@ -2793,18 +1417,626 @@ HashMap<Vector2i, TileSet::TerrainsPattern> TileMap::terrain_fill_pattern(int p_ } // Fills in the constraint list from modified tiles border. - for (TerrainConstraint c : _get_terrain_constraints_from_painted_cells_list(p_layer, painted_set, p_terrain_set, p_ignore_empty_terrains)) { + for (TerrainConstraint c : _get_terrain_constraints_from_painted_cells_list(painted_set, p_terrain_set, p_ignore_empty_terrains)) { constraints.insert(c); } // Fill the terrains. - output = terrain_fill_constraints(p_layer, can_modify_list, p_terrain_set, constraints); + output = terrain_fill_constraints(can_modify_list, p_terrain_set, constraints); return output; } -void TileMap::set_cells_terrain_connect(int p_layer, TypedArray<Vector2i> p_cells, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains) { +TileMapCell TileMapLayer::get_cell(const Vector2i &p_coords, bool p_use_proxies) const { + if (!tile_map.has(p_coords)) { + return TileMapCell(); + } else { + TileMapCell c = tile_map.find(p_coords)->value; + const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); + if (p_use_proxies && tile_set.is_valid()) { + Array proxyed = tile_set->map_tile_proxy(c.source_id, c.get_atlas_coords(), c.alternative_tile); + c.source_id = proxyed[0]; + c.set_atlas_coords(proxyed[1]); + c.alternative_tile = proxyed[2]; + } + return c; + } +} + +int TileMapLayer::get_effective_quadrant_size() const { + // When using YSort, the quadrant size is reduced to 1 to have one CanvasItem per quadrant. + if (tile_map_node->is_y_sort_enabled() && is_y_sort_enabled()) { + return 1; + } else { + return tile_map_node->get_quadrant_size(); + } +} + +void TileMapLayer::set_tile_data(TileMapLayer::DataFormat p_format, const Vector<int> &p_data) { + ERR_FAIL_COND(p_format > TileMapLayer::FORMAT_3); + + // Set data for a given tile from raw data. + + int c = p_data.size(); + const int *r = p_data.ptr(); + + int offset = (p_format >= TileMapLayer::FORMAT_2) ? 3 : 2; + ERR_FAIL_COND_MSG(c % offset != 0, vformat("Corrupted tile data. Got size: %s. Expected modulo: %s", offset)); + + clear(); + +#ifdef DISABLE_DEPRECATED + ERR_FAIL_COND_MSG(p_format != TileMapLayer::FORMAT_3, vformat("Cannot handle deprecated TileMap data format version %d. This Godot version was compiled with no support for deprecated data.", p_format)); +#endif + + for (int i = 0; i < c; i += offset) { + const uint8_t *ptr = (const uint8_t *)&r[i]; + uint8_t local[12]; + for (int j = 0; j < ((p_format >= TileMapLayer::FORMAT_2) ? 12 : 8); j++) { + local[j] = ptr[j]; + } + +#ifdef BIG_ENDIAN_ENABLED + + SWAP(local[0], local[3]); + SWAP(local[1], local[2]); + SWAP(local[4], local[7]); + SWAP(local[5], local[6]); + //TODO: ask someone to check this... + if (FORMAT >= FORMAT_2) { + SWAP(local[8], local[11]); + SWAP(local[9], local[10]); + } +#endif + // Extracts position in TileMap. + int16_t x = decode_uint16(&local[0]); + int16_t y = decode_uint16(&local[2]); + + if (p_format == TileMapLayer::FORMAT_3) { + uint16_t source_id = decode_uint16(&local[4]); + uint16_t atlas_coords_x = decode_uint16(&local[6]); + uint16_t atlas_coords_y = decode_uint16(&local[8]); + uint16_t alternative_tile = decode_uint16(&local[10]); + set_cell(Vector2i(x, y), source_id, Vector2i(atlas_coords_x, atlas_coords_y), alternative_tile); + } else { +#ifndef DISABLE_DEPRECATED + // Previous decated format. + + uint32_t v = decode_uint32(&local[4]); + // Extract the transform flags that used to be in the tilemap. + bool flip_h = v & (1UL << 29); + bool flip_v = v & (1UL << 30); + bool transpose = v & (1UL << 31); + v &= (1UL << 29) - 1; + + // Extract autotile/atlas coords. + int16_t coord_x = 0; + int16_t coord_y = 0; + if (p_format == TileMapLayer::FORMAT_2) { + coord_x = decode_uint16(&local[8]); + coord_y = decode_uint16(&local[10]); + } + + const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); + if (tile_set.is_valid()) { + Array a = tile_set->compatibility_tilemap_map(v, Vector2i(coord_x, coord_y), flip_h, flip_v, transpose); + if (a.size() == 3) { + set_cell(Vector2i(x, y), a[0], a[1], a[2]); + } else { + ERR_PRINT(vformat("No valid tile in Tileset for: tile:%s coords:%s flip_h:%s flip_v:%s transpose:%s", v, Vector2i(coord_x, coord_y), flip_h, flip_v, transpose)); + } + } else { + int compatibility_alternative_tile = ((int)flip_h) + ((int)flip_v << 1) + ((int)transpose << 2); + set_cell(Vector2i(x, y), v, Vector2i(coord_x, coord_y), compatibility_alternative_tile); + } +#endif + } + } +} + +Vector<int> TileMapLayer::get_tile_data() const { + // Export tile data to raw format. + Vector<int> tile_data; + tile_data.resize(tile_map.size() * 3); + int *w = tile_data.ptrw(); + + // Save in highest format. + + int idx = 0; + for (const KeyValue<Vector2i, TileMapCell> &E : tile_map) { + uint8_t *ptr = (uint8_t *)&w[idx]; + encode_uint16((int16_t)(E.key.x), &ptr[0]); + encode_uint16((int16_t)(E.key.y), &ptr[2]); + encode_uint16(E.value.source_id, &ptr[4]); + encode_uint16(E.value.coord_x, &ptr[6]); + encode_uint16(E.value.coord_y, &ptr[8]); + encode_uint16(E.value.alternative_tile, &ptr[10]); + idx += 3; + } + + return tile_data; +} + +void TileMapLayer::clear_instantiated_scenes() { + instantiated_scenes.clear(); +} + +void TileMapLayer::clear_internals() { + // Clear quadrants. + while (quadrant_map.size()) { + _erase_quadrant(quadrant_map.begin()); + } + + // Clear the layers internals. + _rendering_cleanup(); + + // Clear the layers internal navigation maps. + _navigation_cleanup(); + + // Clear the dirty quadrants list. + while (dirty_quadrant_list.first()) { + dirty_quadrant_list.remove(dirty_quadrant_list.first()); + } +} + +void TileMapLayer::recreate_internals() { + // Make sure that _clear_internals() was called prior. + ERR_FAIL_COND_MSG(quadrant_map.size() > 0, "TileMap layer had a non-empty quadrant map."); + + if (!enabled) { + return; + } + + // Update the layer internals. + _rendering_update(); + + // Update the layer internal navigation maps. + _navigation_update(); + + // Recreate the quadrants. + for (const KeyValue<Vector2i, TileMapCell> &E : tile_map) { + Vector2i qk = _coords_to_quadrant_coords(Vector2i(E.key.x, E.key.y)); + + HashMap<Vector2i, TileMapQuadrant>::Iterator Q = quadrant_map.find(qk); + if (!Q) { + Q = _create_quadrant(qk); + dirty_quadrant_list.add(&Q->value.dirty_list_element); + } + + Vector2i pk = E.key; + Q->value.cells.insert(pk); + + _make_quadrant_dirty(Q); + } + + tile_map_node->queue_update_dirty_quadrants(); +} + +void TileMapLayer::notify_canvas_entered() { + // Rendering. + bool node_visible = tile_map_node->is_visible_in_tree(); + for (KeyValue<Vector2i, TileMapQuadrant> &E_quadrant : quadrant_map) { + TileMapQuadrant &q = E_quadrant.value; + for (const KeyValue<Vector2i, RID> &kv : q.occluders) { + Transform2D xform; + xform.set_origin(tile_map_node->map_to_local(kv.key)); + RS::get_singleton()->canvas_light_occluder_attach_to_canvas(kv.value, tile_map_node->get_canvas()); + RS::get_singleton()->canvas_light_occluder_set_transform(kv.value, tile_map_node->get_global_transform() * xform); + RS::get_singleton()->canvas_light_occluder_set_enabled(kv.value, node_visible); + } + } +} + +void TileMapLayer::notify_visibility_changed() { + bool node_visible = tile_map_node->is_visible_in_tree(); + for (KeyValue<Vector2i, TileMapQuadrant> &E_quadrant : quadrant_map) { + TileMapQuadrant &q = E_quadrant.value; + + // Update occluders transform. + for (const KeyValue<Vector2, Vector2i> &E_cell : q.local_to_map) { + Transform2D xform; + xform.set_origin(E_cell.key); + for (const KeyValue<Vector2i, RID> &kv : q.occluders) { + RS::get_singleton()->canvas_light_occluder_set_enabled(kv.value, node_visible); + } + } + } +} + +void TileMapLayer::notify_xform_changed() { + if (!tile_map_node->is_inside_tree()) { + return; + } + + bool in_editor = false; +#ifdef TOOLS_ENABLED + in_editor = Engine::get_singleton()->is_editor_hint(); +#endif + + Transform2D tilemap_xform = tile_map_node->get_global_transform(); + for (KeyValue<Vector2i, TileMapQuadrant> &E_quadrant : quadrant_map) { + TileMapQuadrant &q = E_quadrant.value; + + // Update occluders transform. + for (const KeyValue<Vector2i, RID> &kv : q.occluders) { + Transform2D xform; + xform.set_origin(tile_map_node->map_to_local(kv.key)); + RenderingServer::get_singleton()->canvas_light_occluder_set_transform(kv.value, tilemap_xform * xform); + } + + // Update navigation regions transform. + for (const KeyValue<Vector2i, Vector<RID>> &E_region : q.navigation_regions) { + for (const RID ®ion : E_region.value) { + if (!region.is_valid()) { + continue; + } + Transform2D tile_transform; + tile_transform.set_origin(tile_map_node->map_to_local(E_region.key)); + NavigationServer2D::get_singleton()->region_set_transform(region, tilemap_xform * tile_transform); + } + } + + // Physics. + if (!tile_map_node->is_collision_animatable() || in_editor) { + for (RID body : q.bodies) { + Transform2D xform; + xform.set_origin(tile_map_node->map_to_local(bodies_coords[body])); + xform = tilemap_xform * xform; + PhysicsServer2D::get_singleton()->body_set_state(body, PhysicsServer2D::BODY_STATE_TRANSFORM, xform); + } + } + } +} + +void TileMapLayer::notify_local_xform_changed() { + if (!tile_map_node->is_inside_tree()) { + return; + } + + bool in_editor = false; +#ifdef TOOLS_ENABLED + in_editor = Engine::get_singleton()->is_editor_hint(); +#endif + if (!tile_map_node->is_collision_animatable() || in_editor) { + Transform2D gl_transform = tile_map_node->get_global_transform(); + for (KeyValue<Vector2i, TileMapQuadrant> &E : quadrant_map) { + TileMapQuadrant &q = E.value; + + for (RID body : q.bodies) { + Transform2D xform; + xform.set_origin(tile_map_node->map_to_local(bodies_coords[body])); + xform = gl_transform * xform; + PhysicsServer2D::get_singleton()->body_set_state(body, PhysicsServer2D::BODY_STATE_TRANSFORM, xform); + } + } + } +} + +void TileMapLayer::notify_canvas_exited() { + for (KeyValue<Vector2i, TileMapQuadrant> &E_quadrant : quadrant_map) { + TileMapQuadrant &q = E_quadrant.value; + for (const KeyValue<Vector2i, RID> &kv : q.occluders) { + RS::get_singleton()->canvas_light_occluder_attach_to_canvas(kv.value, RID()); + } + } +} + +void TileMapLayer::notify_selected_layer_changed() { + _rendering_update(); +} + +void TileMapLayer::notify_light_mask_changed() { + for (const KeyValue<Vector2i, TileMapQuadrant> &E : quadrant_map) { + for (const RID &ci : E.value.canvas_items) { + RenderingServer::get_singleton()->canvas_item_set_light_mask(ci, tile_map_node->get_light_mask()); + } + } + _rendering_update(); +} + +void TileMapLayer::notify_material_changed() { + for (KeyValue<Vector2i, TileMapQuadrant> &E : quadrant_map) { + TileMapQuadrant &q = E.value; + for (const RID &ci : q.canvas_items) { + RS::get_singleton()->canvas_item_set_use_parent_material(ci, tile_map_node->get_use_parent_material() || tile_map_node->get_material().is_valid()); + } + } + _rendering_update(); +} + +void TileMapLayer::notify_use_parent_material_changed() { + notify_material_changed(); +} + +void TileMapLayer::notify_texture_filter_changed() { + for (HashMap<Vector2i, TileMapQuadrant>::Iterator F = quadrant_map.begin(); F; ++F) { + TileMapQuadrant &q = F->value; + for (const RID &ci : q.canvas_items) { + RenderingServer::get_singleton()->canvas_item_set_default_texture_filter(ci, RS::CanvasItemTextureFilter(tile_map_node->get_texture_filter_in_tree())); + _make_quadrant_dirty(F); + } + } + _rendering_update(); +} + +void TileMapLayer::notify_texture_repeat_changed() { + for (HashMap<Vector2i, TileMapQuadrant>::Iterator F = quadrant_map.begin(); F; ++F) { + TileMapQuadrant &q = F->value; + for (const RID &ci : q.canvas_items) { + RenderingServer::get_singleton()->canvas_item_set_default_texture_repeat(ci, RS::CanvasItemTextureRepeat(tile_map_node->get_texture_repeat_in_tree())); + _make_quadrant_dirty(F); + } + } + _rendering_update(); +} + +void TileMapLayer::update_dirty_quadrants() { + // Update the coords cache. + for (SelfList<TileMapQuadrant> *q = dirty_quadrant_list.first(); q; q = q->next()) { + q->self()->map_to_local.clear(); + q->self()->local_to_map.clear(); + for (const Vector2i &E : q->self()->cells) { + Vector2i pk = E; + Vector2 pk_local_coords = tile_map_node->map_to_local(pk); + q->self()->map_to_local[pk] = pk_local_coords; + q->self()->local_to_map[pk_local_coords] = pk; + } + } + + // Find TileData that need a runtime modification. + _build_runtime_update_tile_data(dirty_quadrant_list); + + // Call the update_dirty_quadrant method on plugins. + _rendering_update_dirty_quadrants(dirty_quadrant_list); + _physics_update_dirty_quadrants(dirty_quadrant_list); + _navigation_update_dirty_quadrants(dirty_quadrant_list); + _scenes_update_dirty_quadrants(dirty_quadrant_list); + + // Redraw the debug canvas_items. + RenderingServer *rs = RenderingServer::get_singleton(); + for (SelfList<TileMapQuadrant> *q = dirty_quadrant_list.first(); q; q = q->next()) { + rs->canvas_item_clear(q->self()->debug_canvas_item); + Transform2D xform; + xform.set_origin(tile_map_node->map_to_local(q->self()->coords * get_effective_quadrant_size())); + rs->canvas_item_set_transform(q->self()->debug_canvas_item, xform); + + _rendering_draw_quadrant_debug(q->self()); + _physics_draw_quadrant_debug(q->self()); + _navigation_draw_quadrant_debug(q->self()); + _scenes_draw_quadrant_debug(q->self()); + } + + // Clear the list. + while (dirty_quadrant_list.first()) { + // Clear the runtime tile data. + for (const KeyValue<Vector2i, TileData *> &kv : dirty_quadrant_list.first()->self()->runtime_tile_data_cache) { + memdelete(kv.value); + } + + dirty_quadrant_list.remove(dirty_quadrant_list.first()); + } +} + +void TileMapLayer::set_cell(const Vector2i &p_coords, int p_source_id, const Vector2i p_atlas_coords, int p_alternative_tile) { + // Set the current cell tile (using integer position). + Vector2i pk(p_coords); + HashMap<Vector2i, TileMapCell>::Iterator E = tile_map.find(pk); + + int source_id = p_source_id; + Vector2i atlas_coords = p_atlas_coords; + int alternative_tile = p_alternative_tile; + + if ((source_id == TileSet::INVALID_SOURCE || atlas_coords == TileSetSource::INVALID_ATLAS_COORDS || alternative_tile == TileSetSource::INVALID_TILE_ALTERNATIVE) && + (source_id != TileSet::INVALID_SOURCE || atlas_coords != TileSetSource::INVALID_ATLAS_COORDS || alternative_tile != TileSetSource::INVALID_TILE_ALTERNATIVE)) { + source_id = TileSet::INVALID_SOURCE; + atlas_coords = TileSetSource::INVALID_ATLAS_COORDS; + alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE; + } + + if (!E && source_id == TileSet::INVALID_SOURCE) { + return; // Nothing to do, the tile is already empty. + } + + // Get the quadrant + Vector2i qk = _coords_to_quadrant_coords(pk); + + HashMap<Vector2i, TileMapQuadrant>::Iterator Q = quadrant_map.find(qk); + + if (source_id == TileSet::INVALID_SOURCE) { + // Erase existing cell in the tile map. + tile_map.erase(pk); + + // Erase existing cell in the quadrant. + ERR_FAIL_COND(!Q); + TileMapQuadrant &q = Q->value; + + q.cells.erase(pk); + + // Remove or make the quadrant dirty. + if (q.cells.size() == 0) { + _erase_quadrant(Q); + } else { + _make_quadrant_dirty(Q); + } + + used_rect_cache_dirty = true; + } else { + if (!E) { + // Insert a new cell in the tile map. + E = tile_map.insert(pk, TileMapCell()); + + // Create a new quadrant if needed, then insert the cell if needed. + if (!Q) { + Q = _create_quadrant(qk); + } + TileMapQuadrant &q = Q->value; + q.cells.insert(pk); + + } else { + ERR_FAIL_COND(!Q); // TileMapQuadrant should exist... + + if (E->value.source_id == source_id && E->value.get_atlas_coords() == atlas_coords && E->value.alternative_tile == alternative_tile) { + return; // Nothing changed. + } + } + + TileMapCell &c = E->value; + + c.source_id = source_id; + c.set_atlas_coords(atlas_coords); + c.alternative_tile = alternative_tile; + + _make_quadrant_dirty(Q); + used_rect_cache_dirty = true; + } +} + +void TileMapLayer::erase_cell(const Vector2i &p_coords) { + set_cell(p_coords, TileSet::INVALID_SOURCE, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE); +} + +int TileMapLayer::get_cell_source_id(const Vector2i &p_coords, bool p_use_proxies) const { + // Get a cell source id from position. + HashMap<Vector2i, TileMapCell>::ConstIterator E = tile_map.find(p_coords); + + if (!E) { + return TileSet::INVALID_SOURCE; + } + + const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); + if (p_use_proxies && tile_set.is_valid()) { + Array proxyed = tile_set->map_tile_proxy(E->value.source_id, E->value.get_atlas_coords(), E->value.alternative_tile); + return proxyed[0]; + } + + return E->value.source_id; +} + +Vector2i TileMapLayer::get_cell_atlas_coords(const Vector2i &p_coords, bool p_use_proxies) const { + // Get a cell source id from position. + HashMap<Vector2i, TileMapCell>::ConstIterator E = tile_map.find(p_coords); + + if (!E) { + return TileSetSource::INVALID_ATLAS_COORDS; + } + + const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); + if (p_use_proxies && tile_set.is_valid()) { + Array proxyed = tile_set->map_tile_proxy(E->value.source_id, E->value.get_atlas_coords(), E->value.alternative_tile); + return proxyed[1]; + } + + return E->value.get_atlas_coords(); +} + +int TileMapLayer::get_cell_alternative_tile(const Vector2i &p_coords, bool p_use_proxies) const { + // Get a cell source id from position. + HashMap<Vector2i, TileMapCell>::ConstIterator E = tile_map.find(p_coords); + + if (!E) { + return TileSetSource::INVALID_TILE_ALTERNATIVE; + } + + const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); + if (p_use_proxies && tile_set.is_valid()) { + Array proxyed = tile_set->map_tile_proxy(E->value.source_id, E->value.get_atlas_coords(), E->value.alternative_tile); + return proxyed[2]; + } + + return E->value.alternative_tile; +} + +TileData *TileMapLayer::get_cell_tile_data(const Vector2i &p_coords, bool p_use_proxies) const { + int source_id = get_cell_source_id(p_coords, p_use_proxies); + if (source_id == TileSet::INVALID_SOURCE) { + return nullptr; + } + + const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); + Ref<TileSetAtlasSource> source = tile_set->get_source(source_id); + if (source.is_valid()) { + return source->get_tile_data(get_cell_atlas_coords(p_coords, p_use_proxies), get_cell_alternative_tile(p_coords, p_use_proxies)); + } + + return nullptr; +} + +void TileMapLayer::clear() { + // Remove all tiles. + clear_internals(); + tile_map.clear(); + recreate_internals(); + used_rect_cache_dirty = true; +} + +Ref<TileMapPattern> TileMapLayer::get_pattern(TypedArray<Vector2i> p_coords_array) { + const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); + ERR_FAIL_COND_V(!tile_set.is_valid(), nullptr); + + Ref<TileMapPattern> output; + output.instantiate(); + if (p_coords_array.is_empty()) { + return output; + } + + Vector2i min = Vector2i(p_coords_array[0]); + for (int i = 1; i < p_coords_array.size(); i++) { + min = min.min(p_coords_array[i]); + } + + Vector<Vector2i> coords_in_pattern_array; + coords_in_pattern_array.resize(p_coords_array.size()); + Vector2i ensure_positive_offset; + for (int i = 0; i < p_coords_array.size(); i++) { + Vector2i coords = p_coords_array[i]; + Vector2i coords_in_pattern = coords - min; + if (tile_set->get_tile_shape() != TileSet::TILE_SHAPE_SQUARE) { + if (tile_set->get_tile_layout() == TileSet::TILE_LAYOUT_STACKED) { + if (tile_set->get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_HORIZONTAL && bool(min.y % 2) && bool(coords_in_pattern.y % 2)) { + coords_in_pattern.x -= 1; + if (coords_in_pattern.x < 0) { + ensure_positive_offset.x = 1; + } + } else if (tile_set->get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_VERTICAL && bool(min.x % 2) && bool(coords_in_pattern.x % 2)) { + coords_in_pattern.y -= 1; + if (coords_in_pattern.y < 0) { + ensure_positive_offset.y = 1; + } + } + } else if (tile_set->get_tile_layout() == TileSet::TILE_LAYOUT_STACKED_OFFSET) { + if (tile_set->get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_HORIZONTAL && bool(min.y % 2) && bool(coords_in_pattern.y % 2)) { + coords_in_pattern.x += 1; + } else if (tile_set->get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_VERTICAL && bool(min.x % 2) && bool(coords_in_pattern.x % 2)) { + coords_in_pattern.y += 1; + } + } + } + coords_in_pattern_array.write[i] = coords_in_pattern; + } + + for (int i = 0; i < coords_in_pattern_array.size(); i++) { + Vector2i coords = p_coords_array[i]; + Vector2i coords_in_pattern = coords_in_pattern_array[i]; + output->set_cell(coords_in_pattern + ensure_positive_offset, get_cell_source_id(coords), get_cell_atlas_coords(coords), get_cell_alternative_tile(coords)); + } + + return output; +} + +void TileMapLayer::set_pattern(const Vector2i &p_position, const Ref<TileMapPattern> p_pattern) { + const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); + ERR_FAIL_COND(tile_set.is_null()); + ERR_FAIL_COND(p_pattern.is_null()); + + TypedArray<Vector2i> used_cells = p_pattern->get_used_cells(); + for (int i = 0; i < used_cells.size(); i++) { + Vector2i coords = tile_map_node->map_pattern(p_position, used_cells[i], p_pattern); + set_cell(coords, p_pattern->get_cell_source_id(used_cells[i]), p_pattern->get_cell_atlas_coords(used_cells[i]), p_pattern->get_cell_alternative_tile(used_cells[i])); + } +} + +void TileMapLayer::set_cells_terrain_connect(TypedArray<Vector2i> p_cells, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains) { + const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); ERR_FAIL_COND(!tile_set.is_valid()); - ERR_FAIL_INDEX(p_layer, (int)layers.size()); ERR_FAIL_INDEX(p_terrain_set, tile_set->get_terrain_sets_count()); Vector<Vector2i> cells_vector; @@ -2813,16 +2045,16 @@ void TileMap::set_cells_terrain_connect(int p_layer, TypedArray<Vector2i> p_cell cells_vector.push_back(p_cells[i]); painted_set.insert(p_cells[i]); } - HashMap<Vector2i, TileSet::TerrainsPattern> terrain_fill_output = terrain_fill_connect(p_layer, cells_vector, p_terrain_set, p_terrain, p_ignore_empty_terrains); + HashMap<Vector2i, TileSet::TerrainsPattern> terrain_fill_output = terrain_fill_connect(cells_vector, p_terrain_set, p_terrain, p_ignore_empty_terrains); for (const KeyValue<Vector2i, TileSet::TerrainsPattern> &kv : terrain_fill_output) { if (painted_set.has(kv.key)) { // Paint a random tile with the correct terrain for the painted path. TileMapCell c = tile_set->get_random_tile_from_terrains_pattern(p_terrain_set, kv.value); - set_cell(p_layer, kv.key, c.source_id, c.get_atlas_coords(), c.alternative_tile); + set_cell(kv.key, c.source_id, c.get_atlas_coords(), c.alternative_tile); } else { // Avoids updating the painted path from the output if the new pattern is the same as before. TileSet::TerrainsPattern in_map_terrain_pattern = TileSet::TerrainsPattern(*tile_set, p_terrain_set); - TileMapCell cell = get_cell(p_layer, kv.key); + TileMapCell cell = get_cell(kv.key); if (cell.source_id != TileSet::INVALID_SOURCE) { TileSetSource *source = *tile_set->get_source(cell.source_id); TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); @@ -2836,15 +2068,15 @@ void TileMap::set_cells_terrain_connect(int p_layer, TypedArray<Vector2i> p_cell } if (in_map_terrain_pattern != kv.value) { TileMapCell c = tile_set->get_random_tile_from_terrains_pattern(p_terrain_set, kv.value); - set_cell(p_layer, kv.key, c.source_id, c.get_atlas_coords(), c.alternative_tile); + set_cell(kv.key, c.source_id, c.get_atlas_coords(), c.alternative_tile); } } } } -void TileMap::set_cells_terrain_path(int p_layer, TypedArray<Vector2i> p_path, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains) { +void TileMapLayer::set_cells_terrain_path(TypedArray<Vector2i> p_path, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains) { + const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); ERR_FAIL_COND(!tile_set.is_valid()); - ERR_FAIL_INDEX(p_layer, (int)layers.size()); ERR_FAIL_INDEX(p_terrain_set, tile_set->get_terrain_sets_count()); Vector<Vector2i> vector_path; @@ -2854,16 +2086,16 @@ void TileMap::set_cells_terrain_path(int p_layer, TypedArray<Vector2i> p_path, i painted_set.insert(p_path[i]); } - HashMap<Vector2i, TileSet::TerrainsPattern> terrain_fill_output = terrain_fill_path(p_layer, vector_path, p_terrain_set, p_terrain, p_ignore_empty_terrains); + HashMap<Vector2i, TileSet::TerrainsPattern> terrain_fill_output = terrain_fill_path(vector_path, p_terrain_set, p_terrain, p_ignore_empty_terrains); for (const KeyValue<Vector2i, TileSet::TerrainsPattern> &kv : terrain_fill_output) { if (painted_set.has(kv.key)) { // Paint a random tile with the correct terrain for the painted path. TileMapCell c = tile_set->get_random_tile_from_terrains_pattern(p_terrain_set, kv.value); - set_cell(p_layer, kv.key, c.source_id, c.get_atlas_coords(), c.alternative_tile); + set_cell(kv.key, c.source_id, c.get_atlas_coords(), c.alternative_tile); } else { // Avoids updating the painted path from the output if the new pattern is the same as before. TileSet::TerrainsPattern in_map_terrain_pattern = TileSet::TerrainsPattern(*tile_set, p_terrain_set); - TileMapCell cell = get_cell(p_layer, kv.key); + TileMapCell cell = get_cell(kv.key); if (cell.source_id != TileSet::INVALID_SOURCE) { TileSetSource *source = *tile_set->get_source(cell.source_id); TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); @@ -2877,246 +2109,1236 @@ void TileMap::set_cells_terrain_path(int p_layer, TypedArray<Vector2i> p_path, i } if (in_map_terrain_pattern != kv.value) { TileMapCell c = tile_set->get_random_tile_from_terrains_pattern(p_terrain_set, kv.value); - set_cell(p_layer, kv.key, c.source_id, c.get_atlas_coords(), c.alternative_tile); + set_cell(kv.key, c.source_id, c.get_atlas_coords(), c.alternative_tile); } } } } -TileMapCell TileMap::get_cell(int p_layer, const Vector2i &p_coords, bool p_use_proxies) const { - ERR_FAIL_INDEX_V(p_layer, (int)layers.size(), TileMapCell()); - const HashMap<Vector2i, TileMapCell> &tile_map = layers[p_layer].tile_map; - if (!tile_map.has(p_coords)) { - return TileMapCell(); - } else { - TileMapCell c = tile_map.find(p_coords)->value; - if (p_use_proxies && tile_set.is_valid()) { - Array proxyed = tile_set->map_tile_proxy(c.source_id, c.get_atlas_coords(), c.alternative_tile); - c.source_id = proxyed[0]; - c.set_atlas_coords(proxyed[1]); - c.alternative_tile = proxyed[2]; - } - return c; +TypedArray<Vector2i> TileMapLayer::get_used_cells() const { + // Returns the cells used in the tilemap. + TypedArray<Vector2i> a; + a.resize(tile_map.size()); + int i = 0; + for (const KeyValue<Vector2i, TileMapCell> &E : tile_map) { + Vector2i p(E.key.x, E.key.y); + a[i++] = p; } -} -HashMap<Vector2i, TileMapQuadrant> *TileMap::get_quadrant_map(int p_layer) { - ERR_FAIL_INDEX_V(p_layer, (int)layers.size(), nullptr); - - return &layers[p_layer].quadrant_map; + return a; } -Vector2i TileMap::get_coords_for_body_rid(RID p_physics_body) { - ERR_FAIL_COND_V_MSG(!bodies_coords.has(p_physics_body), Vector2i(), vformat("No tiles for the given body RID %d.", p_physics_body)); - return bodies_coords[p_physics_body]; -} +TypedArray<Vector2i> TileMapLayer::get_used_cells_by_id(int p_source_id, const Vector2i p_atlas_coords, int p_alternative_tile) const { + // Returns the cells used in the tilemap. + TypedArray<Vector2i> a; + for (const KeyValue<Vector2i, TileMapCell> &E : tile_map) { + if ((p_source_id == TileSet::INVALID_SOURCE || p_source_id == E.value.source_id) && + (p_atlas_coords == TileSetSource::INVALID_ATLAS_COORDS || p_atlas_coords == E.value.get_atlas_coords()) && + (p_alternative_tile == TileSetSource::INVALID_TILE_ALTERNATIVE || p_alternative_tile == E.value.alternative_tile)) { + a.push_back(E.key); + } + } -int TileMap::get_layer_for_body_rid(RID p_physics_body) { - ERR_FAIL_COND_V_MSG(!bodies_layers.has(p_physics_body), int(), vformat("No tiles for the given body RID %d.", p_physics_body)); - return bodies_layers[p_physics_body]; + return a; } -void TileMap::fix_invalid_tiles() { - ERR_FAIL_COND_MSG(tile_set.is_null(), "Cannot fix invalid tiles if Tileset is not open."); +Rect2i TileMapLayer::get_used_rect() const { + // Return the rect of the currently used area. + if (used_rect_cache_dirty) { + bool first = true; + used_rect_cache = Rect2i(); - for (unsigned int i = 0; i < layers.size(); i++) { - const HashMap<Vector2i, TileMapCell> &tile_map = layers[i].tile_map; - RBSet<Vector2i> coords; - for (const KeyValue<Vector2i, TileMapCell> &E : tile_map) { - TileSetSource *source = *tile_set->get_source(E.value.source_id); - if (!source || !source->has_tile(E.value.get_atlas_coords()) || !source->has_alternative_tile(E.value.get_atlas_coords(), E.value.alternative_tile)) { - coords.insert(E.key); + if (tile_map.size() > 0) { + if (first) { + used_rect_cache = Rect2i(tile_map.begin()->key.x, tile_map.begin()->key.y, 0, 0); + first = false; + } + + for (const KeyValue<Vector2i, TileMapCell> &E : tile_map) { + used_rect_cache.expand_to(Vector2i(E.key.x, E.key.y)); } } - for (const Vector2i &E : coords) { - set_cell(i, E, TileSet::INVALID_SOURCE, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE); + + if (!first) { // first is true if every layer is empty. + used_rect_cache.size += Vector2i(1, 1); // The cache expands to top-left coordinate, so we add one full tile. } + used_rect_cache_dirty = false; } + + return used_rect_cache; } -void TileMap::clear_layer(int p_layer) { - ERR_FAIL_INDEX(p_layer, (int)layers.size()); +void TileMapLayer::set_name(String p_name) { + if (name == p_name) { + return; + } + name = p_name; + tile_map_node->emit_signal(CoreStringNames::get_singleton()->changed); +} - // Remove all tiles. - _clear_layer_internals(p_layer); - layers[p_layer].tile_map.clear(); - _recreate_layer_internals(p_layer); - used_rect_cache_dirty = true; +String TileMapLayer::get_name() const { + return name; } -void TileMap::clear() { - // Remove all tiles. - _clear_internals(); - for (TileMapLayer &layer : layers) { - layer.tile_map.clear(); +void TileMapLayer::set_enabled(bool p_enabled) { + if (enabled == p_enabled) { + return; } - _recreate_internals(); - used_rect_cache_dirty = true; + enabled = p_enabled; + clear_internals(); + recreate_internals(); + tile_map_node->emit_signal(CoreStringNames::get_singleton()->changed); + + tile_map_node->update_configuration_warnings(); } -void TileMap::force_update(int p_layer) { - if (p_layer >= 0) { - ERR_FAIL_INDEX(p_layer, (int)layers.size()); - _clear_layer_internals(p_layer); - _recreate_layer_internals(p_layer); - } else { - _clear_internals(); - _recreate_internals(); +bool TileMapLayer::is_enabled() const { + return enabled; +} + +void TileMapLayer::set_modulate(Color p_modulate) { + if (modulate == p_modulate) { + return; } + modulate = p_modulate; + _rendering_update(); + tile_map_node->emit_signal(CoreStringNames::get_singleton()->changed); } -void TileMap::_set_tile_data(int p_layer, const Vector<int> &p_data) { - ERR_FAIL_INDEX(p_layer, (int)layers.size()); - ERR_FAIL_COND(format > FORMAT_3); +Color TileMapLayer::get_modulate() const { + return modulate; +} - // Set data for a given tile from raw data. +void TileMapLayer::set_y_sort_enabled(bool p_y_sort_enabled) { + if (y_sort_enabled == p_y_sort_enabled) { + return; + } + y_sort_enabled = p_y_sort_enabled; + clear_internals(); + recreate_internals(); + tile_map_node->emit_signal(CoreStringNames::get_singleton()->changed); - int c = p_data.size(); - const int *r = p_data.ptr(); + tile_map_node->update_configuration_warnings(); +} - int offset = (format >= FORMAT_2) ? 3 : 2; - ERR_FAIL_COND_MSG(c % offset != 0, vformat("Corrupted tile data. Got size: %s. Expected modulo: %s", offset)); +bool TileMapLayer::is_y_sort_enabled() const { + return y_sort_enabled; +} - clear_layer(p_layer); +void TileMapLayer::set_y_sort_origin(int p_y_sort_origin) { + if (y_sort_origin == p_y_sort_origin) { + return; + } + y_sort_origin = p_y_sort_origin; + clear_internals(); + recreate_internals(); + tile_map_node->emit_signal(CoreStringNames::get_singleton()->changed); +} -#ifdef DISABLE_DEPRECATED - ERR_FAIL_COND_MSG(format != FORMAT_3, vformat("Cannot handle deprecated TileMap data format version %d. This Godot version was compiled with no support for deprecated data.", format)); -#endif +int TileMapLayer::get_y_sort_origin() const { + return y_sort_origin; +} - for (int i = 0; i < c; i += offset) { - const uint8_t *ptr = (const uint8_t *)&r[i]; - uint8_t local[12]; - for (int j = 0; j < ((format >= FORMAT_2) ? 12 : 8); j++) { - local[j] = ptr[j]; +void TileMapLayer::set_z_index(int p_z_index) { + if (z_index == p_z_index) { + return; + } + z_index = p_z_index; + _rendering_update(); + tile_map_node->emit_signal(CoreStringNames::get_singleton()->changed); + + tile_map_node->update_configuration_warnings(); +} + +int TileMapLayer::get_z_index() const { + return z_index; +} + +void TileMapLayer::set_navigation_map(RID p_map) { + ERR_FAIL_COND_MSG(!tile_map_node->is_inside_tree(), "A TileMap navigation map can only be changed while inside the SceneTree."); + navigation_map = p_map; + uses_world_navigation_map = p_map == tile_map_node->get_world_2d()->get_navigation_map(); +} + +RID TileMapLayer::get_navigation_map() const { + if (navigation_map.is_valid()) { + return navigation_map; + } + return RID(); +} + +void TileMapLayer::force_update() { + clear_internals(); + recreate_internals(); +} + +void TileMapLayer::fix_invalid_tiles() { + Ref<TileSet> tileset = tile_map_node->get_tileset(); + ERR_FAIL_COND_MSG(tileset.is_null(), "Cannot call fix_invalid_tiles() on a TileMap without a valid TileSet."); + + RBSet<Vector2i> coords; + for (const KeyValue<Vector2i, TileMapCell> &E : tile_map) { + TileSetSource *source = *tileset->get_source(E.value.source_id); + if (!source || !source->has_tile(E.value.get_atlas_coords()) || !source->has_alternative_tile(E.value.get_atlas_coords(), E.value.alternative_tile)) { + coords.insert(E.key); } + } + for (const Vector2i &E : coords) { + set_cell(E, TileSet::INVALID_SOURCE, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE); + } +} -#ifdef BIG_ENDIAN_ENABLED +bool TileMapLayer::has_body_rid(RID p_physics_body) const { + return bodies_coords.has(p_physics_body); +} - SWAP(local[0], local[3]); - SWAP(local[1], local[2]); - SWAP(local[4], local[7]); - SWAP(local[5], local[6]); - //TODO: ask someone to check this... - if (FORMAT >= FORMAT_2) { - SWAP(local[8], local[11]); - SWAP(local[9], local[10]); +Vector2i TileMapLayer::get_coords_for_body_rid(RID p_physics_body) const { + return bodies_coords[p_physics_body]; +} + +HashMap<Vector2i, TileSet::CellNeighbor> TerrainConstraint::get_overlapping_coords_and_peering_bits() const { + HashMap<Vector2i, TileSet::CellNeighbor> output; + + ERR_FAIL_COND_V(is_center_bit(), output); + + Ref<TileSet> ts = tile_map->get_tileset(); + ERR_FAIL_COND_V(!ts.is_valid(), output); + + TileSet::TileShape shape = ts->get_tile_shape(); + if (shape == TileSet::TILE_SHAPE_SQUARE) { + switch (bit) { + case 1: + output[base_cell_coords] = TileSet::CELL_NEIGHBOR_RIGHT_SIDE; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_LEFT_SIDE; + break; + case 2: + output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER; + break; + case 3: + output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_SIDE; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_SIDE; + break; + default: + ERR_FAIL_V(output); } -#endif - // Extracts position in TileMap. - int16_t x = decode_uint16(&local[0]); - int16_t y = decode_uint16(&local[2]); + } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC) { + switch (bit) { + case 1: + output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE; + break; + case 2: + output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_CORNER; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_LEFT_CORNER; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_CORNER)] = TileSet::CELL_NEIGHBOR_TOP_CORNER; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE)] = TileSet::CELL_NEIGHBOR_RIGHT_CORNER; + break; + case 3: + output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE; + break; + default: + ERR_FAIL_V(output); + } + } else { + // Half offset shapes. + TileSet::TileOffsetAxis offset_axis = ts->get_tile_offset_axis(); + if (offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { + switch (bit) { + case 1: + output[base_cell_coords] = TileSet::CELL_NEIGHBOR_RIGHT_SIDE; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_LEFT_SIDE; + break; + case 2: + output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_CORNER; + break; + case 3: + output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE; + break; + case 4: + output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_CORNER; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER; + break; + case 5: + output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE; + break; + default: + ERR_FAIL_V(output); + } + } else { + switch (bit) { + case 1: + output[base_cell_coords] = TileSet::CELL_NEIGHBOR_RIGHT_CORNER; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER; + break; + case 2: + output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE; + break; + case 3: + output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_LEFT_CORNER; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER; + break; + case 4: + output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_SIDE; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_SIDE; + break; + case 5: + output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE; + break; + default: + ERR_FAIL_V(output); + } + } + } + return output; +} - if (format == FORMAT_3) { - uint16_t source_id = decode_uint16(&local[4]); - uint16_t atlas_coords_x = decode_uint16(&local[6]); - uint16_t atlas_coords_y = decode_uint16(&local[8]); - uint16_t alternative_tile = decode_uint16(&local[10]); - set_cell(p_layer, Vector2i(x, y), source_id, Vector2i(atlas_coords_x, atlas_coords_y), alternative_tile); +TerrainConstraint::TerrainConstraint(const TileMap *p_tile_map, const Vector2i &p_position, int p_terrain) { + tile_map = p_tile_map; + + Ref<TileSet> ts = tile_map->get_tileset(); + ERR_FAIL_COND(!ts.is_valid()); + + bit = 0; + base_cell_coords = p_position; + terrain = p_terrain; +} + +TerrainConstraint::TerrainConstraint(const TileMap *p_tile_map, const Vector2i &p_position, const TileSet::CellNeighbor &p_bit, int p_terrain) { + // The way we build the constraint make it easy to detect conflicting constraints. + tile_map = p_tile_map; + + Ref<TileSet> ts = tile_map->get_tileset(); + ERR_FAIL_COND(!ts.is_valid()); + + TileSet::TileShape shape = ts->get_tile_shape(); + if (shape == TileSet::TILE_SHAPE_SQUARE) { + switch (p_bit) { + case TileSet::CELL_NEIGHBOR_RIGHT_SIDE: + bit = 1; + base_cell_coords = p_position; + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER: + bit = 2; + base_cell_coords = p_position; + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_SIDE: + bit = 3; + base_cell_coords = p_position; + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER: + bit = 2; + base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_LEFT_SIDE); + break; + case TileSet::CELL_NEIGHBOR_LEFT_SIDE: + bit = 1; + base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_LEFT_SIDE); + break; + case TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER: + bit = 2; + base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER); + break; + case TileSet::CELL_NEIGHBOR_TOP_SIDE: + bit = 3; + base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_SIDE); + break; + case TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER: + bit = 2; + base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_SIDE); + break; + default: + ERR_FAIL(); + break; + } + } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC) { + switch (p_bit) { + case TileSet::CELL_NEIGHBOR_RIGHT_CORNER: + bit = 2; + base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE); + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE: + bit = 1; + base_cell_coords = p_position; + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_CORNER: + bit = 2; + base_cell_coords = p_position; + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE: + bit = 3; + base_cell_coords = p_position; + break; + case TileSet::CELL_NEIGHBOR_LEFT_CORNER: + bit = 2; + base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE); + break; + case TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE: + bit = 1; + base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE); + break; + case TileSet::CELL_NEIGHBOR_TOP_CORNER: + bit = 2; + base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_CORNER); + break; + case TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE: + bit = 3; + base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE); + break; + default: + ERR_FAIL(); + break; + } + } else { + // Half-offset shapes. + TileSet::TileOffsetAxis offset_axis = ts->get_tile_offset_axis(); + if (offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { + switch (p_bit) { + case TileSet::CELL_NEIGHBOR_RIGHT_SIDE: + bit = 1; + base_cell_coords = p_position; + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER: + bit = 2; + base_cell_coords = p_position; + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE: + bit = 3; + base_cell_coords = p_position; + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_CORNER: + bit = 4; + base_cell_coords = p_position; + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE: + bit = 5; + base_cell_coords = p_position; + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER: + bit = 2; + base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_LEFT_SIDE); + break; + case TileSet::CELL_NEIGHBOR_LEFT_SIDE: + bit = 1; + base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_LEFT_SIDE); + break; + case TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER: + bit = 4; + base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE); + break; + case TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE: + bit = 3; + base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE); + break; + case TileSet::CELL_NEIGHBOR_TOP_CORNER: + bit = 2; + base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE); + break; + case TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE: + bit = 5; + base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE); + break; + case TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER: + bit = 4; + base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE); + break; + default: + ERR_FAIL(); + break; + } } else { -#ifndef DISABLE_DEPRECATED - // Previous decated format. + switch (p_bit) { + case TileSet::CELL_NEIGHBOR_RIGHT_CORNER: + bit = 1; + base_cell_coords = p_position; + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE: + bit = 2; + base_cell_coords = p_position; + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER: + bit = 3; + base_cell_coords = p_position; + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_SIDE: + bit = 4; + base_cell_coords = p_position; + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER: + bit = 1; + base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE); + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE: + bit = 5; + base_cell_coords = p_position; + break; + case TileSet::CELL_NEIGHBOR_LEFT_CORNER: + bit = 3; + base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE); + break; + case TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE: + bit = 2; + base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE); + break; + case TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER: + bit = 1; + base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE); + break; + case TileSet::CELL_NEIGHBOR_TOP_SIDE: + bit = 4; + base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_SIDE); + break; + case TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER: + bit = 3; + base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_SIDE); + break; + case TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE: + bit = 5; + base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE); + break; + default: + ERR_FAIL(); + break; + } + } + } + terrain = p_terrain; +} - uint32_t v = decode_uint32(&local[4]); - // Extract the transform flags that used to be in the tilemap. - bool flip_h = v & (1UL << 29); - bool flip_v = v & (1UL << 30); - bool transpose = v & (1UL << 31); - v &= (1UL << 29) - 1; +#define TILEMAP_CALL_FOR_LAYER(layer, function, ...) \ + if (layer < 0) { \ + layer = layers.size() + layer; \ + }; \ + ERR_FAIL_INDEX(layer, (int)layers.size()); \ + layers[layer]->function(__VA_ARGS__); - // Extract autotile/atlas coords. - int16_t coord_x = 0; - int16_t coord_y = 0; - if (format == FORMAT_2) { - coord_x = decode_uint16(&local[8]); - coord_y = decode_uint16(&local[10]); +#define TILEMAP_CALL_FOR_LAYER_V(layer, err_value, function, ...) \ + if (layer < 0) { \ + layer = layers.size() + layer; \ + }; \ + ERR_FAIL_INDEX_V(layer, (int)layers.size(), err_value); \ + return layers[layer]->function(__VA_ARGS__); + +Vector2i TileMap::transform_coords_layout(const Vector2i &p_coords, TileSet::TileOffsetAxis p_offset_axis, TileSet::TileLayout p_from_layout, TileSet::TileLayout p_to_layout) { + // Transform to stacked layout. + Vector2i output = p_coords; + if (p_offset_axis == TileSet::TILE_OFFSET_AXIS_VERTICAL) { + SWAP(output.x, output.y); + } + switch (p_from_layout) { + case TileSet::TILE_LAYOUT_STACKED: + break; + case TileSet::TILE_LAYOUT_STACKED_OFFSET: + if (output.y % 2) { + output.x -= 1; + } + break; + case TileSet::TILE_LAYOUT_STAIRS_RIGHT: + case TileSet::TILE_LAYOUT_STAIRS_DOWN: + if ((p_from_layout == TileSet::TILE_LAYOUT_STAIRS_RIGHT) ^ (p_offset_axis == TileSet::TILE_OFFSET_AXIS_VERTICAL)) { + if (output.y < 0 && bool(output.y % 2)) { + output = Vector2i(output.x + output.y / 2 - 1, output.y); + } else { + output = Vector2i(output.x + output.y / 2, output.y); + } + } else { + if (output.x < 0 && bool(output.x % 2)) { + output = Vector2i(output.x / 2 - 1, output.x + output.y * 2); + } else { + output = Vector2i(output.x / 2, output.x + output.y * 2); + } + } + break; + case TileSet::TILE_LAYOUT_DIAMOND_RIGHT: + case TileSet::TILE_LAYOUT_DIAMOND_DOWN: + if ((p_from_layout == TileSet::TILE_LAYOUT_DIAMOND_RIGHT) ^ (p_offset_axis == TileSet::TILE_OFFSET_AXIS_VERTICAL)) { + if ((output.x + output.y) < 0 && (output.x - output.y) % 2) { + output = Vector2i((output.x + output.y) / 2 - 1, output.y - output.x); + } else { + output = Vector2i((output.x + output.y) / 2, -output.x + output.y); + } + } else { + if ((output.x - output.y) < 0 && (output.x + output.y) % 2) { + output = Vector2i((output.x - output.y) / 2 - 1, output.x + output.y); + } else { + output = Vector2i((output.x - output.y) / 2, output.x + output.y); + } } + break; + } - if (tile_set.is_valid()) { - Array a = tile_set->compatibility_tilemap_map(v, Vector2i(coord_x, coord_y), flip_h, flip_v, transpose); - if (a.size() == 3) { - set_cell(p_layer, Vector2i(x, y), a[0], a[1], a[2]); + switch (p_to_layout) { + case TileSet::TILE_LAYOUT_STACKED: + break; + case TileSet::TILE_LAYOUT_STACKED_OFFSET: + if (output.y % 2) { + output.x += 1; + } + break; + case TileSet::TILE_LAYOUT_STAIRS_RIGHT: + case TileSet::TILE_LAYOUT_STAIRS_DOWN: + if ((p_to_layout == TileSet::TILE_LAYOUT_STAIRS_RIGHT) ^ (p_offset_axis == TileSet::TILE_OFFSET_AXIS_VERTICAL)) { + if (output.y < 0 && (output.y % 2)) { + output = Vector2i(output.x - output.y / 2 + 1, output.y); } else { - ERR_PRINT(vformat("No valid tile in Tileset for: tile:%s coords:%s flip_h:%s flip_v:%s transpose:%s", v, Vector2i(coord_x, coord_y), flip_h, flip_v, transpose)); + output = Vector2i(output.x - output.y / 2, output.y); } } else { - int compatibility_alternative_tile = ((int)flip_h) + ((int)flip_v << 1) + ((int)transpose << 2); - set_cell(p_layer, Vector2i(x, y), v, Vector2i(coord_x, coord_y), compatibility_alternative_tile); + if (output.y % 2) { + if (output.y < 0) { + output = Vector2i(2 * output.x + 1, -output.x + output.y / 2 - 1); + } else { + output = Vector2i(2 * output.x + 1, -output.x + output.y / 2); + } + } else { + output = Vector2i(2 * output.x, -output.x + output.y / 2); + } } + break; + case TileSet::TILE_LAYOUT_DIAMOND_RIGHT: + case TileSet::TILE_LAYOUT_DIAMOND_DOWN: + if ((p_to_layout == TileSet::TILE_LAYOUT_DIAMOND_RIGHT) ^ (p_offset_axis == TileSet::TILE_OFFSET_AXIS_VERTICAL)) { + if (output.y % 2) { + if (output.y > 0) { + output = Vector2i(output.x - output.y / 2, output.x + output.y / 2 + 1); + } else { + output = Vector2i(output.x - output.y / 2 + 1, output.x + output.y / 2); + } + } else { + output = Vector2i(output.x - output.y / 2, output.x + output.y / 2); + } + } else { + if (output.y % 2) { + if (output.y < 0) { + output = Vector2i(output.x + output.y / 2, -output.x + output.y / 2 - 1); + } else { + output = Vector2i(output.x + output.y / 2 + 1, -output.x + output.y / 2); + } + } else { + output = Vector2i(output.x + output.y / 2, -output.x + output.y / 2); + } + } + break; + } + + if (p_offset_axis == TileSet::TILE_OFFSET_AXIS_VERTICAL) { + SWAP(output.x, output.y); + } + + return output; +} + +void TileMap::set_selected_layer(int p_layer_id) { + ERR_FAIL_COND(p_layer_id < -1 || p_layer_id >= (int)layers.size()); + selected_layer = p_layer_id; + emit_signal(CoreStringNames::get_singleton()->changed); + + // Update the layers modulation. + for (Ref<TileMapLayer> &layer : layers) { + layer->notify_selected_layer_changed(); + } +} + +int TileMap::get_selected_layer() const { + return selected_layer; +} + +void TileMap::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + _clear_internals(); + _recreate_internals(); + } break; + + case NOTIFICATION_EXIT_TREE: { + _clear_internals(); + } break; + } + + // Transfers the notification to tileset plugins. + if (tile_set.is_valid()) { + switch (p_what) { + case TileMap::NOTIFICATION_ENTER_CANVAS: { + for (Ref<TileMapLayer> &layer : layers) { + layer->notify_canvas_entered(); + } + } break; + + case TileMap::NOTIFICATION_EXIT_CANVAS: { + for (Ref<TileMapLayer> &layer : layers) { + layer->notify_canvas_exited(); + } + } break; + + case NOTIFICATION_DRAW: { + // Rendering. + if (tile_set.is_valid()) { + RenderingServer::get_singleton()->canvas_item_set_sort_children_by_y(get_canvas_item(), is_y_sort_enabled()); + } + } break; + + case TileMap::NOTIFICATION_VISIBILITY_CHANGED: { + for (Ref<TileMapLayer> &layer : layers) { + layer->notify_visibility_changed(); + } + } break; + + case NOTIFICATION_TRANSFORM_CHANGED: { + // Physics. + for (Ref<TileMapLayer> &layer : layers) { + layer->notify_xform_changed(); + } + } break; + + case TileMap::NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { + // Physics. + bool in_editor = false; +#ifdef TOOLS_ENABLED + in_editor = Engine::get_singleton()->is_editor_hint(); #endif + if (is_inside_tree() && collision_animatable && !in_editor) { + // Update transform on the physics tick when in animatable mode. + last_valid_transform = new_transform; + set_notify_local_transform(false); + set_global_transform(new_transform); + set_notify_local_transform(true); + } + } break; + + case TileMap::NOTIFICATION_LOCAL_TRANSFORM_CHANGED: { + for (Ref<TileMapLayer> &layer : layers) { + layer->notify_local_xform_changed(); + } + + // Physics. + bool in_editor = false; +#ifdef TOOLS_ENABLED + in_editor = Engine::get_singleton()->is_editor_hint(); +#endif + + // Only active when animatable. Send the new transform to the physics... + if (is_inside_tree() && !in_editor && collision_animatable) { + // ... but then revert changes. + set_notify_local_transform(false); + set_global_transform(last_valid_transform); + set_notify_local_transform(true); + } + } break; } } - emit_signal(SNAME("changed")); } -Vector<int> TileMap::_get_tile_data(int p_layer) const { - ERR_FAIL_INDEX_V(p_layer, (int)layers.size(), Vector<int>()); +void TileMap::queue_update_dirty_quadrants() { + if (pending_update || !is_inside_tree()) { + return; + } + pending_update = true; + call_deferred(SNAME("_update_dirty_quadrants")); +} - // Export tile data to raw format - const HashMap<Vector2i, TileMapCell> &tile_map = layers[p_layer].tile_map; - Vector<int> tile_data; - tile_data.resize(tile_map.size() * 3); - int *w = tile_data.ptrw(); +void TileMap::_update_dirty_quadrants() { + if (!pending_update) { + return; + } - // Save in highest format + if (!is_inside_tree() || !tile_set.is_valid()) { + pending_update = false; + return; + } - int idx = 0; - for (const KeyValue<Vector2i, TileMapCell> &E : tile_map) { - uint8_t *ptr = (uint8_t *)&w[idx]; - encode_uint16((int16_t)(E.key.x), &ptr[0]); - encode_uint16((int16_t)(E.key.y), &ptr[2]); - encode_uint16(E.value.source_id, &ptr[4]); - encode_uint16(E.value.coord_x, &ptr[6]); - encode_uint16(E.value.coord_y, &ptr[8]); - encode_uint16(E.value.alternative_tile, &ptr[10]); - idx += 3; + // Physics: + Transform2D gl_transform = get_global_transform(); + last_valid_transform = gl_transform; + new_transform = gl_transform; + + // Update dirty quadrants on layers. + for (Ref<TileMapLayer> &layer : layers) { + layer->update_dirty_quadrants(); } - return tile_data; + pending_update = false; +} + +void TileMap::set_tileset(const Ref<TileSet> &p_tileset) { + if (p_tileset == tile_set) { + return; + } + + // Set the tileset, registering to its changes. + if (tile_set.is_valid()) { + tile_set->disconnect_changed(callable_mp(this, &TileMap::_tile_set_changed)); + } + + if (!p_tileset.is_valid()) { + _clear_internals(); + } + + tile_set = p_tileset; + + if (tile_set.is_valid()) { + tile_set->connect_changed(callable_mp(this, &TileMap::_tile_set_changed)); + _clear_internals(); + _recreate_internals(); + } + + emit_signal(CoreStringNames::get_singleton()->changed); } -void TileMap::_build_runtime_update_tile_data(SelfList<TileMapQuadrant>::List &r_dirty_quadrant_list) { - if (GDVIRTUAL_IS_OVERRIDDEN(_use_tile_data_runtime_update) && GDVIRTUAL_IS_OVERRIDDEN(_tile_data_runtime_update)) { - SelfList<TileMapQuadrant> *q_list_element = r_dirty_quadrant_list.first(); - while (q_list_element) { - TileMapQuadrant &q = *q_list_element->self(); - // Iterate over the cells of the quadrant. - for (const KeyValue<Vector2, Vector2i> &E_cell : q.local_to_map) { - TileMapCell c = get_cell(q.layer, E_cell.value, true); +Ref<TileSet> TileMap::get_tileset() const { + return tile_set; +} - TileSetSource *source; - if (tile_set->has_source(c.source_id)) { - source = *tile_set->get_source(c.source_id); +void TileMap::set_quadrant_size(int p_size) { + ERR_FAIL_COND_MSG(p_size < 1, "TileMapQuadrant size cannot be smaller than 1."); - if (!source->has_tile(c.get_atlas_coords()) || !source->has_alternative_tile(c.get_atlas_coords(), c.alternative_tile)) { - continue; - } + quadrant_size = p_size; + _clear_internals(); + _recreate_internals(); + emit_signal(CoreStringNames::get_singleton()->changed); +} - TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); - if (atlas_source) { - bool ret = false; - if (GDVIRTUAL_CALL(_use_tile_data_runtime_update, q.layer, E_cell.value, ret) && ret) { - TileData *tile_data = atlas_source->get_tile_data(c.get_atlas_coords(), c.alternative_tile); +int TileMap::get_quadrant_size() const { + return quadrant_size; +} - // Create the runtime TileData. - TileData *tile_data_runtime_use = tile_data->duplicate(); - tile_data_runtime_use->set_allow_transform(true); - q.runtime_tile_data_cache[E_cell.value] = tile_data_runtime_use; +void TileMap::draw_tile(RID p_canvas_item, const Vector2 &p_position, const Ref<TileSet> p_tile_set, int p_atlas_source_id, const Vector2i &p_atlas_coords, int p_alternative_tile, int p_frame, Color p_modulation, const TileData *p_tile_data_override, real_t p_animation_offset) { + ERR_FAIL_COND(!p_tile_set.is_valid()); + ERR_FAIL_COND(!p_tile_set->has_source(p_atlas_source_id)); + ERR_FAIL_COND(!p_tile_set->get_source(p_atlas_source_id)->has_tile(p_atlas_coords)); + ERR_FAIL_COND(!p_tile_set->get_source(p_atlas_source_id)->has_alternative_tile(p_atlas_coords, p_alternative_tile)); + TileSetSource *source = *p_tile_set->get_source(p_atlas_source_id); + TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); + if (atlas_source) { + // Check for the frame. + if (p_frame >= 0) { + ERR_FAIL_INDEX(p_frame, atlas_source->get_tile_animation_frames_count(p_atlas_coords)); + } - GDVIRTUAL_CALL(_tile_data_runtime_update, q.layer, E_cell.value, tile_data_runtime_use); - } - } - } + // Get the texture. + Ref<Texture2D> tex = atlas_source->get_runtime_texture(); + if (!tex.is_valid()) { + return; + } + + // Check if we are in the texture, return otherwise. + Vector2i grid_size = atlas_source->get_atlas_grid_size(); + if (p_atlas_coords.x >= grid_size.x || p_atlas_coords.y >= grid_size.y) { + return; + } + + // Get tile data. + const TileData *tile_data = p_tile_data_override ? p_tile_data_override : atlas_source->get_tile_data(p_atlas_coords, p_alternative_tile); + + // Get the tile modulation. + Color modulate = tile_data->get_modulate() * p_modulation; + + // Compute the offset. + Vector2 tile_offset = tile_data->get_texture_origin(); + + // Get destination rect. + Rect2 dest_rect; + dest_rect.size = atlas_source->get_runtime_tile_texture_region(p_atlas_coords).size; + dest_rect.size.x += FP_ADJUST; + dest_rect.size.y += FP_ADJUST; + + bool transpose = tile_data->get_transpose(); + if (transpose) { + dest_rect.position = (p_position - Vector2(dest_rect.size.y, dest_rect.size.x) / 2 - tile_offset); + } else { + dest_rect.position = (p_position - dest_rect.size / 2 - tile_offset); + } + + if (tile_data->get_flip_h()) { + dest_rect.size.x = -dest_rect.size.x; + } + + if (tile_data->get_flip_v()) { + dest_rect.size.y = -dest_rect.size.y; + } + + // Draw the tile. + if (p_frame >= 0) { + Rect2i source_rect = atlas_source->get_runtime_tile_texture_region(p_atlas_coords, p_frame); + tex->draw_rect_region(p_canvas_item, dest_rect, source_rect, modulate, transpose, p_tile_set->is_uv_clipping()); + } else if (atlas_source->get_tile_animation_frames_count(p_atlas_coords) == 1) { + Rect2i source_rect = atlas_source->get_runtime_tile_texture_region(p_atlas_coords, 0); + tex->draw_rect_region(p_canvas_item, dest_rect, source_rect, modulate, transpose, p_tile_set->is_uv_clipping()); + } else { + real_t speed = atlas_source->get_tile_animation_speed(p_atlas_coords); + real_t animation_duration = atlas_source->get_tile_animation_total_duration(p_atlas_coords) / speed; + real_t time = 0.0; + for (int frame = 0; frame < atlas_source->get_tile_animation_frames_count(p_atlas_coords); frame++) { + real_t frame_duration = atlas_source->get_tile_animation_frame_duration(p_atlas_coords, frame) / speed; + RenderingServer::get_singleton()->canvas_item_add_animation_slice(p_canvas_item, animation_duration, time, time + frame_duration, p_animation_offset); + + Rect2i source_rect = atlas_source->get_runtime_tile_texture_region(p_atlas_coords, frame); + tex->draw_rect_region(p_canvas_item, dest_rect, source_rect, modulate, transpose, p_tile_set->is_uv_clipping()); + + time += frame_duration; } - q_list_element = q_list_element->next(); + RenderingServer::get_singleton()->canvas_item_add_animation_slice(p_canvas_item, 1.0, 0.0, 1.0, 0.0); } } } +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() + p_to_pos + 1; + } + + ERR_FAIL_INDEX(p_to_pos, (int)layers.size() + 1); + + // Must clear before adding the layer. + _clear_internals(); + Ref<TileMapLayer> new_layer; + new_layer.instantiate(); + new_layer->set_tile_map(this); + layers.insert(p_to_pos, new_layer); + for (unsigned int i = 0; i < layers.size(); i++) { + layers[i]->set_layer_index_in_tile_map_node(i); + } + _recreate_internals(); + notify_property_list_changed(); + + emit_signal(CoreStringNames::get_singleton()->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); + + // Clear before shuffling layers. + _clear_internals(); + Ref<TileMapLayer> layer = layers[p_layer]; + layers.insert(p_to_pos, layer); + layers.remove_at(p_to_pos < p_layer ? p_layer + 1 : p_layer); + for (unsigned int i = 0; i < layers.size(); i++) { + layers[i]->set_layer_index_in_tile_map_node(i); + } + _recreate_internals(); + notify_property_list_changed(); + + if (selected_layer == p_layer) { + selected_layer = p_to_pos < p_layer ? p_to_pos - 1 : p_to_pos; + } + + emit_signal(CoreStringNames::get_singleton()->changed); + + update_configuration_warnings(); +} + +void TileMap::remove_layer(int p_layer) { + ERR_FAIL_INDEX(p_layer, (int)layers.size()); + + // Clear before removing the layer. + _clear_internals(); + layers.remove_at(p_layer); + for (unsigned int i = 0; i < layers.size(); i++) { + layers[i]->set_layer_index_in_tile_map_node(i); + } + _recreate_internals(); + notify_property_list_changed(); + + if (selected_layer >= p_layer) { + selected_layer -= 1; + } + + emit_signal(CoreStringNames::get_singleton()->changed); + + update_configuration_warnings(); +} + +void TileMap::set_layer_name(int p_layer, String p_name) { + TILEMAP_CALL_FOR_LAYER(p_layer, set_name, p_name); +} + +String TileMap::get_layer_name(int p_layer) const { + TILEMAP_CALL_FOR_LAYER_V(p_layer, "", get_name); +} + +void TileMap::set_layer_enabled(int p_layer, bool p_enabled) { + TILEMAP_CALL_FOR_LAYER(p_layer, set_enabled, p_enabled); +} + +bool TileMap::is_layer_enabled(int p_layer) const { + TILEMAP_CALL_FOR_LAYER_V(p_layer, false, is_enabled); +} + +void TileMap::set_layer_modulate(int p_layer, Color p_modulate) { + TILEMAP_CALL_FOR_LAYER(p_layer, set_modulate, p_modulate); +} + +Color TileMap::get_layer_modulate(int p_layer) const { + TILEMAP_CALL_FOR_LAYER_V(p_layer, Color(), get_modulate); +} + +void TileMap::set_layer_y_sort_enabled(int p_layer, bool p_y_sort_enabled) { + TILEMAP_CALL_FOR_LAYER(p_layer, set_y_sort_enabled, p_y_sort_enabled); +} + +bool TileMap::is_layer_y_sort_enabled(int p_layer) const { + TILEMAP_CALL_FOR_LAYER_V(p_layer, false, is_y_sort_enabled); +} + +void TileMap::set_layer_y_sort_origin(int p_layer, int p_y_sort_origin) { + TILEMAP_CALL_FOR_LAYER(p_layer, set_y_sort_origin, p_y_sort_origin); +} + +int TileMap::get_layer_y_sort_origin(int p_layer) const { + TILEMAP_CALL_FOR_LAYER_V(p_layer, 0, get_y_sort_origin); +} + +void TileMap::set_layer_z_index(int p_layer, int p_z_index) { + TILEMAP_CALL_FOR_LAYER(p_layer, set_z_index, p_z_index); +} + +int TileMap::get_layer_z_index(int p_layer) const { + TILEMAP_CALL_FOR_LAYER_V(p_layer, 0, get_z_index); +} + +void TileMap::set_layer_navigation_map(int p_layer, RID p_map) { + TILEMAP_CALL_FOR_LAYER(p_layer, set_navigation_map, p_map); +} + +RID TileMap::get_layer_navigation_map(int p_layer) const { + TILEMAP_CALL_FOR_LAYER_V(p_layer, RID(), get_navigation_map); +} + +void TileMap::set_collision_animatable(bool p_enabled) { + if (collision_animatable == p_enabled) { + return; + } + collision_animatable = p_enabled; + _clear_internals(); + set_notify_local_transform(p_enabled); + set_physics_process_internal(p_enabled); + _recreate_internals(); + emit_signal(CoreStringNames::get_singleton()->changed); +} + +bool TileMap::is_collision_animatable() const { + return collision_animatable; +} + +void TileMap::set_collision_visibility_mode(TileMap::VisibilityMode p_show_collision) { + if (collision_visibility_mode == p_show_collision) { + return; + } + collision_visibility_mode = p_show_collision; + _clear_internals(); + _recreate_internals(); + emit_signal(CoreStringNames::get_singleton()->changed); +} + +TileMap::VisibilityMode TileMap::get_collision_visibility_mode() { + return collision_visibility_mode; +} + +void TileMap::set_navigation_visibility_mode(TileMap::VisibilityMode p_show_navigation) { + if (navigation_visibility_mode == p_show_navigation) { + return; + } + navigation_visibility_mode = p_show_navigation; + _clear_internals(); + _recreate_internals(); + emit_signal(CoreStringNames::get_singleton()->changed); +} + +TileMap::VisibilityMode TileMap::get_navigation_visibility_mode() { + return navigation_visibility_mode; +} + +void TileMap::set_y_sort_enabled(bool p_enable) { + if (is_y_sort_enabled() == p_enable) { + return; + } + Node2D::set_y_sort_enabled(p_enable); + _clear_internals(); + _recreate_internals(); + emit_signal(CoreStringNames::get_singleton()->changed); + update_configuration_warnings(); +} + +void TileMap::_clear_internals() { + // Clear quadrants. + for (Ref<TileMapLayer> &layer : layers) { + layer->clear_internals(); + } +} + +void TileMap::_recreate_internals() { + for (Ref<TileMapLayer> &layer : layers) { + layer->recreate_internals(); + } +} + +/////////////////////////////// Rendering ////////////////////////////////////// + +void TileMap::set_cell(int p_layer, const Vector2i &p_coords, int p_source_id, const Vector2i p_atlas_coords, int p_alternative_tile) { + TILEMAP_CALL_FOR_LAYER(p_layer, set_cell, p_coords, p_source_id, p_atlas_coords, p_alternative_tile); +} + +void TileMap::erase_cell(int p_layer, const Vector2i &p_coords) { + TILEMAP_CALL_FOR_LAYER(p_layer, set_cell, p_coords, TileSet::INVALID_SOURCE, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE); +} + +int TileMap::get_cell_source_id(int p_layer, const Vector2i &p_coords, bool p_use_proxies) const { + TILEMAP_CALL_FOR_LAYER_V(p_layer, TileSet::INVALID_SOURCE, get_cell_source_id, p_coords, p_use_proxies); +} + +Vector2i TileMap::get_cell_atlas_coords(int p_layer, const Vector2i &p_coords, bool p_use_proxies) const { + TILEMAP_CALL_FOR_LAYER_V(p_layer, TileSetSource::INVALID_ATLAS_COORDS, get_cell_atlas_coords, p_coords, p_use_proxies); +} + +int TileMap::get_cell_alternative_tile(int p_layer, const Vector2i &p_coords, bool p_use_proxies) const { + TILEMAP_CALL_FOR_LAYER_V(p_layer, TileSetSource::INVALID_TILE_ALTERNATIVE, get_cell_alternative_tile, p_coords, p_use_proxies); +} + +TileData *TileMap::get_cell_tile_data(int p_layer, const Vector2i &p_coords, bool p_use_proxies) const { + TILEMAP_CALL_FOR_LAYER_V(p_layer, nullptr, get_cell_tile_data, p_coords, p_use_proxies); +} + +Ref<TileMapPattern> TileMap::get_pattern(int p_layer, TypedArray<Vector2i> p_coords_array) { + TILEMAP_CALL_FOR_LAYER_V(p_layer, Ref<TileMapPattern>(), get_pattern, p_coords_array); +} + +Vector2i TileMap::map_pattern(const Vector2i &p_position_in_tilemap, const Vector2i &p_coords_in_pattern, Ref<TileMapPattern> p_pattern) { + ERR_FAIL_COND_V(p_pattern.is_null(), Vector2i()); + ERR_FAIL_COND_V(!p_pattern->has_cell(p_coords_in_pattern), Vector2i()); + + Vector2i output = p_position_in_tilemap + p_coords_in_pattern; + if (tile_set->get_tile_shape() != TileSet::TILE_SHAPE_SQUARE) { + if (tile_set->get_tile_layout() == TileSet::TILE_LAYOUT_STACKED) { + if (tile_set->get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_HORIZONTAL && bool(p_position_in_tilemap.y % 2) && bool(p_coords_in_pattern.y % 2)) { + output.x += 1; + } else if (tile_set->get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_VERTICAL && bool(p_position_in_tilemap.x % 2) && bool(p_coords_in_pattern.x % 2)) { + output.y += 1; + } + } else if (tile_set->get_tile_layout() == TileSet::TILE_LAYOUT_STACKED_OFFSET) { + if (tile_set->get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_HORIZONTAL && bool(p_position_in_tilemap.y % 2) && bool(p_coords_in_pattern.y % 2)) { + output.x -= 1; + } else if (tile_set->get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_VERTICAL && bool(p_position_in_tilemap.x % 2) && bool(p_coords_in_pattern.x % 2)) { + output.y -= 1; + } + } + } + + return output; +} + +void TileMap::set_pattern(int p_layer, const Vector2i &p_position, const Ref<TileMapPattern> p_pattern) { + TILEMAP_CALL_FOR_LAYER(p_layer, set_pattern, p_position, p_pattern); +} + +HashMap<Vector2i, TileSet::TerrainsPattern> TileMap::terrain_fill_constraints(int p_layer, const Vector<Vector2i> &p_to_replace, int p_terrain_set, const RBSet<TerrainConstraint> &p_constraints) { + HashMap<Vector2i, TileSet::TerrainsPattern> err_value; + TILEMAP_CALL_FOR_LAYER_V(p_layer, err_value, terrain_fill_constraints, p_to_replace, p_terrain_set, p_constraints); +} + +HashMap<Vector2i, TileSet::TerrainsPattern> TileMap::terrain_fill_connect(int p_layer, const Vector<Vector2i> &p_coords_array, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains) { + HashMap<Vector2i, TileSet::TerrainsPattern> err_value; + TILEMAP_CALL_FOR_LAYER_V(p_layer, err_value, terrain_fill_connect, p_coords_array, p_terrain_set, p_terrain, p_ignore_empty_terrains); +} + +HashMap<Vector2i, TileSet::TerrainsPattern> TileMap::terrain_fill_path(int p_layer, const Vector<Vector2i> &p_coords_array, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains) { + HashMap<Vector2i, TileSet::TerrainsPattern> err_value; + TILEMAP_CALL_FOR_LAYER_V(p_layer, err_value, terrain_fill_path, p_coords_array, p_terrain_set, p_terrain, p_ignore_empty_terrains); +} + +HashMap<Vector2i, TileSet::TerrainsPattern> TileMap::terrain_fill_pattern(int p_layer, const Vector<Vector2i> &p_coords_array, int p_terrain_set, TileSet::TerrainsPattern p_terrains_pattern, bool p_ignore_empty_terrains) { + HashMap<Vector2i, TileSet::TerrainsPattern> err_value; + TILEMAP_CALL_FOR_LAYER_V(p_layer, err_value, terrain_fill_pattern, p_coords_array, p_terrain_set, p_terrains_pattern, p_ignore_empty_terrains); +} + +void TileMap::set_cells_terrain_connect(int p_layer, TypedArray<Vector2i> p_cells, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains) { + TILEMAP_CALL_FOR_LAYER(p_layer, set_cells_terrain_connect, p_cells, p_terrain_set, p_terrain, p_ignore_empty_terrains); +} + +void TileMap::set_cells_terrain_path(int p_layer, TypedArray<Vector2i> p_path, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains) { + TILEMAP_CALL_FOR_LAYER(p_layer, set_cells_terrain_path, p_path, p_terrain_set, p_terrain, p_ignore_empty_terrains); +} + +TileMapCell TileMap::get_cell(int p_layer, const Vector2i &p_coords, bool p_use_proxies) const { + TILEMAP_CALL_FOR_LAYER_V(p_layer, TileMapCell(), get_cell, p_coords, p_use_proxies); +} + +Vector2i TileMap::get_coords_for_body_rid(RID p_physics_body) { + for (const Ref<TileMapLayer> &layer : layers) { + if (layer->has_body_rid(p_physics_body)) { + return layer->get_coords_for_body_rid(p_physics_body); + } + } + ERR_FAIL_V_MSG(Vector2i(), vformat("No tiles for the given body RID %d.", p_physics_body)); +} + +int TileMap::get_layer_for_body_rid(RID p_physics_body) { + for (unsigned int i = 0; i < layers.size(); i++) { + if (layers[i]->has_body_rid(p_physics_body)) { + return i; + } + } + ERR_FAIL_V_MSG(-1, vformat("No tiles for the given body RID %d.", p_physics_body)); +} + +void TileMap::fix_invalid_tiles() { + for (Ref<TileMapLayer> &layer : layers) { + layer->fix_invalid_tiles(); + } +} + +void TileMap::clear_layer(int p_layer) { + TILEMAP_CALL_FOR_LAYER(p_layer, clear) +} + +void TileMap::clear() { + for (Ref<TileMapLayer> &layer : layers) { + layer->clear(); + } +} + +void TileMap::force_update(int p_layer) { + if (p_layer >= 0) { + TILEMAP_CALL_FOR_LAYER(p_layer, force_update); + } else { + _clear_internals(); + _recreate_internals(); + } +} + #ifdef TOOLS_ENABLED Rect2 TileMap::_edit_get_rect() const { - // Return the visible rect of the tilemap - const_cast<TileMap *>(this)->_recompute_rect_cache(); - return rect_cache; + // Return the visible rect of the tilemap. + if (layers.is_empty()) { + return Rect2(); + } + + bool any_changed = false; + bool changed = false; + Rect2 rect = layers[0]->get_rect(changed); + any_changed |= changed; + for (unsigned int i = 1; i < layers.size(); i++) { + rect = rect.merge(layers[i]->get_rect(changed)); + any_changed |= changed; + } + const_cast<TileMap *>(this)->item_rect_changed(any_changed); + return rect; } #endif @@ -3124,15 +3346,20 @@ bool TileMap::_set(const StringName &p_name, const Variant &p_value) { Vector<String> components = String(p_name).split("/", true, 2); if (p_name == "format") { if (p_value.get_type() == Variant::INT) { - format = (DataFormat)(p_value.operator int64_t()); // Set format used for loading + format = (TileMapLayer::DataFormat)(p_value.operator int64_t()); // Set format used for loading. return true; } } else if (p_name == "tile_data") { // Kept for compatibility reasons. if (p_value.is_array()) { - if (layers.size() < 1) { - layers.resize(1); + if (layers.size() == 0) { + Ref<TileMapLayer> new_layer; + new_layer.instantiate(); + new_layer->set_tile_map(this); + new_layer->set_layer_index_in_tile_map_node(0); + layers.push_back(new_layer); } - _set_tile_data(0, p_value); + layers[0]->set_tile_data(format, p_value); + emit_signal(CoreStringNames::get_singleton()->changed); return true; } return false; @@ -3145,12 +3372,16 @@ bool TileMap::_set(const StringName &p_name, const Variant &p_value) { if (index >= (int)layers.size()) { _clear_internals(); while (index >= (int)layers.size()) { - layers.push_back(TileMapLayer()); + Ref<TileMapLayer> new_layer; + new_layer.instantiate(); + new_layer->set_tile_map(this); + new_layer->set_layer_index_in_tile_map_node(index); + layers.push_back(new_layer); } _recreate_internals(); notify_property_list_changed(); - emit_signal(SNAME("changed")); + emit_signal(CoreStringNames::get_singleton()->changed); update_configuration_warnings(); } @@ -3173,7 +3404,8 @@ bool TileMap::_set(const StringName &p_name, const Variant &p_value) { set_layer_z_index(index, p_value); return true; } else if (components[1] == "tile_data") { - _set_tile_data(index, p_value); + layers[index]->set_tile_data(format, p_value); + emit_signal(CoreStringNames::get_singleton()->changed); return true; } else { return false; @@ -3185,7 +3417,7 @@ bool TileMap::_set(const StringName &p_name, const Variant &p_value) { bool TileMap::_get(const StringName &p_name, Variant &r_ret) const { Vector<String> components = String(p_name).split("/", true, 2); if (p_name == "format") { - r_ret = FORMAT_3; // When saving, always save highest format + r_ret = TileMapLayer::FORMAT_MAX - 1; // When saving, always save highest format. return true; } else 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(); @@ -3212,7 +3444,7 @@ bool TileMap::_get(const StringName &p_name, Variant &r_ret) const { r_ret = get_layer_z_index(index); return true; } else if (components[1] == "tile_data") { - r_ret = _get_tile_data(index); + r_ret = layers[index]->get_tile_data(); return true; } else { return false; @@ -3236,7 +3468,7 @@ void TileMap::_get_property_list(List<PropertyInfo> *p_list) const { } Vector2 TileMap::map_to_local(const Vector2i &p_pos) const { - // SHOULD RETURN THE CENTER OF THE CELL + // SHOULD RETURN THE CENTER OF THE CELL. ERR_FAIL_COND_V(!tile_set.is_valid(), Vector2()); Vector2 ret = p_pos; @@ -3245,7 +3477,7 @@ Vector2 TileMap::map_to_local(const Vector2i &p_pos) const { if (tile_shape == TileSet::TILE_SHAPE_HALF_OFFSET_SQUARE || tile_shape == TileSet::TILE_SHAPE_HEXAGON || tile_shape == TileSet::TILE_SHAPE_ISOMETRIC) { // Technically, those 3 shapes are equivalent, as they are basically half-offset, but with different levels or overlap. - // square = no overlap, hexagon = 0.25 overlap, isometric = 0.5 overlap + // square = no overlap, hexagon = 0.25 overlap, isometric = 0.5 overlap. if (tile_offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { switch (tile_set->get_tile_layout()) { case TileSet::TILE_LAYOUT_STACKED: @@ -3267,7 +3499,7 @@ Vector2 TileMap::map_to_local(const Vector2i &p_pos) const { ret = Vector2((ret.x - ret.y) / 2, ret.y + ret.x); break; } - } else { // TILE_OFFSET_AXIS_VERTICAL + } else { // TILE_OFFSET_AXIS_VERTICAL. switch (tile_set->get_tile_layout()) { case TileSet::TILE_LAYOUT_STACKED: ret = Vector2(ret.x, ret.y + (Math::posmod(ret.x, 2) == 0 ? 0.0 : 0.5)); @@ -3291,7 +3523,7 @@ Vector2 TileMap::map_to_local(const Vector2i &p_pos) const { } } - // Multiply by the overlapping ratio + // Multiply by the overlapping ratio. double overlapping_ratio = 1.0; if (tile_offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC) { @@ -3300,7 +3532,7 @@ Vector2 TileMap::map_to_local(const Vector2i &p_pos) const { overlapping_ratio = 0.75; } ret.y *= overlapping_ratio; - } else { // TILE_OFFSET_AXIS_VERTICAL + } else { // TILE_OFFSET_AXIS_VERTICAL. if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC) { overlapping_ratio = 0.5; } else if (tile_shape == TileSet::TILE_SHAPE_HEXAGON) { @@ -3322,7 +3554,7 @@ Vector2i TileMap::local_to_map(const Vector2 &p_local_position) const { TileSet::TileOffsetAxis tile_offset_axis = tile_set->get_tile_offset_axis(); TileSet::TileLayout tile_layout = tile_set->get_tile_layout(); - // Divide by the overlapping ratio + // Divide by the overlapping ratio. double overlapping_ratio = 1.0; if (tile_offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC) { @@ -3331,7 +3563,7 @@ Vector2i TileMap::local_to_map(const Vector2 &p_local_position) const { overlapping_ratio = 0.75; } ret.y /= overlapping_ratio; - } else { // TILE_OFFSET_AXIS_VERTICAL + } else { // TILE_OFFSET_AXIS_VERTICAL. if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC) { overlapping_ratio = 0.5; } else if (tile_shape == TileSet::TILE_SHAPE_HEXAGON) { @@ -3343,7 +3575,7 @@ Vector2i TileMap::local_to_map(const Vector2 &p_local_position) const { // For each half-offset shape, we check if we are in the corner of the tile, and thus should correct the local position accordingly. if (tile_shape == TileSet::TILE_SHAPE_HALF_OFFSET_SQUARE || tile_shape == TileSet::TILE_SHAPE_HEXAGON || tile_shape == TileSet::TILE_SHAPE_ISOMETRIC) { // Technically, those 3 shapes are equivalent, as they are basically half-offset, but with different levels or overlap. - // square = no overlap, hexagon = 0.25 overlap, isometric = 0.5 overlap + // square = no overlap, hexagon = 0.25 overlap, isometric = 0.5 overlap. if (tile_offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { // Smart floor of the position Vector2 raw_pos = ret; @@ -3353,7 +3585,7 @@ Vector2i TileMap::local_to_map(const Vector2 &p_local_position) const { ret = ret.floor(); } - // Compute the tile offset, and if we might the output for a neighbor top tile + // Compute the tile offset, and if we might the output for a neighbor top tile. Vector2 in_tile_pos = raw_pos - ret; bool in_top_left_triangle = (in_tile_pos - Vector2(0.5, 0.0)).cross(Vector2(-0.5, 1.0 / overlapping_ratio - 1)) <= 0; bool in_top_right_triangle = (in_tile_pos - Vector2(0.5, 0.0)).cross(Vector2(0.5, 1.0 / overlapping_ratio - 1)) > 0; @@ -3408,8 +3640,8 @@ Vector2i TileMap::local_to_map(const Vector2 &p_local_position) const { } break; } - } else { // TILE_OFFSET_AXIS_VERTICAL - // Smart floor of the position + } else { // TILE_OFFSET_AXIS_VERTICAL. + // Smart floor of the position. Vector2 raw_pos = ret; if (Math::posmod(Math::floor(ret.x), 2) ^ (tile_layout == TileSet::TILE_LAYOUT_STACKED_OFFSET)) { ret = Vector2(Math::floor(ret.x), Math::floor(ret.y + 0.5) - 0.5); @@ -3417,7 +3649,7 @@ Vector2i TileMap::local_to_map(const Vector2 &p_local_position) const { ret = ret.floor(); } - // Compute the tile offset, and if we might the output for a neighbor top tile + // Compute the tile offset, and if we might the output for a neighbor top tile. Vector2 in_tile_pos = raw_pos - ret; bool in_top_left_triangle = (in_tile_pos - Vector2(0.0, 0.5)).cross(Vector2(1.0 / overlapping_ratio - 1, -0.5)) > 0; bool in_bottom_left_triangle = (in_tile_pos - Vector2(0.0, 0.5)).cross(Vector2(1.0 / overlapping_ratio - 1, 0.5)) <= 0; @@ -3546,7 +3778,7 @@ Vector2i TileMap::get_neighbor_cell(const Vector2i &p_coords, TileSet::CellNeigh default: ERR_FAIL_V(p_coords); } - } else { // Half-offset shapes (square and hexagon) + } else { // Half-offset shapes (square and hexagon). switch (tile_set->get_tile_layout()) { case TileSet::TILE_LAYOUT_STACKED: { if (tile_set->get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { @@ -3856,63 +4088,23 @@ Vector2i TileMap::get_neighbor_cell(const Vector2i &p_coords, TileSet::CellNeigh } TypedArray<Vector2i> TileMap::get_used_cells(int p_layer) const { - ERR_FAIL_INDEX_V(p_layer, (int)layers.size(), TypedArray<Vector2i>()); - - // Returns the cells used in the tilemap. - TypedArray<Vector2i> a; - a.resize(layers[p_layer].tile_map.size()); - int i = 0; - for (const KeyValue<Vector2i, TileMapCell> &E : layers[p_layer].tile_map) { - Vector2i p(E.key.x, E.key.y); - a[i++] = p; - } - - return a; + TILEMAP_CALL_FOR_LAYER_V(p_layer, TypedArray<Vector2i>(), get_used_cells); } TypedArray<Vector2i> TileMap::get_used_cells_by_id(int p_layer, int p_source_id, const Vector2i p_atlas_coords, int p_alternative_tile) const { - ERR_FAIL_INDEX_V(p_layer, (int)layers.size(), TypedArray<Vector2i>()); - - // Returns the cells used in the tilemap. - TypedArray<Vector2i> a; - for (const KeyValue<Vector2i, TileMapCell> &E : layers[p_layer].tile_map) { - if ((p_source_id == TileSet::INVALID_SOURCE || p_source_id == E.value.source_id) && - (p_atlas_coords == TileSetSource::INVALID_ATLAS_COORDS || p_atlas_coords == E.value.get_atlas_coords()) && - (p_alternative_tile == TileSetSource::INVALID_TILE_ALTERNATIVE || p_alternative_tile == E.value.alternative_tile)) { - a.push_back(E.key); - } - } - - return a; + TILEMAP_CALL_FOR_LAYER_V(p_layer, TypedArray<Vector2i>(), get_used_cells_by_id); } -Rect2i TileMap::get_used_rect() { // Not const because of cache - // Return the rect of the currently used area - if (used_rect_cache_dirty) { - bool first = true; - used_rect_cache = Rect2i(); - - for (unsigned int i = 0; i < layers.size(); i++) { - const HashMap<Vector2i, TileMapCell> &tile_map = layers[i].tile_map; - if (tile_map.size() > 0) { - if (first) { - used_rect_cache = Rect2i(tile_map.begin()->key.x, tile_map.begin()->key.y, 0, 0); - first = false; - } - - for (const KeyValue<Vector2i, TileMapCell> &E : tile_map) { - used_rect_cache.expand_to(Vector2i(E.key.x, E.key.y)); - } - } - } - - if (!first) { // first is true if every layer is empty. - used_rect_cache.size += Vector2i(1, 1); // The cache expands to top-left coordinate, so we add one full tile. - } - used_rect_cache_dirty = false; +Rect2i TileMap::get_used_rect() const { + // Return the visible rect of the tilemap. + if (layers.is_empty()) { + return Rect2i(); } - - return used_rect_cache; + Rect2 rect = layers[0]->get_used_rect(); + for (unsigned int i = 1; i < layers.size(); i++) { + rect = rect.merge(layers[i]->get_used_rect()); + } + return rect; } // --- Override some methods of the CanvasItem class to pass the changes to the quadrants CanvasItems --- @@ -3920,13 +4112,8 @@ Rect2i TileMap::get_used_rect() { // Not const because of cache void TileMap::set_light_mask(int p_light_mask) { // Occlusion: set light mask. CanvasItem::set_light_mask(p_light_mask); - for (unsigned int layer = 0; layer < layers.size(); layer++) { - for (const KeyValue<Vector2i, TileMapQuadrant> &E : layers[layer].quadrant_map) { - for (const RID &ci : E.value.canvas_items) { - RenderingServer::get_singleton()->canvas_item_set_light_mask(ci, get_light_mask()); - } - } - _rendering_update_layer(layer); + for (Ref<TileMapLayer> &layer : layers) { + layer->notify_light_mask_changed(); } } @@ -3935,14 +4122,8 @@ void TileMap::set_material(const Ref<Material> &p_material) { CanvasItem::set_material(p_material); // Update material for the whole tilemap. - for (unsigned int layer = 0; layer < layers.size(); layer++) { - for (KeyValue<Vector2i, TileMapQuadrant> &E : layers[layer].quadrant_map) { - TileMapQuadrant &q = E.value; - for (const RID &ci : q.canvas_items) { - RS::get_singleton()->canvas_item_set_use_parent_material(ci, get_use_parent_material() || get_material().is_valid()); - } - } - _rendering_update_layer(layer); + for (Ref<TileMapLayer> &layer : layers) { + layer->notify_material_changed(); } } @@ -3951,46 +4132,24 @@ void TileMap::set_use_parent_material(bool p_use_parent_material) { CanvasItem::set_use_parent_material(p_use_parent_material); // Update use_parent_material for the whole tilemap. - for (unsigned int layer = 0; layer < layers.size(); layer++) { - for (KeyValue<Vector2i, TileMapQuadrant> &E : layers[layer].quadrant_map) { - TileMapQuadrant &q = E.value; - for (const RID &ci : q.canvas_items) { - RS::get_singleton()->canvas_item_set_use_parent_material(ci, get_use_parent_material() || get_material().is_valid()); - } - } - _rendering_update_layer(layer); + for (Ref<TileMapLayer> &layer : layers) { + layer->notify_use_parent_material_changed(); } } void TileMap::set_texture_filter(TextureFilter p_texture_filter) { // Set a default texture filter for the whole tilemap. CanvasItem::set_texture_filter(p_texture_filter); - TextureFilter target_filter = get_texture_filter_in_tree(); - for (unsigned int layer = 0; layer < layers.size(); layer++) { - for (HashMap<Vector2i, TileMapQuadrant>::Iterator F = layers[layer].quadrant_map.begin(); F; ++F) { - TileMapQuadrant &q = F->value; - for (const RID &ci : q.canvas_items) { - RenderingServer::get_singleton()->canvas_item_set_default_texture_filter(ci, RS::CanvasItemTextureFilter(target_filter)); - _make_quadrant_dirty(F); - } - } - _rendering_update_layer(layer); + for (Ref<TileMapLayer> &layer : layers) { + layer->notify_texture_filter_changed(); } } void TileMap::set_texture_repeat(CanvasItem::TextureRepeat p_texture_repeat) { // Set a default texture repeat for the whole tilemap. CanvasItem::set_texture_repeat(p_texture_repeat); - TextureRepeat target_repeat = get_texture_repeat_in_tree(); - for (unsigned int layer = 0; layer < layers.size(); layer++) { - for (HashMap<Vector2i, TileMapQuadrant>::Iterator F = layers[layer].quadrant_map.begin(); F; ++F) { - TileMapQuadrant &q = F->value; - for (const RID &ci : q.canvas_items) { - RenderingServer::get_singleton()->canvas_item_set_default_texture_repeat(ci, RS::CanvasItemTextureRepeat(target_repeat)); - _make_quadrant_dirty(F); - } - } - _rendering_update_layer(layer); + for (Ref<TileMapLayer> &layer : layers) { + layer->notify_texture_repeat_changed(); } } @@ -4088,15 +4247,15 @@ PackedStringArray TileMap::get_configuration_warnings() const { // Retrieve the set of Z index values with a Y-sorted layer. RBSet<int> y_sorted_z_index; - for (const TileMapLayer &layer : layers) { - if (layer.y_sort_enabled) { - y_sorted_z_index.insert(layer.z_index); + for (const Ref<TileMapLayer> &layer : layers) { + if (layer->is_y_sort_enabled()) { + y_sorted_z_index.insert(layer->get_z_index()); } } // Check if we have a non-sorted layer in a Z-index with a Y-sorted layer. - for (const TileMapLayer &layer : layers) { - if (!layer.y_sort_enabled && y_sorted_z_index.has(layer.z_index)) { + for (const Ref<TileMapLayer> &layer : layers) { + if (!layer->is_y_sort_enabled() && y_sorted_z_index.has(layer->get_z_index())) { warnings.push_back(RTR("A Y-sorted layer has the same Z-index value as a not Y-sorted layer.\nThis may lead to unwanted behaviors, as a layer that is not Y-sorted will be Y-sorted as a whole with tiles from Y-sorted layers.")); break; } @@ -4104,8 +4263,8 @@ PackedStringArray TileMap::get_configuration_warnings() const { // Check if Y-sort is enabled on a layer but not on the node. if (!is_y_sort_enabled()) { - for (const TileMapLayer &layer : layers) { - if (layer.y_sort_enabled) { + for (const Ref<TileMapLayer> &layer : layers) { + if (layer->is_y_sort_enabled()) { warnings.push_back(RTR("A TileMap layer is set as Y-sorted, but Y-sort is not enabled on the TileMap node itself.")); break; } @@ -4116,8 +4275,8 @@ PackedStringArray TileMap::get_configuration_warnings() const { if (tile_set.is_valid() && tile_set->get_tile_shape() == TileSet::TILE_SHAPE_ISOMETRIC) { bool warn = !is_y_sort_enabled(); if (!warn) { - for (const TileMapLayer &layer : layers) { - if (!layer.y_sort_enabled) { + for (const Ref<TileMapLayer> &layer : layers) { + if (!layer->is_y_sort_enabled()) { warn = true; break; } @@ -4155,6 +4314,13 @@ void TileMap::_bind_methods() { 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_index", "layer"), &TileMap::get_layer_z_index); + ClassDB::bind_method(D_METHOD("set_layer_navigation_map", "layer", "map"), &TileMap::set_layer_navigation_map); + ClassDB::bind_method(D_METHOD("get_layer_navigation_map", "layer"), &TileMap::get_layer_navigation_map); + +#ifndef DISABLE_DEPRECATED + ClassDB::bind_method(D_METHOD("set_navigation_map", "layer", "map"), &TileMap::set_layer_navigation_map); + ClassDB::bind_method(D_METHOD("get_navigation_map", "layer"), &TileMap::get_layer_navigation_map); +#endif // DISABLE_DEPRECATED ClassDB::bind_method(D_METHOD("set_collision_animatable", "enabled"), &TileMap::set_collision_animatable); ClassDB::bind_method(D_METHOD("is_collision_animatable"), &TileMap::is_collision_animatable); @@ -4164,9 +4330,6 @@ void TileMap::_bind_methods() { ClassDB::bind_method(D_METHOD("set_navigation_visibility_mode", "navigation_visibility_mode"), &TileMap::set_navigation_visibility_mode); ClassDB::bind_method(D_METHOD("get_navigation_visibility_mode"), &TileMap::get_navigation_visibility_mode); - ClassDB::bind_method(D_METHOD("set_navigation_map", "layer", "map"), &TileMap::set_navigation_map); - ClassDB::bind_method(D_METHOD("get_navigation_map", "layer"), &TileMap::get_navigation_map); - ClassDB::bind_method(D_METHOD("set_cell", "layer", "coords", "source_id", "atlas_coords", "alternative_tile"), &TileMap::set_cell, DEFVAL(TileSet::INVALID_SOURCE), DEFVAL(TileSetSource::INVALID_ATLAS_COORDS), DEFVAL(0)); ClassDB::bind_method(D_METHOD("erase_cell", "layer", "coords"), &TileMap::erase_cell); ClassDB::bind_method(D_METHOD("get_cell_source_id", "layer", "coords", "use_proxies"), &TileMap::get_cell_source_id, DEFVAL(false)); @@ -4216,9 +4379,9 @@ void TileMap::_bind_methods() { ADD_ARRAY("layers", "layer_"); - ADD_PROPERTY_DEFAULT("format", FORMAT_1); + ADD_PROPERTY_DEFAULT("format", TileMapLayer::FORMAT_1); - ADD_SIGNAL(MethodInfo("changed")); + ADD_SIGNAL(MethodInfo(CoreStringNames::get_singleton()->changed)); BIND_ENUM_CONSTANT(VISIBILITY_MODE_DEFAULT); BIND_ENUM_CONSTANT(VISIBILITY_MODE_FORCE_HIDE); @@ -4226,9 +4389,11 @@ void TileMap::_bind_methods() { } void TileMap::_tile_set_changed() { - emit_signal(SNAME("changed")); + emit_signal(CoreStringNames::get_singleton()->changed); _tile_set_changed_deferred_update_needed = true; - instantiated_scenes.clear(); + for (Ref<TileMapLayer> &layer : layers) { + layer->clear_instantiated_scenes(); + } call_deferred(SNAME("_tile_set_changed_deferred_update")); update_configuration_warnings(); } @@ -4245,13 +4410,20 @@ TileMap::TileMap() { set_notify_transform(true); set_notify_local_transform(false); - layers.resize(1); + Ref<TileMapLayer> new_layer; + new_layer.instantiate(); + new_layer->set_tile_map(this); + new_layer->set_layer_index_in_tile_map_node(0); + layers.push_back(new_layer); } TileMap::~TileMap() { if (tile_set.is_valid()) { - tile_set->disconnect("changed", callable_mp(this, &TileMap::_tile_set_changed)); + tile_set->disconnect_changed(callable_mp(this, &TileMap::_tile_set_changed)); } _clear_internals(); } + +#undef TILEMAP_CALL_FOR_LAYER +#undef TILEMAP_CALL_FOR_LAYER_V diff --git a/scene/2d/tile_map.h b/scene/2d/tile_map.h index 13c0eb4a95..0ad47c51da 100644 --- a/scene/2d/tile_map.h +++ b/scene/2d/tile_map.h @@ -37,6 +37,58 @@ class TileSetAtlasSource; +class TerrainConstraint { +private: + const TileMap *tile_map = nullptr; + Vector2i base_cell_coords; + int bit = -1; + int terrain = -1; + + int priority = 1; + +public: + bool operator<(const TerrainConstraint &p_other) const { + if (base_cell_coords == p_other.base_cell_coords) { + return bit < p_other.bit; + } + return base_cell_coords < p_other.base_cell_coords; + } + + String to_string() const { + return vformat("Constraint {pos:%s, bit:%d, terrain:%d, priority:%d}", base_cell_coords, bit, terrain, priority); + } + + Vector2i get_base_cell_coords() const { + return base_cell_coords; + } + + bool is_center_bit() const { + return bit == 0; + } + + HashMap<Vector2i, TileSet::CellNeighbor> get_overlapping_coords_and_peering_bits() const; + + void set_terrain(int p_terrain) { + terrain = p_terrain; + } + + int get_terrain() const { + return terrain; + } + + void set_priority(int p_priority) { + priority = p_priority; + } + + int get_priority() const { + return priority; + } + + TerrainConstraint(const TileMap *p_tile_map, const Vector2i &p_position, int p_terrain); // For the center terrain bit + TerrainConstraint(const TileMap *p_tile_map, const Vector2i &p_position, const TileSet::CellNeighbor &p_bit, int p_terrain); // For peering bits + TerrainConstraint(){}; +}; + struct TileMapQuadrant { struct CoordsWorldComparator { _ALWAYS_INLINE_ bool operator()(const Vector2 &p_a, const Vector2 &p_b) const { @@ -52,13 +104,12 @@ struct TileMapQuadrant { // Dirty list element. SelfList<TileMapQuadrant> dirty_list_element; - // Quadrant layer and coords. - int layer = -1; + // Quadrant coords. Vector2i coords; - // TileMapCells + // TileMapCells. RBSet<Vector2i> cells; - // We need those two maps to sort by local position for rendering + // We need those two maps to sort by local position for rendering. // This is kind of workaround, it would be better to sort the cells directly in the "cells" set instead. RBMap<Vector2i, Vector2> map_to_local; RBMap<Vector2, Vector2i, CoordsWorldComparator> local_to_map; @@ -83,7 +134,6 @@ struct TileMapQuadrant { HashMap<Vector2i, TileData *> runtime_tile_data_cache; void operator=(const TileMapQuadrant &q) { - layer = q.layer; coords = q.coords; debug_canvas_item = q.debug_canvas_item; canvas_items = q.canvas_items; @@ -94,7 +144,6 @@ struct TileMapQuadrant { TileMapQuadrant(const TileMapQuadrant &q) : dirty_list_element(this) { - layer = q.layer; coords = q.coords; debug_canvas_item = q.debug_canvas_item; canvas_items = q.canvas_items; @@ -108,62 +157,174 @@ struct TileMapQuadrant { } }; -class TileMap : public Node2D { - GDCLASS(TileMap, Node2D); - +class TileMapLayer : public RefCounted { public: - class TerrainConstraint { - private: - const TileMap *tile_map; - Vector2i base_cell_coords; - int bit = -1; - int terrain = -1; - - int priority = 1; - - public: - bool operator<(const TerrainConstraint &p_other) const { - if (base_cell_coords == p_other.base_cell_coords) { - return bit < p_other.bit; - } - return base_cell_coords < p_other.base_cell_coords; - } + enum DataFormat { + FORMAT_1 = 0, + FORMAT_2, + FORMAT_3, + FORMAT_MAX, + }; - String to_string() const { - return vformat("Constraint {pos:%s, bit:%d, terrain:%d, priority:%d}", base_cell_coords, bit, terrain, priority); - } +private: + // Exposed properties. + String name; + bool enabled = true; + Color modulate = Color(1, 1, 1, 1); + bool y_sort_enabled = false; + int y_sort_origin = 0; + int z_index = 0; + RID navigation_map; + bool uses_world_navigation_map = false; + + // Internal. + TileMap *tile_map_node = nullptr; + int layer_index_in_tile_map_node = -1; + RID canvas_item; + bool _rendering_quadrant_order_dirty = false; + HashMap<Vector2i, TileMapCell> tile_map; + HashMap<Vector2i, TileMapQuadrant> quadrant_map; + SelfList<TileMapQuadrant>::List dirty_quadrant_list; + + // Rect cache. + mutable Rect2 rect_cache; + mutable bool rect_cache_dirty = true; + mutable Rect2i used_rect_cache; + mutable bool used_rect_cache_dirty = true; + + // Quadrants management. + Vector2i _coords_to_quadrant_coords(const Vector2i &p_coords) const; + HashMap<Vector2i, TileMapQuadrant>::Iterator _create_quadrant(const Vector2i &p_qk); + void _make_quadrant_dirty(HashMap<Vector2i, TileMapQuadrant>::Iterator Q); + void _erase_quadrant(HashMap<Vector2i, TileMapQuadrant>::Iterator Q); - Vector2i get_base_cell_coords() const { - return base_cell_coords; - } + // Per-system methods. + void _rendering_notification(int p_what); + void _rendering_update(); + void _rendering_cleanup(); + void _rendering_update_dirty_quadrants(SelfList<TileMapQuadrant>::List &r_dirty_quadrant_list); + void _rendering_reorder_quadrants(int &r_index); + void _rendering_create_quadrant(TileMapQuadrant *p_quadrant); + void _rendering_cleanup_quadrant(TileMapQuadrant *p_quadrant); + void _rendering_draw_quadrant_debug(TileMapQuadrant *p_quadrant); - bool is_center_bit() const { - return bit == 0; - } + HashMap<RID, Vector2i> bodies_coords; // Mapping for RID to coords. + void _physics_update_dirty_quadrants(SelfList<TileMapQuadrant>::List &r_dirty_quadrant_list); + void _physics_cleanup_quadrant(TileMapQuadrant *p_quadrant); + void _physics_draw_quadrant_debug(TileMapQuadrant *p_quadrant); - HashMap<Vector2i, TileSet::CellNeighbor> get_overlapping_coords_and_peering_bits() const; + void _navigation_update(); + void _navigation_cleanup(); + void _navigation_update_dirty_quadrants(SelfList<TileMapQuadrant>::List &r_dirty_quadrant_list); + void _navigation_cleanup_quadrant(TileMapQuadrant *p_quadrant); + void _navigation_draw_quadrant_debug(TileMapQuadrant *p_quadrant); - void set_terrain(int p_terrain) { - terrain = p_terrain; - } + HashSet<Vector2i> instantiated_scenes; + void _scenes_update_dirty_quadrants(SelfList<TileMapQuadrant>::List &r_dirty_quadrant_list); + void _scenes_cleanup_quadrant(TileMapQuadrant *p_quadrant); + void _scenes_draw_quadrant_debug(TileMapQuadrant *p_quadrant); - int get_terrain() const { - return terrain; - } + // Runtime tile data. + void _build_runtime_update_tile_data(SelfList<TileMapQuadrant>::List &r_dirty_quadrant_list); - void set_priority(int p_priority) { - priority = p_priority; - } + // Terrains. + TileSet::TerrainsPattern _get_best_terrain_pattern_for_constraints(int p_terrain_set, const Vector2i &p_position, const RBSet<TerrainConstraint> &p_constraints, TileSet::TerrainsPattern p_current_pattern); + RBSet<TerrainConstraint> _get_terrain_constraints_from_added_pattern(const Vector2i &p_position, int p_terrain_set, TileSet::TerrainsPattern p_terrains_pattern) const; + RBSet<TerrainConstraint> _get_terrain_constraints_from_painted_cells_list(const RBSet<Vector2i> &p_painted, int p_terrain_set, bool p_ignore_empty_terrains) const; - int get_priority() const { - return priority; - } +public: + // TileMap node. + void set_tile_map(TileMap *p_tile_map); + void set_layer_index_in_tile_map_node(int p_index); - TerrainConstraint(const TileMap *p_tile_map, const Vector2i &p_position, int p_terrain); // For the center terrain bit - TerrainConstraint(const TileMap *p_tile_map, const Vector2i &p_position, const TileSet::CellNeighbor &p_bit, int p_terrain); // For peering bits - TerrainConstraint(){}; - }; + // Rect caching. + Rect2 get_rect(bool &r_changed) const; + // Terrains. + HashMap<Vector2i, TileSet::TerrainsPattern> terrain_fill_constraints(const Vector<Vector2i> &p_to_replace, int p_terrain_set, const RBSet<TerrainConstraint> &p_constraints); // Not exposed. + HashMap<Vector2i, TileSet::TerrainsPattern> terrain_fill_connect(const Vector<Vector2i> &p_coords_array, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains = true); // Not exposed. + HashMap<Vector2i, TileSet::TerrainsPattern> terrain_fill_path(const Vector<Vector2i> &p_coords_array, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains = true); // Not exposed. + HashMap<Vector2i, TileSet::TerrainsPattern> terrain_fill_pattern(const Vector<Vector2i> &p_coords_array, int p_terrain_set, TileSet::TerrainsPattern p_terrains_pattern, bool p_ignore_empty_terrains = true); // Not exposed. + + // Not exposed to users. + TileMapCell get_cell(const Vector2i &p_coords, bool p_use_proxies = false) const; + int get_effective_quadrant_size() const; + + // For TileMap node's use. + void notify_canvas_entered(); + void notify_visibility_changed(); + void notify_xform_changed(); + void notify_local_xform_changed(); + void notify_canvas_exited(); + void notify_selected_layer_changed(); + void notify_light_mask_changed(); + void notify_material_changed(); + void notify_use_parent_material_changed(); + void notify_texture_filter_changed(); + void notify_texture_repeat_changed(); + void update_dirty_quadrants(); + void set_tile_data(DataFormat p_format, const Vector<int> &p_data); + Vector<int> get_tile_data() const; + void clear_instantiated_scenes(); + void clear_internals(); // Exposed for now to tilemap, but ideally, we should avoid it. + void recreate_internals(); // Exposed for now to tilemap, but ideally, we should avoid it. + + // --- Exposed in TileMap --- + + // Cells manipulation. + void set_cell(const Vector2i &p_coords, int p_source_id = TileSet::INVALID_SOURCE, const Vector2i p_atlas_coords = TileSetSource::INVALID_ATLAS_COORDS, int p_alternative_tile = 0); + void erase_cell(const Vector2i &p_coords); + + int get_cell_source_id(const Vector2i &p_coords, bool p_use_proxies = false) const; + Vector2i get_cell_atlas_coords(const Vector2i &p_coords, bool p_use_proxies = false) const; + int get_cell_alternative_tile(const Vector2i &p_coords, bool p_use_proxies = false) const; + TileData *get_cell_tile_data(const Vector2i &p_coords, bool p_use_proxies = false) const; // Helper method to make accessing the data easier. + void clear(); + + // Patterns. + Ref<TileMapPattern> get_pattern(TypedArray<Vector2i> p_coords_array); + void set_pattern(const Vector2i &p_position, const Ref<TileMapPattern> p_pattern); + + // Terrains. + void set_cells_terrain_connect(TypedArray<Vector2i> p_cells, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains = true); + void set_cells_terrain_path(TypedArray<Vector2i> p_path, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains = true); + + // Cells usage. + TypedArray<Vector2i> get_used_cells() const; + TypedArray<Vector2i> get_used_cells_by_id(int p_source_id = TileSet::INVALID_SOURCE, const Vector2i p_atlas_coords = TileSetSource::INVALID_ATLAS_COORDS, int p_alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE) const; + Rect2i get_used_rect() const; + + // Layer properties. + void set_name(String p_name); + String get_name() const; + void set_enabled(bool p_enabled); + bool is_enabled() const; + void set_modulate(Color p_modulate); + Color get_modulate() const; + void set_y_sort_enabled(bool p_y_sort_enabled); + bool is_y_sort_enabled() const; + void set_y_sort_origin(int p_y_sort_origin); + int get_y_sort_origin() const; + void set_z_index(int p_z_index); + int get_z_index() const; + void set_navigation_map(RID p_map); + RID get_navigation_map() const; + + // In case something goes wrong. + void force_update(); + + // Fixing and clearing methods. + void fix_invalid_tiles(); + + // Find coords for body. + bool has_body_rid(RID p_physics_body) const; + Vector2i get_coords_for_body_rid(RID p_physics_body) const; // For finding tiles from collision. +}; + +class TileMap : public Node2D { + GDCLASS(TileMap, Node2D); + +public: enum VisibilityMode { VISIBILITY_MODE_DEFAULT, VISIBILITY_MODE_FORCE_SHOW, @@ -174,12 +335,7 @@ private: friend class TileSetPlugin; // A compatibility enum to specify how is the data if formatted. - enum DataFormat { - FORMAT_1 = 0, - FORMAT_2, - FORMAT_3 - }; - mutable DataFormat format = FORMAT_3; + mutable TileMapLayer::DataFormat format = TileMapLayer::FORMAT_3; static constexpr float FP_ADJUST = 0.00001; @@ -190,99 +346,17 @@ private: VisibilityMode collision_visibility_mode = VISIBILITY_MODE_DEFAULT; VisibilityMode navigation_visibility_mode = VISIBILITY_MODE_DEFAULT; - // Updates. - bool pending_update = false; - - // Rect. - Rect2 rect_cache; - bool rect_cache_dirty = true; - Rect2i used_rect_cache; - bool used_rect_cache_dirty = true; - - // TileMap layers. - struct TileMapLayer { - String name; - bool enabled = true; - Color modulate = Color(1, 1, 1, 1); - bool y_sort_enabled = false; - int y_sort_origin = 0; - int z_index = 0; - RID canvas_item; - HashMap<Vector2i, TileMapCell> tile_map; - HashMap<Vector2i, TileMapQuadrant> quadrant_map; - SelfList<TileMapQuadrant>::List dirty_quadrant_list; - RID navigation_map; - bool uses_world_navigation_map = false; - }; - LocalVector<TileMapLayer> layers; + // Layers. + LocalVector<Ref<TileMapLayer>> layers; int selected_layer = -1; - // Mapping for RID to coords. - HashMap<RID, Vector2i> bodies_coords; - // Mapping for RID to tile layer. - HashMap<RID, int> bodies_layers; - - // Quadrants and internals management. - Vector2i _coords_to_quadrant_coords(int p_layer, const Vector2i &p_coords) const; - - HashMap<Vector2i, TileMapQuadrant>::Iterator _create_quadrant(int p_layer, const Vector2i &p_qk); - - void _make_quadrant_dirty(HashMap<Vector2i, TileMapQuadrant>::Iterator Q); - void _make_all_quadrants_dirty(); - void _queue_update_dirty_quadrants(); - - void _update_dirty_quadrants(); - - void _recreate_layer_internals(int p_layer); - void _recreate_internals(); - - void _erase_quadrant(HashMap<Vector2i, TileMapQuadrant>::Iterator Q); - void _clear_layer_internals(int p_layer); void _clear_internals(); + void _recreate_internals(); - HashSet<Vector3i> instantiated_scenes; - - // Rect caching. - void _recompute_rect_cache(); - - // Per-system methods. - bool _rendering_quadrant_order_dirty = false; - void _rendering_notification(int p_what); - void _rendering_update_layer(int p_layer); - void _rendering_cleanup_layer(int p_layer); - void _rendering_update_dirty_quadrants(SelfList<TileMapQuadrant>::List &r_dirty_quadrant_list); - void _rendering_create_quadrant(TileMapQuadrant *p_quadrant); - void _rendering_cleanup_quadrant(TileMapQuadrant *p_quadrant); - void _rendering_draw_quadrant_debug(TileMapQuadrant *p_quadrant); + bool pending_update = false; Transform2D last_valid_transform; Transform2D new_transform; - void _physics_notification(int p_what); - void _physics_update_dirty_quadrants(SelfList<TileMapQuadrant>::List &r_dirty_quadrant_list); - void _physics_cleanup_quadrant(TileMapQuadrant *p_quadrant); - void _physics_draw_quadrant_debug(TileMapQuadrant *p_quadrant); - - void _navigation_notification(int p_what); - void _navigation_update_layer(int p_layer); - void _navigation_cleanup_layer(int p_layer); - void _navigation_update_dirty_quadrants(SelfList<TileMapQuadrant>::List &r_dirty_quadrant_list); - void _navigation_cleanup_quadrant(TileMapQuadrant *p_quadrant); - void _navigation_draw_quadrant_debug(TileMapQuadrant *p_quadrant); - - void _scenes_update_dirty_quadrants(SelfList<TileMapQuadrant>::List &r_dirty_quadrant_list); - void _scenes_cleanup_quadrant(TileMapQuadrant *p_quadrant); - void _scenes_draw_quadrant_debug(TileMapQuadrant *p_quadrant); - - // Terrains. - TileSet::TerrainsPattern _get_best_terrain_pattern_for_constraints(int p_terrain_set, const Vector2i &p_position, const RBSet<TerrainConstraint> &p_constraints, TileSet::TerrainsPattern p_current_pattern); - RBSet<TerrainConstraint> _get_terrain_constraints_from_added_pattern(const Vector2i &p_position, int p_terrain_set, TileSet::TerrainsPattern p_terrains_pattern) const; - RBSet<TerrainConstraint> _get_terrain_constraints_from_painted_cells_list(int p_layer, const RBSet<Vector2i> &p_painted, int p_terrain_set, bool p_ignore_empty_terrains) const; - - // Set and get tiles from data arrays. - void _set_tile_data(int p_layer, const Vector<int> &p_data); - Vector<int> _get_tile_data(int p_layer) const; - - void _build_runtime_update_tile_data(SelfList<TileMapQuadrant>::List &r_dirty_quadrant_list); void _tile_set_changed(); bool _tile_set_changed_deferred_update_needed = false; @@ -296,17 +370,22 @@ protected: void _notification(int p_what); static void _bind_methods(); +#ifndef DISABLE_DEPRECATED + Rect2i _get_used_rect_bind_compat_78328(); + static void _bind_compatibility_methods(); +#endif + public: static Vector2i transform_coords_layout(const Vector2i &p_coords, TileSet::TileOffsetAxis p_offset_axis, TileSet::TileLayout p_from_layout, TileSet::TileLayout p_to_layout); - enum { - INVALID_CELL = -1 - }; - #ifdef TOOLS_ENABLED virtual Rect2 _edit_get_rect() const override; #endif + // Called by TileMapLayers. + void queue_update_dirty_quadrants(); + void _update_dirty_quadrants(); + void set_tileset(const Ref<TileSet> &p_tileset); Ref<TileSet> get_tileset() const; @@ -320,6 +399,7 @@ public: 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); @@ -332,6 +412,9 @@ public: int get_layer_y_sort_origin(int p_layer) const; void set_layer_z_index(int p_layer, int p_z_index); int get_layer_z_index(int p_layer) const; + void set_layer_navigation_map(int p_layer, RID p_map); + RID get_layer_navigation_map(int p_layer) const; + void set_selected_layer(int p_layer_id); // For editor use. int get_selected_layer() const; @@ -345,9 +428,6 @@ public: void set_navigation_visibility_mode(VisibilityMode p_show_navigation); VisibilityMode get_navigation_visibility_mode(); - void set_navigation_map(int p_layer, RID p_map); - RID get_navigation_map(int p_layer) const; - // Cells accessors. void set_cell(int p_layer, const Vector2i &p_coords, int p_source_id = TileSet::INVALID_SOURCE, const Vector2i p_atlas_coords = TileSetSource::INVALID_ATLAS_COORDS, int p_alternative_tile = 0); void erase_cell(int p_layer, const Vector2i &p_coords); @@ -362,20 +442,19 @@ public: Vector2i map_pattern(const Vector2i &p_position_in_tilemap, const Vector2i &p_coords_in_pattern, Ref<TileMapPattern> p_pattern); void set_pattern(int p_layer, const Vector2i &p_position, const Ref<TileMapPattern> p_pattern); - // Terrains. - HashMap<Vector2i, TileSet::TerrainsPattern> terrain_fill_constraints(int p_layer, const Vector<Vector2i> &p_to_replace, int p_terrain_set, const RBSet<TerrainConstraint> &p_constraints); // Not exposed. - HashMap<Vector2i, TileSet::TerrainsPattern> terrain_fill_connect(int p_layer, const Vector<Vector2i> &p_coords_array, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains = true); // Not exposed. - HashMap<Vector2i, TileSet::TerrainsPattern> terrain_fill_path(int p_layer, const Vector<Vector2i> &p_coords_array, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains = true); // Not exposed. - HashMap<Vector2i, TileSet::TerrainsPattern> terrain_fill_pattern(int p_layer, const Vector<Vector2i> &p_coords_array, int p_terrain_set, TileSet::TerrainsPattern p_terrains_pattern, bool p_ignore_empty_terrains = true); // Not exposed. + // Terrains (Not exposed). + HashMap<Vector2i, TileSet::TerrainsPattern> terrain_fill_constraints(int p_layer, const Vector<Vector2i> &p_to_replace, int p_terrain_set, const RBSet<TerrainConstraint> &p_constraints); + HashMap<Vector2i, TileSet::TerrainsPattern> terrain_fill_connect(int p_layer, const Vector<Vector2i> &p_coords_array, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains = true); + HashMap<Vector2i, TileSet::TerrainsPattern> terrain_fill_path(int p_layer, const Vector<Vector2i> &p_coords_array, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains = true); + HashMap<Vector2i, TileSet::TerrainsPattern> terrain_fill_pattern(int p_layer, const Vector<Vector2i> &p_coords_array, int p_terrain_set, TileSet::TerrainsPattern p_terrains_pattern, bool p_ignore_empty_terrains = true); + // Terrains (exposed). void set_cells_terrain_connect(int p_layer, TypedArray<Vector2i> p_cells, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains = true); void set_cells_terrain_path(int p_layer, TypedArray<Vector2i> p_path, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains = true); - // Not exposed to users + // Not exposed to users. TileMapCell get_cell(int p_layer, const Vector2i &p_coords, bool p_use_proxies = false) const; - HashMap<Vector2i, TileMapQuadrant> *get_quadrant_map(int p_layer); int get_effective_quadrant_size(int p_layer) const; - //--- virtual void set_y_sort_enabled(bool p_enable) override; @@ -387,9 +466,9 @@ public: TypedArray<Vector2i> get_used_cells(int p_layer) const; TypedArray<Vector2i> get_used_cells_by_id(int p_layer, int p_source_id = TileSet::INVALID_SOURCE, const Vector2i p_atlas_coords = TileSetSource::INVALID_ATLAS_COORDS, int p_alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE) const; - Rect2i get_used_rect(); // Not const because of cache + Rect2i get_used_rect() const; - // Override some methods of the CanvasItem class to pass the changes to the quadrants CanvasItems + // Override some methods of the CanvasItem class to pass the changes to the quadrants CanvasItems. virtual void set_light_mask(int p_light_mask) override; virtual void set_material(const Ref<Material> &p_material) override; virtual void set_use_parent_material(bool p_use_parent_material) override; @@ -404,18 +483,18 @@ public: // Fixing and clearing methods. void fix_invalid_tiles(); - // Clears tiles from a given layer + // Clears tiles from a given layer. void clear_layer(int p_layer); void clear(); - // Force a TileMap update + // Force a TileMap update. void force_update(int p_layer = -1); // Helpers? TypedArray<Vector2i> get_surrounding_cells(const Vector2i &coords); void draw_cells_outline(Control *p_control, const RBSet<Vector2i> &p_cells, Color p_color, Transform2D p_transform = Transform2D()); - // Virtual function to modify the TileData at runtime + // Virtual function to modify the TileData at runtime. GDVIRTUAL2R(bool, _use_tile_data_runtime_update, int, Vector2i); GDVIRTUAL3(_tile_data_runtime_update, int, Vector2i, TileData *); diff --git a/scene/2d/touch_screen_button.cpp b/scene/2d/touch_screen_button.cpp index 10a9e7edec..5ed7fadb2a 100644 --- a/scene/2d/touch_screen_button.cpp +++ b/scene/2d/touch_screen_button.cpp @@ -82,11 +82,11 @@ void TouchScreenButton::set_shape(const Ref<Shape2D> &p_shape) { return; } if (shape.is_valid()) { - shape->disconnect("changed", callable_mp((CanvasItem *)this, &CanvasItem::queue_redraw)); + shape->disconnect_changed(callable_mp((CanvasItem *)this, &CanvasItem::queue_redraw)); } shape = p_shape; if (shape.is_valid()) { - shape->connect("changed", callable_mp((CanvasItem *)this, &CanvasItem::queue_redraw)); + shape->connect_changed(callable_mp((CanvasItem *)this, &CanvasItem::queue_redraw)); } queue_redraw(); } diff --git a/scene/3d/camera_3d.cpp b/scene/3d/camera_3d.cpp index 225b9b35b3..37ceb9d1a1 100644 --- a/scene/3d/camera_3d.cpp +++ b/scene/3d/camera_3d.cpp @@ -31,7 +31,6 @@ #include "camera_3d.h" #include "collision_object_3d.h" -#include "core/core_string_names.h" #include "core/math/projection.h" #include "scene/main/viewport.h" @@ -430,7 +429,7 @@ void Camera3D::set_attributes(const Ref<CameraAttributes> &p_attributes) { if (attributes.is_valid()) { CameraAttributesPhysical *physical_attributes = Object::cast_to<CameraAttributesPhysical>(attributes.ptr()); if (physical_attributes) { - attributes->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(this, &Camera3D::_attributes_changed)); + attributes->disconnect_changed(callable_mp(this, &Camera3D::_attributes_changed)); } } @@ -439,7 +438,7 @@ void Camera3D::set_attributes(const Ref<CameraAttributes> &p_attributes) { if (attributes.is_valid()) { CameraAttributesPhysical *physical_attributes = Object::cast_to<CameraAttributesPhysical>(attributes.ptr()); if (physical_attributes) { - attributes->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &Camera3D::_attributes_changed)); + attributes->connect_changed(callable_mp(this, &Camera3D::_attributes_changed)); _attributes_changed(); } diff --git a/scene/3d/collision_object_3d.cpp b/scene/3d/collision_object_3d.cpp index 6d8d60dcaa..bfe594adc2 100644 --- a/scene/3d/collision_object_3d.cpp +++ b/scene/3d/collision_object_3d.cpp @@ -394,11 +394,7 @@ void CollisionObject3D::_update_debug_shapes() { if (s.debug_shape.is_null()) { s.debug_shape = RS::get_singleton()->instance_create(); RS::get_singleton()->instance_set_scenario(s.debug_shape, get_world_3d()->get_scenario()); - - if (!s.shape->is_connected("changed", callable_mp(this, &CollisionObject3D::_shape_changed))) { - s.shape->connect("changed", callable_mp(this, &CollisionObject3D::_shape_changed).bind(s.shape), CONNECT_DEFERRED); - } - + s.shape->connect_changed(callable_mp(this, &CollisionObject3D::_shape_changed).bind(s.shape), CONNECT_DEFERRED); ++debug_shapes_count; } @@ -422,8 +418,8 @@ void CollisionObject3D::_clear_debug_shapes() { if (s.debug_shape.is_valid()) { RS::get_singleton()->free(s.debug_shape); s.debug_shape = RID(); - if (s.shape.is_valid() && s.shape->is_connected("changed", callable_mp(this, &CollisionObject3D::_update_shape_data))) { - s.shape->disconnect("changed", callable_mp(this, &CollisionObject3D::_update_shape_data)); + if (s.shape.is_valid()) { + s.shape->disconnect_changed(callable_mp(this, &CollisionObject3D::_update_shape_data)); } } } @@ -663,8 +659,8 @@ void CollisionObject3D::shape_owner_remove_shape(uint32_t p_owner, int p_shape) if (s.debug_shape.is_valid()) { RS::get_singleton()->free(s.debug_shape); - if (s.shape.is_valid() && s.shape->is_connected("changed", callable_mp(this, &CollisionObject3D::_shape_changed))) { - s.shape->disconnect("changed", callable_mp(this, &CollisionObject3D::_shape_changed)); + if (s.shape.is_valid()) { + s.shape->disconnect_changed(callable_mp(this, &CollisionObject3D::_shape_changed)); } --debug_shapes_count; } diff --git a/scene/3d/collision_shape_3d.cpp b/scene/3d/collision_shape_3d.cpp index 10eefc784d..0bb0382301 100644 --- a/scene/3d/collision_shape_3d.cpp +++ b/scene/3d/collision_shape_3d.cpp @@ -112,9 +112,10 @@ void CollisionShape3D::_notification(int p_what) { } } +#ifndef DISABLE_DEPRECATED void CollisionShape3D::resource_changed(Ref<Resource> res) { - update_gizmos(); } +#endif PackedStringArray CollisionShape3D::get_configuration_warnings() const { PackedStringArray warnings = Node::get_configuration_warnings(); @@ -145,8 +146,9 @@ PackedStringArray CollisionShape3D::get_configuration_warnings() const { } void CollisionShape3D::_bind_methods() { - //not sure if this should do anything +#ifndef DISABLE_DEPRECATED ClassDB::bind_method(D_METHOD("resource_changed", "resource"), &CollisionShape3D::resource_changed); +#endif ClassDB::bind_method(D_METHOD("set_shape", "shape"), &CollisionShape3D::set_shape); ClassDB::bind_method(D_METHOD("get_shape"), &CollisionShape3D::get_shape); ClassDB::bind_method(D_METHOD("set_disabled", "enable"), &CollisionShape3D::set_disabled); @@ -162,12 +164,12 @@ void CollisionShape3D::set_shape(const Ref<Shape3D> &p_shape) { if (p_shape == shape) { return; } - if (!shape.is_null()) { - shape->unregister_owner(this); + if (shape.is_valid()) { + shape->disconnect_changed(callable_mp((Node3D *)this, &Node3D::update_gizmos)); } shape = p_shape; - if (!shape.is_null()) { - shape->register_owner(this); + if (shape.is_valid()) { + shape->connect_changed(callable_mp((Node3D *)this, &Node3D::update_gizmos)); } update_gizmos(); if (collision_object) { @@ -206,8 +208,5 @@ CollisionShape3D::CollisionShape3D() { } CollisionShape3D::~CollisionShape3D() { - if (!shape.is_null()) { - shape->unregister_owner(this); - } //RenderingServer::get_singleton()->free(indicator); } diff --git a/scene/3d/collision_shape_3d.h b/scene/3d/collision_shape_3d.h index 74928bad6d..bc0e70f8ac 100644 --- a/scene/3d/collision_shape_3d.h +++ b/scene/3d/collision_shape_3d.h @@ -43,7 +43,9 @@ class CollisionShape3D : public Node3D { uint32_t owner_id = 0; CollisionObject3D *collision_object = nullptr; +#ifndef DISABLE_DEPRECATED void resource_changed(Ref<Resource> res); +#endif bool disabled = false; protected: diff --git a/scene/3d/cpu_particles_3d.cpp b/scene/3d/cpu_particles_3d.cpp index 405d478a47..3dc82cfb97 100644 --- a/scene/3d/cpu_particles_3d.cpp +++ b/scene/3d/cpu_particles_3d.cpp @@ -33,7 +33,11 @@ #include "scene/3d/camera_3d.h" #include "scene/3d/gpu_particles_3d.h" #include "scene/main/viewport.h" +#include "scene/resources/curve_texture.h" +#include "scene/resources/gradient_texture.h" +#include "scene/resources/image_texture.h" #include "scene/resources/particle_process_material.h" +#include "scene/scene_string_names.h" AABB CPUParticles3D::get_aabb() const { return AABB(); @@ -46,6 +50,7 @@ void CPUParticles3D::set_emitting(bool p_emitting) { emitting = p_emitting; if (emitting) { + active = true; set_process_internal(true); // first update before rendering to avoid one frame delay after emitting starts @@ -220,7 +225,6 @@ PackedStringArray CPUParticles3D::get_configuration_warnings() const { void CPUParticles3D::restart() { time = 0; - inactive_time = 0; frame_remainder = 0; cycle = 0; emitting = false; @@ -575,21 +579,15 @@ void CPUParticles3D::_update_internal() { } double delta = get_process_delta_time(); - if (emitting) { - inactive_time = 0; - } else { - inactive_time += delta; - if (inactive_time > lifetime * 1.2) { - set_process_internal(false); - _set_redraw(false); + if (!active && !emitting) { + set_process_internal(false); + _set_redraw(false); - //reset variables - time = 0; - inactive_time = 0; - frame_remainder = 0; - cycle = 0; - return; - } + //reset variables + time = 0; + frame_remainder = 0; + cycle = 0; + return; } _set_redraw(true); @@ -670,6 +668,7 @@ void CPUParticles3D::_particles_process(double p_delta) { double system_phase = time / lifetime; + bool should_be_active = false; for (int i = 0; i < pcount; i++) { Particle &p = parray[i]; @@ -1136,6 +1135,12 @@ void CPUParticles3D::_particles_process(double p_delta) { } p.transform.origin += p.velocity * local_delta; + + should_be_active = true; + } + if (!Math::is_equal_approx(time, 0.0) && active && !should_be_active) { + active = false; + emit_signal(SceneStringNames::get_singleton()->finished); } } @@ -1543,6 +1548,8 @@ void CPUParticles3D::_bind_methods() { ClassDB::bind_method(D_METHOD("convert_from_particles", "particles"), &CPUParticles3D::convert_from_particles); + ADD_SIGNAL(MethodInfo("finished")); + ADD_GROUP("Emission Shape", "emission_"); ADD_PROPERTY(PropertyInfo(Variant::INT, "emission_shape", PROPERTY_HINT_ENUM, "Point,Sphere,Sphere Surface,Box,Points,Directed Points,Ring", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), "set_emission_shape", "get_emission_shape"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_sphere_radius", PROPERTY_HINT_RANGE, "0.01,128,0.01"), "set_emission_sphere_radius", "get_emission_sphere_radius"); diff --git a/scene/3d/cpu_particles_3d.h b/scene/3d/cpu_particles_3d.h index 40ea4e8cdf..a5bc7dddb9 100644 --- a/scene/3d/cpu_particles_3d.h +++ b/scene/3d/cpu_particles_3d.h @@ -81,6 +81,7 @@ public: private: bool emitting = false; + bool active = false; struct Particle { Transform3D transform; @@ -101,7 +102,6 @@ private: }; double time = 0.0; - double inactive_time = 0.0; double frame_remainder = 0.0; int cycle = 0; bool redraw = false; diff --git a/scene/3d/gpu_particles_3d.cpp b/scene/3d/gpu_particles_3d.cpp index 4d0bc8b02f..3a23cbcff1 100644 --- a/scene/3d/gpu_particles_3d.cpp +++ b/scene/3d/gpu_particles_3d.cpp @@ -31,19 +31,37 @@ #include "gpu_particles_3d.h" #include "scene/resources/particle_process_material.h" +#include "scene/scene_string_names.h" AABB GPUParticles3D::get_aabb() const { return AABB(); } void GPUParticles3D::set_emitting(bool p_emitting) { - RS::get_singleton()->particles_set_emitting(particles, p_emitting); + // Do not return even if `p_emitting == emitting` because `emitting` is just an approximation. if (p_emitting && one_shot) { + if (!active && !emitting) { + // Last cycle ended. + active = true; + time = 0; + signal_cancled = false; + emission_time = lifetime; + active_time = lifetime * (2 - explosiveness_ratio); + } else { + signal_cancled = true; + } set_process_internal(true); } else if (!p_emitting) { - set_process_internal(false); + if (one_shot) { + set_process_internal(true); + } else { + set_process_internal(false); + } } + + emitting = p_emitting; + RS::get_singleton()->particles_set_emitting(particles, p_emitting); } void GPUParticles3D::set_amount(int p_amount) { @@ -122,7 +140,7 @@ void GPUParticles3D::set_collision_base_size(real_t p_size) { } bool GPUParticles3D::is_emitting() const { - return RS::get_singleton()->particles_get_emitting(particles); + return emitting; } int GPUParticles3D::get_amount() const { @@ -216,13 +234,13 @@ void GPUParticles3D::set_draw_pass_mesh(int p_pass, const Ref<Mesh> &p_mesh) { ERR_FAIL_INDEX(p_pass, draw_passes.size()); if (Engine::get_singleton()->is_editor_hint() && draw_passes.write[p_pass].is_valid()) { - draw_passes.write[p_pass]->disconnect("changed", callable_mp((Node *)this, &Node::update_configuration_warnings)); + draw_passes.write[p_pass]->disconnect_changed(callable_mp((Node *)this, &Node::update_configuration_warnings)); } draw_passes.write[p_pass] = p_mesh; if (Engine::get_singleton()->is_editor_hint() && draw_passes.write[p_pass].is_valid()) { - draw_passes.write[p_pass]->connect("changed", callable_mp((Node *)this, &Node::update_configuration_warnings), CONNECT_DEFERRED); + draw_passes.write[p_pass]->connect_changed(callable_mp((Node *)this, &Node::update_configuration_warnings), CONNECT_DEFERRED); } RID mesh_rid; @@ -373,6 +391,16 @@ PackedStringArray GPUParticles3D::get_configuration_warnings() const { void GPUParticles3D::restart() { RenderingServer::get_singleton()->particles_restart(particles); RenderingServer::get_singleton()->particles_set_emitting(particles, true); + + emitting = true; + active = true; + signal_cancled = false; + time = 0; + emission_time = lifetime * (1 - explosiveness_ratio); + active_time = lifetime * (2 - explosiveness_ratio); + if (one_shot) { + set_process_internal(true); + } } AABB GPUParticles3D::capture_aabb() const { @@ -425,9 +453,23 @@ void GPUParticles3D::_notification(int p_what) { // Use internal process when emitting and one_shot is on so that when // the shot ends the editor can properly update. case NOTIFICATION_INTERNAL_PROCESS: { - if (one_shot && !is_emitting()) { - notify_property_list_changed(); - set_process_internal(false); + if (one_shot) { + time += get_process_delta_time(); + if (time > emission_time) { + emitting = false; + if (!active) { + set_process_internal(false); + } + } + if (time > active_time) { + if (active && !signal_cancled) { + emit_signal(SceneStringNames::get_singleton()->finished); + } + active = false; + if (!emitting) { + set_process_internal(false); + } + } } } break; @@ -571,6 +613,8 @@ void GPUParticles3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_transform_align", "align"), &GPUParticles3D::set_transform_align); ClassDB::bind_method(D_METHOD("get_transform_align"), &GPUParticles3D::get_transform_align); + ADD_SIGNAL(MethodInfo("finished")); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "emitting"), "set_emitting", "is_emitting"); ADD_PROPERTY_DEFAULT("emitting", true); // Workaround for doctool in headless mode, as dummy rasterizer always returns false. ADD_PROPERTY(PropertyInfo(Variant::INT, "amount", PROPERTY_HINT_RANGE, "1,1000000,1,exp"), "set_amount", "get_amount"); diff --git a/scene/3d/gpu_particles_3d.h b/scene/3d/gpu_particles_3d.h index 474f5500f8..dba6a8f2ab 100644 --- a/scene/3d/gpu_particles_3d.h +++ b/scene/3d/gpu_particles_3d.h @@ -60,7 +60,10 @@ public: private: RID particles; - bool one_shot; + bool emitting = false; + bool active = false; + bool signal_cancled = false; + bool one_shot = false; int amount = 0; double lifetime = 0.0; double pre_process_time = 0.0; @@ -87,6 +90,10 @@ private: Vector<Ref<Mesh>> draw_passes; Ref<Skin> skin; + double time = 0.0; + double emission_time = 0.0; + double active_time = 0.0; + void _attach_sub_emitter(); void _skinning_changed(); diff --git a/scene/3d/label_3d.cpp b/scene/3d/label_3d.cpp index 810c0c5326..b0e7c73253 100644 --- a/scene/3d/label_3d.cpp +++ b/scene/3d/label_3d.cpp @@ -30,7 +30,6 @@ #include "label_3d.h" -#include "core/core_string_names.h" #include "scene/main/viewport.h" #include "scene/resources/theme.h" #include "scene/scene_string_names.h" @@ -126,10 +125,6 @@ void Label3D::_bind_methods() { ClassDB::bind_method(D_METHOD("generate_triangle_mesh"), &Label3D::generate_triangle_mesh); - ClassDB::bind_method(D_METHOD("_queue_update"), &Label3D::_queue_update); - ClassDB::bind_method(D_METHOD("_font_changed"), &Label3D::_font_changed); - ClassDB::bind_method(D_METHOD("_im_update"), &Label3D::_im_update); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "pixel_size", PROPERTY_HINT_RANGE, "0.0001,128,0.0001,suffix:m"), "set_pixel_size", "get_pixel_size"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "offset", PROPERTY_HINT_NONE, "suffix:px"), "set_offset", "get_offset"); @@ -239,7 +234,7 @@ void Label3D::_queue_update() { } pending_update = true; - call_deferred(SceneStringNames::get_singleton()->_im_update); + callable_mp(this, &Label3D::_im_update).call_deferred(); } AABB Label3D::get_aabb() const { @@ -766,12 +761,12 @@ void Label3D::_font_changed() { void Label3D::set_font(const Ref<Font> &p_font) { if (font_override != p_font) { if (font_override.is_valid()) { - font_override->disconnect(CoreStringNames::get_singleton()->changed, Callable(this, "_font_changed")); + font_override->disconnect_changed(callable_mp(this, &Label3D::_font_changed)); } font_override = p_font; dirty_font = true; if (font_override.is_valid()) { - font_override->connect(CoreStringNames::get_singleton()->changed, Callable(this, "_font_changed")); + font_override->connect_changed(callable_mp(this, &Label3D::_font_changed)); } _queue_update(); } @@ -783,7 +778,7 @@ Ref<Font> Label3D::get_font() const { Ref<Font> Label3D::_get_font_or_default() const { if (theme_font.is_valid()) { - theme_font->disconnect(CoreStringNames::get_singleton()->changed, Callable(const_cast<Label3D *>(this), "_font_changed")); + theme_font->disconnect_changed(callable_mp(const_cast<Label3D *>(this), &Label3D::_font_changed)); theme_font.unref(); } @@ -801,7 +796,7 @@ Ref<Font> Label3D::_get_font_or_default() const { Ref<Font> f = ThemeDB::get_singleton()->get_project_theme()->get_theme_item(Theme::DATA_TYPE_FONT, "font", E); if (f.is_valid()) { theme_font = f; - theme_font->connect(CoreStringNames::get_singleton()->changed, Callable(const_cast<Label3D *>(this), "_font_changed")); + theme_font->connect_changed(callable_mp(const_cast<Label3D *>(this), &Label3D::_font_changed)); } return f; } @@ -818,7 +813,7 @@ Ref<Font> Label3D::_get_font_or_default() const { Ref<Font> f = ThemeDB::get_singleton()->get_default_theme()->get_theme_item(Theme::DATA_TYPE_FONT, "font", E); if (f.is_valid()) { theme_font = f; - theme_font->connect(CoreStringNames::get_singleton()->changed, Callable(const_cast<Label3D *>(this), "_font_changed")); + theme_font->connect_changed(callable_mp(const_cast<Label3D *>(this), &Label3D::_font_changed)); } return f; } @@ -829,7 +824,7 @@ Ref<Font> Label3D::_get_font_or_default() const { Ref<Font> f = ThemeDB::get_singleton()->get_default_theme()->get_theme_item(Theme::DATA_TYPE_FONT, "font", StringName()); if (f.is_valid()) { theme_font = f; - theme_font->connect(CoreStringNames::get_singleton()->changed, Callable(const_cast<Label3D *>(this), "_font_changed")); + theme_font->connect_changed(callable_mp(const_cast<Label3D *>(this), &Label3D::_font_changed)); } return f; } diff --git a/scene/3d/lightmap_gi.cpp b/scene/3d/lightmap_gi.cpp index 3ee08fd548..b4df06a83e 100644 --- a/scene/3d/lightmap_gi.cpp +++ b/scene/3d/lightmap_gi.cpp @@ -37,6 +37,7 @@ #include "scene/3d/mesh_instance_3d.h" #include "scene/resources/camera_attributes.h" #include "scene/resources/environment.h" +#include "scene/resources/image_texture.h" #include "scene/resources/sky.h" void LightmapGIData::add_user(const NodePath &p_path, const Rect2 &p_uv_scale, int p_slice_index, int32_t p_sub_instance) { diff --git a/scene/3d/mesh_instance_3d.cpp b/scene/3d/mesh_instance_3d.cpp index 28a3cd0b13..0b0b098f65 100644 --- a/scene/3d/mesh_instance_3d.cpp +++ b/scene/3d/mesh_instance_3d.cpp @@ -31,7 +31,6 @@ #include "mesh_instance_3d.h" #include "collision_shape_3d.h" -#include "core/core_string_names.h" #include "physics_body_3d.h" #include "scene/resources/concave_polygon_shape_3d.h" #include "scene/resources/convex_polygon_shape_3d.h" @@ -111,7 +110,7 @@ void MeshInstance3D::set_mesh(const Ref<Mesh> &p_mesh) { } if (mesh.is_valid()) { - mesh->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(this, &MeshInstance3D::_mesh_changed)); + mesh->disconnect_changed(callable_mp(this, &MeshInstance3D::_mesh_changed)); } mesh = p_mesh; @@ -120,7 +119,7 @@ void MeshInstance3D::set_mesh(const Ref<Mesh> &p_mesh) { // If mesh is a PrimitiveMesh, calling get_rid on it can trigger a changed callback // so do this before connecting _mesh_changed. set_base(mesh->get_rid()); - mesh->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &MeshInstance3D::_mesh_changed)); + mesh->connect_changed(callable_mp(this, &MeshInstance3D::_mesh_changed)); _mesh_changed(); } else { blend_shape_tracks.clear(); diff --git a/scene/3d/navigation_link_3d.cpp b/scene/3d/navigation_link_3d.cpp index 2263d38d6c..70416ca93b 100644 --- a/scene/3d/navigation_link_3d.cpp +++ b/scene/3d/navigation_link_3d.cpp @@ -291,15 +291,7 @@ void NavigationLink3D::set_enabled(bool p_enabled) { enabled = p_enabled; - if (!is_inside_tree()) { - return; - } - - if (enabled) { - NavigationServer3D::get_singleton()->link_set_map(link, get_world_3d()->get_navigation_map()); - } else { - NavigationServer3D::get_singleton()->link_set_map(link, RID()); - } + NavigationServer3D::get_singleton()->link_set_enabled(link, enabled); #ifdef DEBUG_ENABLED if (debug_instance.is_valid() && debug_mesh.is_valid()) { diff --git a/scene/3d/navigation_region_3d.cpp b/scene/3d/navigation_region_3d.cpp index 194d3082df..8d66f7ebeb 100644 --- a/scene/3d/navigation_region_3d.cpp +++ b/scene/3d/navigation_region_3d.cpp @@ -30,7 +30,6 @@ #include "navigation_region_3d.h" -#include "core/core_string_names.h" #include "scene/resources/navigation_mesh_source_geometry_data_3d.h" #include "servers/navigation_server_3d.h" @@ -41,15 +40,7 @@ void NavigationRegion3D::set_enabled(bool p_enabled) { enabled = p_enabled; - if (!is_inside_tree()) { - return; - } - - if (!enabled) { - _region_enter_navigation_map(); - } else { - _region_exit_navigation_map(); - } + NavigationServer3D::get_singleton()->region_set_enabled(region, enabled); #ifdef DEBUG_ENABLED if (debug_instance.is_valid()) { @@ -193,13 +184,13 @@ void NavigationRegion3D::set_navigation_mesh(const Ref<NavigationMesh> &p_naviga } if (navigation_mesh.is_valid()) { - navigation_mesh->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(this, &NavigationRegion3D::_navigation_mesh_changed)); + navigation_mesh->disconnect_changed(callable_mp(this, &NavigationRegion3D::_navigation_mesh_changed)); } navigation_mesh = p_navigation_mesh; if (navigation_mesh.is_valid()) { - navigation_mesh->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &NavigationRegion3D::_navigation_mesh_changed)); + navigation_mesh->connect_changed(callable_mp(this, &NavigationRegion3D::_navigation_mesh_changed)); } NavigationServer3D::get_singleton()->region_set_navigation_mesh(region, p_navigation_mesh); @@ -262,11 +253,19 @@ void _bake_navigation_mesh(void *p_user_data) { Ref<NavigationMeshSourceGeometryData3D> source_geometry_data = args->source_geometry_data; NavigationServer3D::get_singleton()->bake_from_source_geometry_data(nav_mesh, source_geometry_data); - args->nav_region->call_deferred(SNAME("_bake_finished"), nav_mesh); + if (!Thread::is_main_thread()) { + args->nav_region->call_deferred(SNAME("_bake_finished"), nav_mesh); + } else { + args->nav_region->_bake_finished(nav_mesh); + } memdelete(args); } else { ERR_PRINT("Can't bake the navigation mesh if the `NavigationMesh` resource doesn't exist"); - args->nav_region->call_deferred(SNAME("_bake_finished"), Ref<NavigationMesh>()); + if (!Thread::is_main_thread()) { + args->nav_region->call_deferred(SNAME("_bake_finished"), Ref<NavigationMesh>()); + } else { + args->nav_region->_bake_finished(Ref<NavigationMesh>()); + } memdelete(args); } } @@ -462,7 +461,7 @@ NavigationRegion3D::~NavigationRegion3D() { } if (navigation_mesh.is_valid()) { - navigation_mesh->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(this, &NavigationRegion3D::_navigation_mesh_changed)); + navigation_mesh->disconnect_changed(callable_mp(this, &NavigationRegion3D::_navigation_mesh_changed)); } ERR_FAIL_NULL(NavigationServer3D::get_singleton()); NavigationServer3D::get_singleton()->free(region); diff --git a/scene/3d/occluder_instance_3d.cpp b/scene/3d/occluder_instance_3d.cpp index 8fd1df372b..7b535f6169 100644 --- a/scene/3d/occluder_instance_3d.cpp +++ b/scene/3d/occluder_instance_3d.cpp @@ -31,7 +31,6 @@ #include "occluder_instance_3d.h" #include "core/config/project_settings.h" -#include "core/core_string_names.h" #include "core/io/marshalls.h" #include "core/math/geometry_2d.h" #include "core/math/triangulate.h" @@ -441,14 +440,14 @@ void OccluderInstance3D::set_occluder(const Ref<Occluder3D> &p_occluder) { } if (occluder.is_valid()) { - occluder->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(this, &OccluderInstance3D::_occluder_changed)); + occluder->disconnect_changed(callable_mp(this, &OccluderInstance3D::_occluder_changed)); } occluder = p_occluder; if (occluder.is_valid()) { set_base(occluder->get_rid()); - occluder->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &OccluderInstance3D::_occluder_changed)); + occluder->connect_changed(callable_mp(this, &OccluderInstance3D::_occluder_changed)); } else { set_base(RID()); } diff --git a/scene/3d/path_3d.cpp b/scene/3d/path_3d.cpp index 9516973ae2..6aea063096 100644 --- a/scene/3d/path_3d.cpp +++ b/scene/3d/path_3d.cpp @@ -144,13 +144,13 @@ void Path3D::_curve_changed() { void Path3D::set_curve(const Ref<Curve3D> &p_curve) { if (curve.is_valid()) { - curve->disconnect("changed", callable_mp(this, &Path3D::_curve_changed)); + curve->disconnect_changed(callable_mp(this, &Path3D::_curve_changed)); } curve = p_curve; if (curve.is_valid()) { - curve->connect("changed", callable_mp(this, &Path3D::_curve_changed)); + curve->connect_changed(callable_mp(this, &Path3D::_curve_changed)); } _curve_changed(); } diff --git a/scene/3d/physics_body_3d.cpp b/scene/3d/physics_body_3d.cpp index 6b31aa0a3c..e49b43c650 100644 --- a/scene/3d/physics_body_3d.cpp +++ b/scene/3d/physics_body_3d.cpp @@ -30,7 +30,6 @@ #include "physics_body_3d.h" -#include "core/core_string_names.h" #include "scene/scene_string_names.h" void PhysicsBody3D::_bind_methods() { @@ -214,15 +213,13 @@ real_t PhysicsBody3D::get_inverse_mass() const { void StaticBody3D::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, &StaticBody3D::_reload_physics_characteristics))) { - physics_material_override->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(this, &StaticBody3D::_reload_physics_characteristics)); - } + physics_material_override->disconnect_changed(callable_mp(this, &StaticBody3D::_reload_physics_characteristics)); } physics_material_override = p_physics_material_override; if (physics_material_override.is_valid()) { - physics_material_override->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &StaticBody3D::_reload_physics_characteristics)); + physics_material_override->connect_changed(callable_mp(this, &StaticBody3D::_reload_physics_characteristics)); } _reload_physics_characteristics(); } @@ -726,15 +723,13 @@ const Vector3 &RigidBody3D::get_center_of_mass() const { 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))) { - physics_material_override->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(this, &RigidBody3D::_reload_physics_characteristics)); - } + physics_material_override->disconnect_changed(callable_mp(this, &RigidBody3D::_reload_physics_characteristics)); } physics_material_override = p_physics_material_override; if (physics_material_override.is_valid()) { - physics_material_override->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &RigidBody3D::_reload_physics_characteristics)); + physics_material_override->connect_changed(callable_mp(this, &RigidBody3D::_reload_physics_characteristics)); } _reload_physics_characteristics(); } diff --git a/scene/3d/ray_cast_3d.cpp b/scene/3d/ray_cast_3d.cpp index 2dea6418ed..7b04669dad 100644 --- a/scene/3d/ray_cast_3d.cpp +++ b/scene/3d/ray_cast_3d.cpp @@ -223,6 +223,7 @@ void RayCast3D::_update_raycast_state() { ray_params.collide_with_bodies = collide_with_bodies; ray_params.collide_with_areas = collide_with_areas; ray_params.hit_from_inside = hit_from_inside; + ray_params.hit_back_faces = hit_back_faces; PhysicsDirectSpaceState3D::RayResult rr; if (dss->intersect_ray(ray_params, rr)) { @@ -297,6 +298,14 @@ bool RayCast3D::is_hit_from_inside_enabled() const { return hit_from_inside; } +void RayCast3D::set_hit_back_faces(bool p_enabled) { + hit_back_faces = p_enabled; +} + +bool RayCast3D::is_hit_back_faces_enabled() const { + return hit_back_faces; +} + void RayCast3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_enabled", "enabled"), &RayCast3D::set_enabled); ClassDB::bind_method(D_METHOD("is_enabled"), &RayCast3D::is_enabled); @@ -339,6 +348,9 @@ void RayCast3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_hit_from_inside", "enable"), &RayCast3D::set_hit_from_inside); ClassDB::bind_method(D_METHOD("is_hit_from_inside_enabled"), &RayCast3D::is_hit_from_inside_enabled); + ClassDB::bind_method(D_METHOD("set_hit_back_faces", "enable"), &RayCast3D::set_hit_back_faces); + ClassDB::bind_method(D_METHOD("is_hit_back_faces_enabled"), &RayCast3D::is_hit_back_faces_enabled); + ClassDB::bind_method(D_METHOD("set_debug_shape_custom_color", "debug_shape_custom_color"), &RayCast3D::set_debug_shape_custom_color); ClassDB::bind_method(D_METHOD("get_debug_shape_custom_color"), &RayCast3D::get_debug_shape_custom_color); @@ -350,6 +362,7 @@ void RayCast3D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "target_position", PROPERTY_HINT_NONE, "suffix:m"), "set_target_position", "get_target_position"); ADD_PROPERTY(PropertyInfo(Variant::INT, "collision_mask", PROPERTY_HINT_LAYERS_3D_PHYSICS), "set_collision_mask", "get_collision_mask"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hit_from_inside"), "set_hit_from_inside", "is_hit_from_inside_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hit_back_faces"), "set_hit_back_faces", "is_hit_back_faces_enabled"); ADD_GROUP("Collide With", "collide_with"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "collide_with_areas", PROPERTY_HINT_LAYERS_3D_PHYSICS), "set_collide_with_areas", "is_collide_with_areas_enabled"); diff --git a/scene/3d/ray_cast_3d.h b/scene/3d/ray_cast_3d.h index e230d61628..1def7a0eca 100644 --- a/scene/3d/ray_cast_3d.h +++ b/scene/3d/ray_cast_3d.h @@ -69,6 +69,7 @@ class RayCast3D : public Node3D { bool collide_with_bodies = true; bool hit_from_inside = false; + bool hit_back_faces = true; protected: void _notification(int p_what); @@ -85,6 +86,9 @@ public: void set_hit_from_inside(bool p_enabled); bool is_hit_from_inside_enabled() const; + void set_hit_back_faces(bool p_enabled); + bool is_hit_back_faces_enabled() const; + void set_enabled(bool p_enabled); bool is_enabled() const; diff --git a/scene/3d/shape_cast_3d.cpp b/scene/3d/shape_cast_3d.cpp index 75f94b36d3..b6401832ed 100644 --- a/scene/3d/shape_cast_3d.cpp +++ b/scene/3d/shape_cast_3d.cpp @@ -30,7 +30,6 @@ #include "shape_cast_3d.h" -#include "core/core_string_names.h" #include "scene/3d/collision_object_3d.h" #include "scene/3d/mesh_instance_3d.h" #include "scene/resources/concave_polygon_shape_3d.h" @@ -93,7 +92,9 @@ void ShapeCast3D::_notification(int p_what) { } void ShapeCast3D::_bind_methods() { +#ifndef DISABLE_DEPRECATED ClassDB::bind_method(D_METHOD("resource_changed", "resource"), &ShapeCast3D::resource_changed); +#endif ClassDB::bind_method(D_METHOD("set_enabled", "enabled"), &ShapeCast3D::set_enabled); ClassDB::bind_method(D_METHOD("is_enabled"), &ShapeCast3D::is_enabled); @@ -312,12 +313,10 @@ real_t ShapeCast3D::get_closest_collision_unsafe_fraction() const { return collision_unsafe_fraction; } +#ifndef DISABLE_DEPRECATED void ShapeCast3D::resource_changed(Ref<Resource> p_res) { - if (is_inside_tree() && get_tree()->is_debugging_collisions_hint()) { - _update_debug_shape(); - } - update_gizmos(); } +#endif void ShapeCast3D::_shape_changed() { update_gizmos(); @@ -332,13 +331,11 @@ void ShapeCast3D::set_shape(const Ref<Shape3D> &p_shape) { return; } if (shape.is_valid()) { - shape->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(this, &ShapeCast3D::_shape_changed)); - shape->unregister_owner(this); + shape->disconnect_changed(callable_mp(this, &ShapeCast3D::_shape_changed)); } shape = p_shape; if (shape.is_valid()) { - shape->register_owner(this); - shape->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &ShapeCast3D::_shape_changed)); + shape->connect_changed(callable_mp(this, &ShapeCast3D::_shape_changed)); shape_rid = shape->get_rid(); } @@ -633,9 +630,3 @@ void ShapeCast3D::_clear_debug_shape() { debug_shape = nullptr; } - -ShapeCast3D::~ShapeCast3D() { - if (!shape.is_null()) { - shape->unregister_owner(this); - } -} diff --git a/scene/3d/shape_cast_3d.h b/scene/3d/shape_cast_3d.h index 98158d3c7c..043e35090f 100644 --- a/scene/3d/shape_cast_3d.h +++ b/scene/3d/shape_cast_3d.h @@ -40,7 +40,9 @@ class ShapeCast3D : public Node3D { GDCLASS(ShapeCast3D, Node3D); bool enabled = true; +#ifndef DISABLE_DEPRECATED void resource_changed(Ref<Resource> p_res); +#endif Ref<Shape3D> shape; RID shape_rid; @@ -74,8 +76,6 @@ class ShapeCast3D : public Node3D { Array _get_collision_result() const; - ~ShapeCast3D(); - protected: void _notification(int p_what); void _update_shapecast_state(); diff --git a/scene/3d/skeleton_3d.cpp b/scene/3d/skeleton_3d.cpp index 51359059ef..445c1003b5 100644 --- a/scene/3d/skeleton_3d.cpp +++ b/scene/3d/skeleton_3d.cpp @@ -887,7 +887,7 @@ Ref<SkinReference> Skeleton3D::register_skin(const Ref<Skin> &p_skin) { skin_bindings.insert(skin_ref.operator->()); - skin_ref->skin->connect("changed", callable_mp(skin_ref.operator->(), &SkinReference::_skin_changed)); + skin_ref->skin->connect_changed(callable_mp(skin_ref.operator->(), &SkinReference::_skin_changed)); _make_dirty(); // Skin needs to be updated, so update skeleton. diff --git a/scene/3d/sprite_3d.cpp b/scene/3d/sprite_3d.cpp index 6e44696fe4..1345cfcdb8 100644 --- a/scene/3d/sprite_3d.cpp +++ b/scene/3d/sprite_3d.cpp @@ -30,6 +30,7 @@ #include "sprite_3d.h" +#include "scene/resources/atlas_texture.h" #include "scene/scene_string_names.h" Color SpriteBase3D::_get_color_accum() { @@ -372,7 +373,7 @@ void SpriteBase3D::_queue_redraw() { update_gizmos(); pending_update = true; - call_deferred(SceneStringNames::get_singleton()->_im_update); + callable_mp(this, &SpriteBase3D::_im_update).call_deferred(); } AABB SpriteBase3D::get_aabb() const { @@ -577,8 +578,6 @@ void SpriteBase3D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_item_rect"), &SpriteBase3D::get_item_rect); ClassDB::bind_method(D_METHOD("generate_triangle_mesh"), &SpriteBase3D::generate_triangle_mesh); - ClassDB::bind_method(D_METHOD("_im_update"), &SpriteBase3D::_im_update); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "centered"), "set_centered", "is_centered"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "offset", PROPERTY_HINT_NONE, "suffix:px"), "set_offset", "get_offset"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "flip_h"), "set_flip_h", "is_flipped_h"); diff --git a/scene/animation/animation_blend_tree.cpp b/scene/animation/animation_blend_tree.cpp index 6a76b5ac16..715d8a5bc1 100644 --- a/scene/animation/animation_blend_tree.cpp +++ b/scene/animation/animation_blend_tree.cpp @@ -1010,6 +1010,7 @@ double AnimationNodeTransition::_process(double p_time, bool p_seek, bool p_is_e } double rem = 0.0; + double abs_time = Math::abs(p_time); if (sync) { for (int i = 0; i < get_input_count(); i++) { @@ -1024,9 +1025,9 @@ double AnimationNodeTransition::_process(double p_time, bool p_seek, bool p_is_e rem = blend_input(cur_current_index, p_time, p_seek, p_is_external_seeking, 1.0, FILTER_IGNORE, true, p_test_only); if (p_seek) { - cur_time = p_time; + cur_time = abs_time; } else { - cur_time += p_time; + cur_time += abs_time; } if (input_data[cur_current_index].auto_advance && rem <= xfade_time) { @@ -1058,10 +1059,10 @@ double AnimationNodeTransition::_process(double p_time, bool p_seek, bool p_is_e blend_input(cur_prev_index, p_time, use_blend && p_seek, p_is_external_seeking, blend, FILTER_IGNORE, true, p_test_only); if (p_seek) { - cur_time = p_time; + cur_time = abs_time; } else { - cur_time += p_time; - cur_prev_xfading -= p_time; + cur_time += abs_time; + cur_prev_xfading -= abs_time; if (cur_prev_xfading < 0) { set_parameter(prev_index, -1); } @@ -1142,7 +1143,7 @@ void AnimationNodeBlendTree::add_node(const StringName &p_name, Ref<AnimationNod p_node->connect("tree_changed", callable_mp(this, &AnimationNodeBlendTree::_tree_changed), CONNECT_REFERENCE_COUNTED); p_node->connect("animation_node_renamed", callable_mp(this, &AnimationNodeBlendTree::_animation_node_renamed), CONNECT_REFERENCE_COUNTED); p_node->connect("animation_node_removed", callable_mp(this, &AnimationNodeBlendTree::_animation_node_removed), CONNECT_REFERENCE_COUNTED); - p_node->connect("changed", callable_mp(this, &AnimationNodeBlendTree::_node_changed).bind(p_name), CONNECT_REFERENCE_COUNTED); + p_node->connect_changed(callable_mp(this, &AnimationNodeBlendTree::_node_changed).bind(p_name), CONNECT_REFERENCE_COUNTED); } Ref<AnimationNode> AnimationNodeBlendTree::get_node(const StringName &p_name) const { @@ -1178,8 +1179,6 @@ void AnimationNodeBlendTree::get_child_nodes(List<ChildNode> *r_child_nodes) { ns.push_back(E.key); } - ns.sort_custom<StringName::AlphCompare>(); - for (int i = 0; i < ns.size(); i++) { ChildNode cn; cn.name = ns[i]; @@ -1206,7 +1205,7 @@ void AnimationNodeBlendTree::remove_node(const StringName &p_name) { node->disconnect("tree_changed", callable_mp(this, &AnimationNodeBlendTree::_tree_changed)); node->disconnect("animation_node_renamed", callable_mp(this, &AnimationNodeBlendTree::_animation_node_renamed)); node->disconnect("animation_node_removed", callable_mp(this, &AnimationNodeBlendTree::_animation_node_removed)); - node->disconnect("changed", callable_mp(this, &AnimationNodeBlendTree::_node_changed)); + node->disconnect_changed(callable_mp(this, &AnimationNodeBlendTree::_node_changed)); } nodes.erase(p_name); @@ -1231,7 +1230,7 @@ void AnimationNodeBlendTree::rename_node(const StringName &p_name, const StringN ERR_FAIL_COND(p_name == SceneStringNames::get_singleton()->output); ERR_FAIL_COND(p_new_name == SceneStringNames::get_singleton()->output); - nodes[p_name].node->disconnect("changed", callable_mp(this, &AnimationNodeBlendTree::_node_changed)); + nodes[p_name].node->disconnect_changed(callable_mp(this, &AnimationNodeBlendTree::_node_changed)); nodes[p_new_name] = nodes[p_name]; nodes.erase(p_name); @@ -1245,7 +1244,7 @@ void AnimationNodeBlendTree::rename_node(const StringName &p_name, const StringN } } // Connection must be done with new name. - nodes[p_new_name].node->connect("changed", callable_mp(this, &AnimationNodeBlendTree::_node_changed).bind(p_new_name), CONNECT_REFERENCE_COUNTED); + nodes[p_new_name].node->connect_changed(callable_mp(this, &AnimationNodeBlendTree::_node_changed).bind(p_new_name), CONNECT_REFERENCE_COUNTED); emit_signal(SNAME("animation_node_renamed"), get_instance_id(), p_name, p_new_name); emit_signal(SNAME("tree_changed")); @@ -1435,7 +1434,6 @@ void AnimationNodeBlendTree::_get_property_list(List<PropertyInfo> *p_list) cons for (const KeyValue<StringName, Node> &E : nodes) { names.push_back(E.key); } - names.sort_custom<StringName::AlphCompare>(); for (const StringName &E : names) { String prop_name = E; diff --git a/scene/animation/animation_blend_tree.h b/scene/animation/animation_blend_tree.h index 2139af8a96..bf95c211f6 100644 --- a/scene/animation/animation_blend_tree.h +++ b/scene/animation/animation_blend_tree.h @@ -388,7 +388,7 @@ class AnimationNodeBlendTree : public AnimationRootNode { Vector<StringName> connections; }; - HashMap<StringName, Node> nodes; + RBMap<StringName, Node, StringName::AlphCompare> nodes; Vector2 graph_offset; diff --git a/scene/animation/tween.cpp b/scene/animation/tween.cpp index 96e5da5a40..b32b04655d 100644 --- a/scene/animation/tween.cpp +++ b/scene/animation/tween.cpp @@ -284,10 +284,6 @@ bool Tween::step(double p_delta) { return false; } - if (!running) { - return true; - } - if (is_bound) { Node *node = get_bound_node(); if (node) { @@ -299,6 +295,10 @@ bool Tween::step(double p_delta) { } } + if (!running) { + return true; + } + if (!started) { if (tweeners.is_empty()) { String tween_id; diff --git a/scene/debugger/scene_debugger.cpp b/scene/debugger/scene_debugger.cpp index 391388fc04..4b097412d6 100644 --- a/scene/debugger/scene_debugger.cpp +++ b/scene/debugger/scene_debugger.cpp @@ -163,6 +163,7 @@ Error SceneDebugger::parse_message(void *p_user, const String &p_msg, const Arra live_editor->_res_set_func(p_args[0], p_args[1], p_args[2]); } else if (p_msg == "live_node_call") { + ERR_FAIL_COND_V(p_args.size() < 2, ERR_INVALID_DATA); LocalVector<Variant> args; LocalVector<Variant *> argptrs; args.resize(p_args.size() - 2); @@ -171,11 +172,10 @@ Error SceneDebugger::parse_message(void *p_user, const String &p_msg, const Arra args[i] = p_args[i + 2]; argptrs[i] = &args[i]; } - live_editor->_node_call_func(p_args[0], p_args[1], (const Variant **)argptrs.ptr(), argptrs.size()); + live_editor->_node_call_func(p_args[0], p_args[1], argptrs.size() ? (const Variant **)argptrs.ptr() : nullptr, argptrs.size()); } else if (p_msg == "live_res_call") { - ERR_FAIL_COND_V(p_args.size() < 10, ERR_INVALID_DATA); - + ERR_FAIL_COND_V(p_args.size() < 2, ERR_INVALID_DATA); LocalVector<Variant> args; LocalVector<Variant *> argptrs; args.resize(p_args.size() - 2); @@ -184,7 +184,7 @@ Error SceneDebugger::parse_message(void *p_user, const String &p_msg, const Arra args[i] = p_args[i + 2]; argptrs[i] = &args[i]; } - live_editor->_res_call_func(p_args[0], p_args[1], (const Variant **)argptrs.ptr(), argptrs.size()); + live_editor->_res_call_func(p_args[0], p_args[1], argptrs.size() ? (const Variant **)argptrs.ptr() : nullptr, argptrs.size()); } else if (p_msg == "live_create_node") { ERR_FAIL_COND_V(p_args.size() < 3, ERR_INVALID_DATA); diff --git a/scene/gui/button.cpp b/scene/gui/button.cpp index 5804d3250d..430569432a 100644 --- a/scene/gui/button.cpp +++ b/scene/gui/button.cpp @@ -30,7 +30,6 @@ #include "button.h" -#include "core/core_string_names.h" #include "core/string/translation.h" #include "servers/rendering_server.h" @@ -327,11 +326,8 @@ void Button::_notification(int p_what) { if (align_rtl_checked == HORIZONTAL_ALIGNMENT_CENTER && icon_align_rtl_checked == HORIZONTAL_ALIGNMENT_CENTER) { icon_ofs.x = 0.0; } - int text_clip = size.width - style->get_minimum_size().width - icon_ofs.width; - text_buf->set_width((clip_text || overrun_behavior != TextServer::OVERRUN_NO_TRIMMING) ? text_clip : -1); - - int text_width = MAX(1, (clip_text || overrun_behavior != TextServer::OVERRUN_NO_TRIMMING) ? MIN(text_clip, text_buf->get_size().x) : text_buf->get_size().x); + int text_clip = size.width - style->get_minimum_size().width - icon_ofs.width; if (_internal_margin[SIDE_LEFT] > 0) { text_clip -= _internal_margin[SIDE_LEFT] + theme_cache.h_separation; } @@ -339,6 +335,10 @@ void Button::_notification(int p_what) { text_clip -= _internal_margin[SIDE_RIGHT] + theme_cache.h_separation; } + text_buf->set_width((clip_text || overrun_behavior != TextServer::OVERRUN_NO_TRIMMING) ? text_clip : -1); + + int text_width = MAX(1, (clip_text || overrun_behavior != TextServer::OVERRUN_NO_TRIMMING) ? MIN(text_clip, text_buf->get_size().x) : text_buf->get_size().x); + Point2 text_ofs = (size - style->get_minimum_size() - icon_ofs - text_buf->get_size() - Point2(_internal_margin[SIDE_RIGHT] - _internal_margin[SIDE_LEFT], 0)) / 2.0; if (vertical_icon_alignment == VERTICAL_ALIGNMENT_TOP) { @@ -539,13 +539,13 @@ void Button::set_icon(const Ref<Texture2D> &p_icon) { } if (icon.is_valid()) { - icon->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(this, &Button::_texture_changed)); + icon->disconnect_changed(callable_mp(this, &Button::_texture_changed)); } icon = p_icon; if (icon.is_valid()) { - icon->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &Button::_texture_changed)); + icon->connect_changed(callable_mp(this, &Button::_texture_changed)); } queue_redraw(); diff --git a/scene/gui/code_edit.cpp b/scene/gui/code_edit.cpp index 1944b8db98..eee59606e3 100644 --- a/scene/gui/code_edit.cpp +++ b/scene/gui/code_edit.cpp @@ -2258,9 +2258,8 @@ bool CodeEdit::is_symbol_lookup_on_click_enabled() const { return symbol_lookup_on_click_enabled; } -String CodeEdit::get_text_for_symbol_lookup() { +String CodeEdit::get_text_for_symbol_lookup() const { Point2i mp = get_local_mouse_pos(); - Point2i pos = get_line_column_at_pos(mp, false); int line = pos.y; int col = pos.x; @@ -2269,25 +2268,29 @@ String CodeEdit::get_text_for_symbol_lookup() { return String(); } - StringBuilder lookup_text; + return get_text_with_cursor_char(line, col); +} + +String CodeEdit::get_text_with_cursor_char(int p_line, int p_column) const { const int text_size = get_line_count(); + StringBuilder result; for (int i = 0; i < text_size; i++) { String line_text = get_line(i); - - if (i == line) { - lookup_text += line_text.substr(0, col); + if (i == p_line && p_column >= 0 && p_column <= line_text.size()) { + result += line_text.substr(0, p_column); /* Not unicode, represents the cursor. */ - lookup_text += String::chr(0xFFFF); - lookup_text += line_text.substr(col, line_text.size()); + result += String::chr(0xFFFF); + result += line_text.substr(p_column, line_text.size()); } else { - lookup_text += line_text; + result += line_text; } if (i != text_size - 1) { - lookup_text += "\n"; + result += "\n"; } } - return lookup_text.as_string(); + + return result.as_string(); } void CodeEdit::set_symbol_lookup_word_as_valid(bool p_valid) { @@ -2472,6 +2475,7 @@ void CodeEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("is_symbol_lookup_on_click_enabled"), &CodeEdit::is_symbol_lookup_on_click_enabled); ClassDB::bind_method(D_METHOD("get_text_for_symbol_lookup"), &CodeEdit::get_text_for_symbol_lookup); + ClassDB::bind_method(D_METHOD("get_text_with_cursor_char", "line", "column"), &CodeEdit::get_text_with_cursor_char); ClassDB::bind_method(D_METHOD("set_symbol_lookup_word_as_valid", "valid"), &CodeEdit::set_symbol_lookup_word_as_valid); diff --git a/scene/gui/code_edit.h b/scene/gui/code_edit.h index 6933eb9392..a3c968da60 100644 --- a/scene/gui/code_edit.h +++ b/scene/gui/code_edit.h @@ -455,7 +455,8 @@ public: void set_symbol_lookup_on_click_enabled(bool p_enabled); bool is_symbol_lookup_on_click_enabled() const; - String get_text_for_symbol_lookup(); + String get_text_for_symbol_lookup() const; + String get_text_with_cursor_char(int p_line, int p_column) const; void set_symbol_lookup_word_as_valid(bool p_valid); diff --git a/scene/gui/color_picker.cpp b/scene/gui/color_picker.cpp index 1493e4a79f..2a0f85a1be 100644 --- a/scene/gui/color_picker.cpp +++ b/scene/gui/color_picker.cpp @@ -36,6 +36,9 @@ #include "core/os/keyboard.h" #include "core/os/os.h" #include "scene/gui/color_mode.h" +#include "scene/resources/image_texture.h" +#include "scene/resources/style_box_flat.h" +#include "scene/resources/style_box_texture.h" #include "servers/display_server.h" #include "thirdparty/misc/ok_color.h" #include "thirdparty/misc/ok_color_shader.h" @@ -556,7 +559,7 @@ void ColorPicker::_html_submitted(const String &p_html) { } const Color previous_color = color; - color = Color::from_string(p_html, previous_color); + color = Color::from_string(p_html.strip_edges(), previous_color); if (!is_editing_alpha()) { color.a = previous_color.a; diff --git a/scene/gui/color_picker.h b/scene/gui/color_picker.h index 03e47cb5c3..1a4b1b6ee1 100644 --- a/scene/gui/color_picker.h +++ b/scene/gui/color_picker.h @@ -46,6 +46,7 @@ #include "scene/gui/slider.h" #include "scene/gui/spin_box.h" #include "scene/gui/texture_rect.h" +#include "scene/resources/style_box_flat.h" class ColorMode; class ColorModeRGB; diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp index 78862364d5..0ca04faf9b 100644 --- a/scene/gui/control.cpp +++ b/scene/gui/control.cpp @@ -271,21 +271,21 @@ bool Control::_set(const StringName &p_name, const Variant &p_value) { if (name.begins_with("theme_override_icons/")) { String dname = name.get_slicec('/', 1); if (data.theme_icon_override.has(dname)) { - data.theme_icon_override[dname]->disconnect("changed", callable_mp(this, &Control::_notify_theme_override_changed)); + data.theme_icon_override[dname]->disconnect_changed(callable_mp(this, &Control::_notify_theme_override_changed)); } data.theme_icon_override.erase(dname); _notify_theme_override_changed(); } else if (name.begins_with("theme_override_styles/")) { String dname = name.get_slicec('/', 1); if (data.theme_style_override.has(dname)) { - data.theme_style_override[dname]->disconnect("changed", callable_mp(this, &Control::_notify_theme_override_changed)); + data.theme_style_override[dname]->disconnect_changed(callable_mp(this, &Control::_notify_theme_override_changed)); } data.theme_style_override.erase(dname); _notify_theme_override_changed(); } else if (name.begins_with("theme_override_fonts/")) { String dname = name.get_slicec('/', 1); if (data.theme_font_override.has(dname)) { - data.theme_font_override[dname]->disconnect("changed", callable_mp(this, &Control::_notify_theme_override_changed)); + data.theme_font_override[dname]->disconnect_changed(callable_mp(this, &Control::_notify_theme_override_changed)); } data.theme_font_override.erase(dname); _notify_theme_override_changed(); @@ -2468,13 +2468,13 @@ void Control::set_theme(const Ref<Theme> &p_theme) { } if (data.theme.is_valid()) { - data.theme->disconnect("changed", callable_mp(this, &Control::_theme_changed)); + data.theme->disconnect_changed(callable_mp(this, &Control::_theme_changed)); } data.theme = p_theme; if (data.theme.is_valid()) { data.theme_owner->propagate_theme_changed(this, this, is_inside_tree(), true); - data.theme->connect("changed", callable_mp(this, &Control::_theme_changed), CONNECT_DEFERRED); + data.theme->connect_changed(callable_mp(this, &Control::_theme_changed), CONNECT_DEFERRED); return; } @@ -2769,11 +2769,11 @@ void Control::add_theme_icon_override(const StringName &p_name, const Ref<Textur ERR_FAIL_COND(!p_icon.is_valid()); if (data.theme_icon_override.has(p_name)) { - data.theme_icon_override[p_name]->disconnect("changed", callable_mp(this, &Control::_notify_theme_override_changed)); + data.theme_icon_override[p_name]->disconnect_changed(callable_mp(this, &Control::_notify_theme_override_changed)); } data.theme_icon_override[p_name] = p_icon; - data.theme_icon_override[p_name]->connect("changed", callable_mp(this, &Control::_notify_theme_override_changed), CONNECT_REFERENCE_COUNTED); + data.theme_icon_override[p_name]->connect_changed(callable_mp(this, &Control::_notify_theme_override_changed), CONNECT_REFERENCE_COUNTED); _notify_theme_override_changed(); } @@ -2782,11 +2782,11 @@ void Control::add_theme_style_override(const StringName &p_name, const Ref<Style ERR_FAIL_COND(!p_style.is_valid()); if (data.theme_style_override.has(p_name)) { - data.theme_style_override[p_name]->disconnect("changed", callable_mp(this, &Control::_notify_theme_override_changed)); + data.theme_style_override[p_name]->disconnect_changed(callable_mp(this, &Control::_notify_theme_override_changed)); } data.theme_style_override[p_name] = p_style; - data.theme_style_override[p_name]->connect("changed", callable_mp(this, &Control::_notify_theme_override_changed), CONNECT_REFERENCE_COUNTED); + data.theme_style_override[p_name]->connect_changed(callable_mp(this, &Control::_notify_theme_override_changed), CONNECT_REFERENCE_COUNTED); _notify_theme_override_changed(); } @@ -2795,11 +2795,11 @@ void Control::add_theme_font_override(const StringName &p_name, const Ref<Font> ERR_FAIL_COND(!p_font.is_valid()); if (data.theme_font_override.has(p_name)) { - data.theme_font_override[p_name]->disconnect("changed", callable_mp(this, &Control::_notify_theme_override_changed)); + data.theme_font_override[p_name]->disconnect_changed(callable_mp(this, &Control::_notify_theme_override_changed)); } data.theme_font_override[p_name] = p_font; - data.theme_font_override[p_name]->connect("changed", callable_mp(this, &Control::_notify_theme_override_changed), CONNECT_REFERENCE_COUNTED); + data.theme_font_override[p_name]->connect_changed(callable_mp(this, &Control::_notify_theme_override_changed), CONNECT_REFERENCE_COUNTED); _notify_theme_override_changed(); } @@ -2824,7 +2824,7 @@ void Control::add_theme_constant_override(const StringName &p_name, int p_consta void Control::remove_theme_icon_override(const StringName &p_name) { ERR_MAIN_THREAD_GUARD; if (data.theme_icon_override.has(p_name)) { - data.theme_icon_override[p_name]->disconnect("changed", callable_mp(this, &Control::_notify_theme_override_changed)); + data.theme_icon_override[p_name]->disconnect_changed(callable_mp(this, &Control::_notify_theme_override_changed)); } data.theme_icon_override.erase(p_name); @@ -2834,7 +2834,7 @@ void Control::remove_theme_icon_override(const StringName &p_name) { void Control::remove_theme_style_override(const StringName &p_name) { ERR_MAIN_THREAD_GUARD; if (data.theme_style_override.has(p_name)) { - data.theme_style_override[p_name]->disconnect("changed", callable_mp(this, &Control::_notify_theme_override_changed)); + data.theme_style_override[p_name]->disconnect_changed(callable_mp(this, &Control::_notify_theme_override_changed)); } data.theme_style_override.erase(p_name); @@ -2844,7 +2844,7 @@ void Control::remove_theme_style_override(const StringName &p_name) { void Control::remove_theme_font_override(const StringName &p_name) { ERR_MAIN_THREAD_GUARD; if (data.theme_font_override.has(p_name)) { - data.theme_font_override[p_name]->disconnect("changed", callable_mp(this, &Control::_notify_theme_override_changed)); + data.theme_font_override[p_name]->disconnect_changed(callable_mp(this, &Control::_notify_theme_override_changed)); } data.theme_font_override.erase(p_name); @@ -3630,13 +3630,13 @@ Control::~Control() { // Resources need to be disconnected. for (KeyValue<StringName, Ref<Texture2D>> &E : data.theme_icon_override) { - E.value->disconnect("changed", callable_mp(this, &Control::_notify_theme_override_changed)); + E.value->disconnect_changed(callable_mp(this, &Control::_notify_theme_override_changed)); } for (KeyValue<StringName, Ref<StyleBox>> &E : data.theme_style_override) { - E.value->disconnect("changed", callable_mp(this, &Control::_notify_theme_override_changed)); + E.value->disconnect_changed(callable_mp(this, &Control::_notify_theme_override_changed)); } for (KeyValue<StringName, Ref<Font>> &E : data.theme_font_override) { - E.value->disconnect("changed", callable_mp(this, &Control::_notify_theme_override_changed)); + E.value->disconnect_changed(callable_mp(this, &Control::_notify_theme_override_changed)); } // Then override maps can be simply cleared. diff --git a/scene/gui/file_dialog.cpp b/scene/gui/file_dialog.cpp index f7507640c8..d4da4797eb 100644 --- a/scene/gui/file_dialog.cpp +++ b/scene/gui/file_dialog.cpp @@ -54,6 +54,38 @@ void FileDialog::_focus_file_text() { } } +void FileDialog::popup(const Rect2i &p_rect) { + if (access == ACCESS_FILESYSTEM && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_NATIVE_DIALOG) && (use_native_dialog || OS::get_singleton()->is_sandboxed())) { + DisplayServer::get_singleton()->file_dialog_show(get_title(), dir->get_text(), file->get_text().get_file(), show_hidden_files, DisplayServer::FileDialogMode(mode), filters, callable_mp(this, &FileDialog::_native_dialog_cb)); + } else { + ConfirmationDialog::popup(p_rect); + } +} + +void FileDialog::_native_dialog_cb(bool p_ok, const Vector<String> &p_files) { + if (p_ok) { + if (p_files.size() > 0) { + String f = p_files[0]; + if (mode == FILE_MODE_OPEN_FILES) { + emit_signal(SNAME("files_selected"), p_files); + } else { + if (mode == FILE_MODE_SAVE_FILE) { + emit_signal(SNAME("file_selected"), f); + } else if ((mode == FILE_MODE_OPEN_ANY || mode == FILE_MODE_OPEN_FILE) && dir_access->file_exists(f)) { + emit_signal(SNAME("file_selected"), f); + } else if (mode == FILE_MODE_OPEN_ANY || mode == FILE_MODE_OPEN_DIR) { + emit_signal(SNAME("dir_selected"), f); + } + } + file->set_text(f); + dir->set_text(f.get_base_dir()); + } + } else { + file->set_text(""); + emit_signal(SNAME("canceled")); + } +} + VBoxContainer *FileDialog::get_vbox() { return vbox; } @@ -980,6 +1012,8 @@ void FileDialog::_bind_methods() { ClassDB::bind_method(D_METHOD("get_root_subfolder"), &FileDialog::get_root_subfolder); ClassDB::bind_method(D_METHOD("set_show_hidden_files", "show"), &FileDialog::set_show_hidden_files); ClassDB::bind_method(D_METHOD("is_showing_hidden_files"), &FileDialog::is_showing_hidden_files); + ClassDB::bind_method(D_METHOD("set_use_native_dialog", "native"), &FileDialog::set_use_native_dialog); + ClassDB::bind_method(D_METHOD("get_use_native_dialog"), &FileDialog::get_use_native_dialog); ClassDB::bind_method(D_METHOD("deselect_all"), &FileDialog::deselect_all); ClassDB::bind_method(D_METHOD("invalidate"), &FileDialog::invalidate); @@ -990,6 +1024,7 @@ void FileDialog::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::STRING, "root_subfolder"), "set_root_subfolder", "get_root_subfolder"); ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "filters"), "set_filters", "get_filters"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_hidden_files"), "set_show_hidden_files", "is_showing_hidden_files"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_native_dialog"), "set_use_native_dialog", "get_use_native_dialog"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "current_dir", PROPERTY_HINT_DIR, "", PROPERTY_USAGE_NONE), "set_current_dir", "get_current_dir"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "current_file", PROPERTY_HINT_FILE, "*", PROPERTY_USAGE_NONE), "set_current_file", "get_current_file"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "current_path", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_current_path", "get_current_path"); @@ -1025,6 +1060,14 @@ void FileDialog::set_default_show_hidden_files(bool p_show) { default_show_hidden_files = p_show; } +void FileDialog::set_use_native_dialog(bool p_native) { + use_native_dialog = p_native; +} + +bool FileDialog::get_use_native_dialog() const { + return use_native_dialog; +} + FileDialog::FileDialog() { show_hidden_files = default_show_hidden_files; diff --git a/scene/gui/file_dialog.h b/scene/gui/file_dialog.h index 87c41be98b..dece241ca9 100644 --- a/scene/gui/file_dialog.h +++ b/scene/gui/file_dialog.h @@ -105,6 +105,7 @@ private: static bool default_show_hidden_files; bool show_hidden_files = false; + bool use_native_dialog = false; bool is_invalidating = false; @@ -158,6 +159,8 @@ private: virtual void shortcut_input(const Ref<InputEvent> &p_event) override; + void _native_dialog_cb(bool p_ok, const Vector<String> &p_files); + bool _is_open_should_be_disabled(); virtual void _post_popup() override; @@ -169,6 +172,8 @@ protected: static void _bind_methods(); //bind helpers public: + virtual void popup(const Rect2i &p_rect = Rect2i()) override; + void popup_file_dialog(); void clear_filters(); void add_filter(const String &p_filter, const String &p_description = ""); @@ -191,6 +196,9 @@ public: void set_mode_overrides_title(bool p_override); bool is_mode_overriding_title() const; + void set_use_native_dialog(bool p_native); + bool get_use_native_dialog() const; + void set_file_mode(FileMode p_mode); FileMode get_file_mode() const; diff --git a/scene/gui/graph_edit.cpp b/scene/gui/graph_edit.cpp index 2c37017fa1..cb9fba44e5 100644 --- a/scene/gui/graph_edit.cpp +++ b/scene/gui/graph_edit.cpp @@ -35,10 +35,18 @@ #include "core/os/keyboard.h" #include "scene/gui/box_container.h" #include "scene/gui/button.h" +#include "scene/gui/graph_edit_arranger.h" #include "scene/gui/view_panner.h" +#include "scene/resources/style_box_flat.h" constexpr int MINIMAP_OFFSET = 12; constexpr int MINIMAP_PADDING = 5; +constexpr int MIN_DRAG_DISTANCE_FOR_VALID_CONNECTION = 20; +constexpr int MAX_CONNECTION_LINE_CURVE_TESSELATION_STAGES = 5; +constexpr int GRID_MINOR_STEPS_PER_MAJOR_LINE = 10; +constexpr int GRID_MIN_SNAPPING_DISTANCE = 2; +constexpr int GRID_MAX_SNAPPING_DISTANCE = 100; +constexpr float CONNECTING_TARGET_LINE_COLOR_BRIGHTENING = 0.4; bool GraphEditFilter::has_point(const Point2 &p_point) const { return ge->_filter_input(p_point); @@ -75,23 +83,23 @@ void GraphEditMinimap::update_minimap() { Vector2 graph_offset = _get_graph_offset(); Vector2 graph_size = _get_graph_size(); - camera_position = ge->get_scroll_ofs() - graph_offset; + camera_position = ge->get_scroll_offset() - graph_offset; camera_size = ge->get_size(); Vector2 render_size = _get_render_size(); - float target_ratio = render_size.x / render_size.y; - float graph_ratio = graph_size.x / graph_size.y; + float target_ratio = render_size.width / render_size.height; + float graph_ratio = graph_size.width / graph_size.height; graph_proportions = graph_size; graph_padding = Vector2(0, 0); if (graph_ratio > target_ratio) { - graph_proportions.x = graph_size.x; - graph_proportions.y = graph_size.x / target_ratio; - graph_padding.y = Math::abs(graph_size.y - graph_proportions.y) / 2; + graph_proportions.width = graph_size.width; + graph_proportions.height = graph_size.width / target_ratio; + graph_padding.y = Math::abs(graph_size.height - graph_proportions.y) / 2; } else { - graph_proportions.x = graph_size.y * target_ratio; - graph_proportions.y = graph_size.y; - graph_padding.x = Math::abs(graph_size.x - graph_proportions.x) / 2; + graph_proportions.width = graph_size.height * target_ratio; + graph_proportions.height = graph_size.height; + graph_padding.x = Math::abs(graph_size.width - graph_proportions.x) / 2; } // This centers minimap inside the minimap rectangle. @@ -114,17 +122,17 @@ Vector2 GraphEditMinimap::_get_render_size() { } Vector2 GraphEditMinimap::_get_graph_offset() { - return Vector2(ge->h_scroll->get_min(), ge->v_scroll->get_min()); + return Vector2(ge->h_scrollbar->get_min(), ge->v_scrollbar->get_min()); } Vector2 GraphEditMinimap::_get_graph_size() { - Vector2 graph_size = Vector2(ge->h_scroll->get_max(), ge->v_scroll->get_max()) - Vector2(ge->h_scroll->get_min(), ge->v_scroll->get_min()); + Vector2 graph_size = Vector2(ge->h_scrollbar->get_max(), ge->v_scrollbar->get_max()) - Vector2(ge->h_scrollbar->get_min(), ge->v_scrollbar->get_min()); - if (graph_size.x == 0) { - graph_size.x = 1; + if (graph_size.width == 0) { + graph_size.width = 1; } - if (graph_size.y == 0) { - graph_size.y = 1; + if (graph_size.height == 0) { + graph_size.height = 1; } return graph_size; @@ -134,8 +142,8 @@ Vector2 GraphEditMinimap::_convert_from_graph_position(const Vector2 &p_position Vector2 map_position = Vector2(0, 0); Vector2 render_size = _get_render_size(); - map_position.x = p_position.x * render_size.x / graph_proportions.x; - map_position.y = p_position.y * render_size.y / graph_proportions.y; + map_position.x = p_position.x * render_size.width / graph_proportions.x; + map_position.y = p_position.y * render_size.height / graph_proportions.y; return map_position; } @@ -144,8 +152,8 @@ Vector2 GraphEditMinimap::_convert_to_graph_position(const Vector2 &p_position) Vector2 graph_position = Vector2(0, 0); Vector2 render_size = _get_render_size(); - graph_position.x = p_position.x * graph_proportions.x / render_size.x; - graph_position.y = p_position.y * graph_proportions.y / render_size.y; + graph_position.x = p_position.x * graph_proportions.x / render_size.width; + graph_position.y = p_position.y * graph_proportions.y / render_size.height; return graph_position; } @@ -179,10 +187,10 @@ void GraphEditMinimap::gui_input(const Ref<InputEvent> &p_ev) { accept_event(); } else if (mm.is_valid() && is_pressing) { if (is_resizing) { - // Prevent setting minimap wider than GraphEdit + // Prevent setting minimap wider than GraphEdit. Vector2 new_minimap_size; - new_minimap_size.x = MIN(get_size().x - mm->get_relative().x, ge->get_size().x - 2.0 * minimap_padding.x); - new_minimap_size.y = MIN(get_size().y - mm->get_relative().y, ge->get_size().y - 2.0 * minimap_padding.y); + new_minimap_size.width = MIN(get_size().width - mm->get_relative().x, ge->get_size().width - 2.0 * minimap_padding.x); + new_minimap_size.height = MIN(get_size().height - mm->get_relative().y, ge->get_size().height - 2.0 * minimap_padding.y); ge->set_minimap_size(new_minimap_size); queue_redraw(); @@ -196,7 +204,7 @@ void GraphEditMinimap::gui_input(const Ref<InputEvent> &p_ev) { void GraphEditMinimap::_adjust_graph_scroll(const Vector2 &p_offset) { Vector2 graph_offset = _get_graph_offset(); - ge->set_scroll_ofs(p_offset + graph_offset - camera_size / 2); + ge->set_scroll_offset(p_offset + graph_offset - camera_size / 2); } Control::CursorShape GraphEdit::get_cursor_shape(const Point2 &p_pos) const { @@ -220,9 +228,9 @@ Error GraphEdit::connect_node(const StringName &p_from, int p_from_port, const S return OK; } Connection c; - c.from = p_from; + c.from_node = p_from; c.from_port = p_from_port; - c.to = p_to; + c.to_node = p_to; c.to_port = p_to_port; c.activity = 0; connections.push_back(c); @@ -236,7 +244,7 @@ Error GraphEdit::connect_node(const StringName &p_from, int p_from_port, const S bool GraphEdit::is_node_connected(const StringName &p_from, int p_from_port, const StringName &p_to, int p_to_port) { for (const Connection &E : connections) { - if (E.from == p_from && E.from_port == p_from_port && E.to == p_to && E.to_port == p_to_port) { + if (E.from_node == p_from && E.from_port == p_from_port && E.to_node == p_to && E.to_port == p_to_port) { return true; } } @@ -246,7 +254,7 @@ bool GraphEdit::is_node_connected(const StringName &p_from, int p_from_port, con void GraphEdit::disconnect_node(const StringName &p_from, int p_from_port, const StringName &p_to, int p_to_port) { for (const List<Connection>::Element *E = connections.front(); E; E = E->next()) { - if (E->get().from == p_from && E->get().from_port == p_from_port && E->get().to == p_to && E->get().to_port == p_to_port) { + if (E->get().from_node == p_from && E->get().from_port == p_from_port && E->get().to_node == p_to && E->get().to_port == p_to_port) { connections.erase(E); top_layer->queue_redraw(); minimap->queue_redraw(); @@ -261,21 +269,21 @@ void GraphEdit::get_connection_list(List<Connection> *r_connections) const { *r_connections = connections; } -void GraphEdit::set_scroll_ofs(const Vector2 &p_ofs) { - setting_scroll_ofs = true; - h_scroll->set_value(p_ofs.x); - v_scroll->set_value(p_ofs.y); +void GraphEdit::set_scroll_offset(const Vector2 &p_offset) { + setting_scroll_offset = true; + h_scrollbar->set_value(p_offset.x); + v_scrollbar->set_value(p_offset.y); _update_scroll(); - setting_scroll_ofs = false; + setting_scroll_offset = false; } -Vector2 GraphEdit::get_scroll_ofs() const { - return Vector2(h_scroll->get_value(), v_scroll->get_value()); +Vector2 GraphEdit::get_scroll_offset() const { + return Vector2(h_scrollbar->get_value(), v_scrollbar->get_value()); } void GraphEdit::_scroll_moved(double) { if (!awaiting_scroll_offset_update) { - call_deferred(SNAME("_update_scroll_offset")); + callable_mp(this, &GraphEdit::_update_scroll_offset).call_deferred(); awaiting_scroll_offset_update = true; } top_layer->queue_redraw(); @@ -287,25 +295,26 @@ void GraphEdit::_update_scroll_offset() { set_block_minimum_size_adjust(true); for (int i = 0; i < get_child_count(); i++) { - GraphNode *gn = Object::cast_to<GraphNode>(get_child(i)); - if (!gn) { + GraphNode *graph_node = Object::cast_to<GraphNode>(get_child(i)); + if (!graph_node) { continue; } - Point2 pos = gn->get_position_offset() * zoom; - pos -= Point2(h_scroll->get_value(), v_scroll->get_value()); - gn->set_position(pos); - if (gn->get_scale() != Vector2(zoom, zoom)) { - gn->set_scale(Vector2(zoom, zoom)); + Point2 pos = graph_node->get_position_offset() * zoom; + pos -= Point2(h_scrollbar->get_value(), v_scrollbar->get_value()); + graph_node->set_position(pos); + if (graph_node->get_scale() != Vector2(zoom, zoom)) { + graph_node->set_scale(Vector2(zoom, zoom)); } } - connections_layer->set_position(-Point2(h_scroll->get_value(), v_scroll->get_value())); + connections_layer->set_position(-Point2(h_scrollbar->get_value(), v_scrollbar->get_value())); set_block_minimum_size_adjust(false); awaiting_scroll_offset_update = false; - if (!setting_scroll_ofs) { //in godot, signals on change value are avoided as a convention - emit_signal(SNAME("scroll_offset_changed"), get_scroll_ofs()); + // In Godot, signals on value change are avoided by convention. + if (!setting_scroll_offset) { + emit_signal(SNAME("scroll_offset_changed"), get_scroll_offset()); } } @@ -313,90 +322,93 @@ void GraphEdit::_update_scroll() { if (updating) { return; } - updating = true; set_block_minimum_size_adjust(true); - Rect2 screen; + Rect2 screen_rect; for (int i = 0; i < get_child_count(); i++) { - GraphNode *gn = Object::cast_to<GraphNode>(get_child(i)); - if (!gn) { + GraphNode *graph_node = Object::cast_to<GraphNode>(get_child(i)); + if (!graph_node) { continue; } - Rect2 r; - r.position = gn->get_position_offset() * zoom; - r.size = gn->get_size() * zoom; - screen = screen.merge(r); + Rect2 node_rect; + node_rect.position = graph_node->get_position_offset() * zoom; + node_rect.size = graph_node->get_size() * zoom; + screen_rect = screen_rect.merge(node_rect); } - screen.position -= get_size(); - screen.size += get_size() * 2.0; + screen_rect.position -= get_size(); + screen_rect.size += get_size() * 2.0; - h_scroll->set_min(screen.position.x); - h_scroll->set_max(screen.position.x + screen.size.x); - h_scroll->set_page(get_size().x); - if (h_scroll->get_max() - h_scroll->get_min() <= h_scroll->get_page()) { - h_scroll->hide(); + h_scrollbar->set_min(screen_rect.position.x); + h_scrollbar->set_max(screen_rect.position.x + screen_rect.size.width); + h_scrollbar->set_page(get_size().x); + if (h_scrollbar->get_max() - h_scrollbar->get_min() <= h_scrollbar->get_page()) { + h_scrollbar->hide(); } else { - h_scroll->show(); + h_scrollbar->show(); } - v_scroll->set_min(screen.position.y); - v_scroll->set_max(screen.position.y + screen.size.y); - v_scroll->set_page(get_size().y); + v_scrollbar->set_min(screen_rect.position.y); + v_scrollbar->set_max(screen_rect.position.y + screen_rect.size.height); + v_scrollbar->set_page(get_size().height); - if (v_scroll->get_max() - v_scroll->get_min() <= v_scroll->get_page()) { - v_scroll->hide(); + if (v_scrollbar->get_max() - v_scrollbar->get_min() <= v_scrollbar->get_page()) { + v_scrollbar->hide(); } else { - v_scroll->show(); + v_scrollbar->show(); } - Size2 hmin = h_scroll->get_combined_minimum_size(); - Size2 vmin = v_scroll->get_combined_minimum_size(); + Size2 hmin = h_scrollbar->get_combined_minimum_size(); + Size2 vmin = v_scrollbar->get_combined_minimum_size(); // Avoid scrollbar overlapping. - h_scroll->set_anchor_and_offset(SIDE_RIGHT, ANCHOR_END, v_scroll->is_visible() ? -vmin.width : 0); - v_scroll->set_anchor_and_offset(SIDE_BOTTOM, ANCHOR_END, h_scroll->is_visible() ? -hmin.height : 0); + h_scrollbar->set_anchor_and_offset(SIDE_RIGHT, ANCHOR_END, v_scrollbar->is_visible() ? -vmin.width : 0); + v_scrollbar->set_anchor_and_offset(SIDE_BOTTOM, ANCHOR_END, h_scrollbar->is_visible() ? -hmin.height : 0); set_block_minimum_size_adjust(false); if (!awaiting_scroll_offset_update) { - call_deferred(SNAME("_update_scroll_offset")); + callable_mp(this, &GraphEdit::_update_scroll_offset).call_deferred(); awaiting_scroll_offset_update = true; } updating = false; } -void GraphEdit::_graph_node_raised(Node *p_gn) { - GraphNode *gn = Object::cast_to<GraphNode>(p_gn); - ERR_FAIL_NULL(gn); - if (gn->is_comment()) { - move_child(gn, 0); - } else { - gn->move_to_front(); - } +void GraphEdit::_graph_node_moved_to_front(Node *p_gn) { + GraphNode *graph_node = Object::cast_to<GraphNode>(p_gn); + ERR_FAIL_NULL(graph_node); + + graph_node->move_to_front(); } void GraphEdit::_graph_node_selected(Node *p_gn) { - GraphNode *gn = Object::cast_to<GraphNode>(p_gn); - ERR_FAIL_NULL(gn); + GraphNode *graph_node = Object::cast_to<GraphNode>(p_gn); + ERR_FAIL_NULL(graph_node); - emit_signal(SNAME("node_selected"), gn); + emit_signal(SNAME("node_selected"), graph_node); } void GraphEdit::_graph_node_deselected(Node *p_gn) { - GraphNode *gn = Object::cast_to<GraphNode>(p_gn); - ERR_FAIL_NULL(gn); + GraphNode *graph_node = Object::cast_to<GraphNode>(p_gn); + ERR_FAIL_NULL(graph_node); - emit_signal(SNAME("node_deselected"), gn); + emit_signal(SNAME("node_deselected"), graph_node); +} + +void GraphEdit::_graph_node_resized(Vector2 p_new_minsize, Node *p_gn) { + GraphNode *graph_node = Object::cast_to<GraphNode>(p_gn); + ERR_FAIL_NULL(graph_node); + + graph_node->set_custom_minimum_size(p_new_minsize); } void GraphEdit::_graph_node_moved(Node *p_gn) { - GraphNode *gn = Object::cast_to<GraphNode>(p_gn); - ERR_FAIL_NULL(gn); + GraphNode *graph_node = Object::cast_to<GraphNode>(p_gn); + ERR_FAIL_NULL(graph_node); top_layer->queue_redraw(); minimap->queue_redraw(); queue_redraw(); @@ -404,8 +416,8 @@ void GraphEdit::_graph_node_moved(Node *p_gn) { } void GraphEdit::_graph_node_slot_updated(int p_index, Node *p_gn) { - GraphNode *gn = Object::cast_to<GraphNode>(p_gn); - ERR_FAIL_NULL(gn); + GraphNode *graph_node = Object::cast_to<GraphNode>(p_gn); + ERR_FAIL_NULL(graph_node); top_layer->queue_redraw(); minimap->queue_redraw(); queue_redraw(); @@ -415,20 +427,22 @@ void GraphEdit::_graph_node_slot_updated(int p_index, Node *p_gn) { void GraphEdit::add_child_notify(Node *p_child) { Control::add_child_notify(p_child); - top_layer->call_deferred(SNAME("raise")); // Top layer always on top! + // Keep the top layer always on top! + callable_mp((CanvasItem *)top_layer, &CanvasItem::move_to_front).call_deferred(); - GraphNode *gn = Object::cast_to<GraphNode>(p_child); - if (gn) { - gn->set_scale(Vector2(zoom, zoom)); - gn->connect("position_offset_changed", callable_mp(this, &GraphEdit::_graph_node_moved).bind(gn)); - gn->connect("node_selected", callable_mp(this, &GraphEdit::_graph_node_selected).bind(gn)); - gn->connect("node_deselected", callable_mp(this, &GraphEdit::_graph_node_deselected).bind(gn)); - gn->connect("slot_updated", callable_mp(this, &GraphEdit::_graph_node_slot_updated).bind(gn)); - gn->connect("raise_request", callable_mp(this, &GraphEdit::_graph_node_raised).bind(gn)); - gn->connect("item_rect_changed", callable_mp((CanvasItem *)connections_layer, &CanvasItem::queue_redraw)); - gn->connect("item_rect_changed", callable_mp((CanvasItem *)minimap, &GraphEditMinimap::queue_redraw)); - _graph_node_moved(gn); - gn->set_mouse_filter(MOUSE_FILTER_PASS); + GraphNode *graph_node = Object::cast_to<GraphNode>(p_child); + if (graph_node) { + graph_node->set_scale(Vector2(zoom, zoom)); + graph_node->connect("position_offset_changed", callable_mp(this, &GraphEdit::_graph_node_moved).bind(graph_node)); + graph_node->connect("node_selected", callable_mp(this, &GraphEdit::_graph_node_selected).bind(graph_node)); + graph_node->connect("node_deselected", callable_mp(this, &GraphEdit::_graph_node_deselected).bind(graph_node)); + graph_node->connect("slot_updated", callable_mp(this, &GraphEdit::_graph_node_slot_updated).bind(graph_node)); + graph_node->connect("raise_request", callable_mp(this, &GraphEdit::_graph_node_moved_to_front).bind(graph_node)); + graph_node->connect("resize_request", callable_mp(this, &GraphEdit::_graph_node_resized).bind(graph_node)); + graph_node->connect("item_rect_changed", callable_mp((CanvasItem *)connections_layer, &CanvasItem::queue_redraw)); + graph_node->connect("item_rect_changed", callable_mp((CanvasItem *)minimap, &GraphEditMinimap::queue_redraw)); + _graph_node_moved(graph_node); + graph_node->set_mouse_filter(MOUSE_FILTER_PASS); } } @@ -443,99 +457,100 @@ void GraphEdit::remove_child_notify(Node *p_child) { } if (top_layer != nullptr && is_inside_tree()) { - top_layer->call_deferred(SNAME("raise")); // Top layer always on top! + // Keep the top layer always on top! + callable_mp((CanvasItem *)top_layer, &CanvasItem::move_to_front).call_deferred(); } - GraphNode *gn = Object::cast_to<GraphNode>(p_child); - if (gn) { - gn->disconnect("position_offset_changed", callable_mp(this, &GraphEdit::_graph_node_moved)); - gn->disconnect("node_selected", callable_mp(this, &GraphEdit::_graph_node_selected)); - gn->disconnect("node_deselected", callable_mp(this, &GraphEdit::_graph_node_deselected)); - gn->disconnect("slot_updated", callable_mp(this, &GraphEdit::_graph_node_slot_updated)); - gn->disconnect("raise_request", callable_mp(this, &GraphEdit::_graph_node_raised)); + GraphNode *graph_node = Object::cast_to<GraphNode>(p_child); + if (graph_node) { + graph_node->disconnect("position_offset_changed", callable_mp(this, &GraphEdit::_graph_node_moved)); + graph_node->disconnect("node_selected", callable_mp(this, &GraphEdit::_graph_node_selected)); + graph_node->disconnect("node_deselected", callable_mp(this, &GraphEdit::_graph_node_deselected)); + graph_node->disconnect("slot_updated", callable_mp(this, &GraphEdit::_graph_node_slot_updated)); + graph_node->disconnect("raise_request", callable_mp(this, &GraphEdit::_graph_node_moved_to_front)); // In case of the whole GraphEdit being destroyed these references can already be freed. if (connections_layer != nullptr && connections_layer->is_inside_tree()) { - gn->disconnect("item_rect_changed", callable_mp((CanvasItem *)connections_layer, &CanvasItem::queue_redraw)); + graph_node->disconnect("item_rect_changed", callable_mp((CanvasItem *)connections_layer, &CanvasItem::queue_redraw)); } if (minimap != nullptr && minimap->is_inside_tree()) { - gn->disconnect("item_rect_changed", callable_mp((CanvasItem *)minimap, &GraphEditMinimap::queue_redraw)); + graph_node->disconnect("item_rect_changed", callable_mp((CanvasItem *)minimap, &GraphEditMinimap::queue_redraw)); } } } void GraphEdit::_notification(int p_what) { switch (p_what) { - case NOTIFICATION_ENTER_TREE: case NOTIFICATION_THEME_CHANGED: { port_hotzone_inner_extent = get_theme_constant("port_hotzone_inner_extent"); port_hotzone_outer_extent = get_theme_constant("port_hotzone_outer_extent"); - zoom_minus->set_icon(get_theme_icon(SNAME("minus"))); - zoom_reset->set_icon(get_theme_icon(SNAME("reset"))); - zoom_plus->set_icon(get_theme_icon(SNAME("more"))); - snap_button->set_icon(get_theme_icon(SNAME("snap"))); - minimap_button->set_icon(get_theme_icon(SNAME("minimap"))); + zoom_minus_button->set_icon(get_theme_icon(SNAME("zoom_out"))); + zoom_reset_button->set_icon(get_theme_icon(SNAME("zoom_reset"))); + zoom_plus_button->set_icon(get_theme_icon(SNAME("zoom_in"))); + + toggle_snapping_button->set_icon(get_theme_icon(SNAME("snapping_toggle"))); + show_grid_button->set_icon(get_theme_icon(SNAME("grid_toggle"))); + minimap_button->set_icon(get_theme_icon(SNAME("minimap_toggle"))); layout_button->set_icon(get_theme_icon(SNAME("layout"))); zoom_label->set_custom_minimum_size(Size2(48, 0) * get_theme_default_base_scale()); } break; case NOTIFICATION_READY: { - Size2 hmin = h_scroll->get_combined_minimum_size(); - Size2 vmin = v_scroll->get_combined_minimum_size(); - - h_scroll->set_anchor_and_offset(SIDE_LEFT, ANCHOR_BEGIN, 0); - h_scroll->set_anchor_and_offset(SIDE_RIGHT, ANCHOR_END, 0); - h_scroll->set_anchor_and_offset(SIDE_TOP, ANCHOR_END, -hmin.height); - h_scroll->set_anchor_and_offset(SIDE_BOTTOM, ANCHOR_END, 0); - - v_scroll->set_anchor_and_offset(SIDE_LEFT, ANCHOR_END, -vmin.width); - v_scroll->set_anchor_and_offset(SIDE_RIGHT, ANCHOR_END, 0); - v_scroll->set_anchor_and_offset(SIDE_TOP, ANCHOR_BEGIN, 0); - v_scroll->set_anchor_and_offset(SIDE_BOTTOM, ANCHOR_END, 0); + Size2 hmin = h_scrollbar->get_combined_minimum_size(); + Size2 vmin = v_scrollbar->get_combined_minimum_size(); + + h_scrollbar->set_anchor_and_offset(SIDE_LEFT, ANCHOR_BEGIN, 0); + h_scrollbar->set_anchor_and_offset(SIDE_RIGHT, ANCHOR_END, 0); + h_scrollbar->set_anchor_and_offset(SIDE_TOP, ANCHOR_END, -hmin.height); + h_scrollbar->set_anchor_and_offset(SIDE_BOTTOM, ANCHOR_END, 0); + + v_scrollbar->set_anchor_and_offset(SIDE_LEFT, ANCHOR_END, -vmin.width); + v_scrollbar->set_anchor_and_offset(SIDE_RIGHT, ANCHOR_END, 0); + v_scrollbar->set_anchor_and_offset(SIDE_TOP, ANCHOR_BEGIN, 0); + v_scrollbar->set_anchor_and_offset(SIDE_BOTTOM, ANCHOR_END, 0); } break; case NOTIFICATION_DRAW: { - draw_style_box(get_theme_stylebox(SNAME("bg")), Rect2(Point2(), get_size())); + // Draw background fill. + draw_style_box(get_theme_stylebox(SNAME("panel")), Rect2(Point2(), get_size())); - if (is_using_snap()) { - // Draw grid. - int snap = get_snap(); - - Vector2 offset = get_scroll_ofs() / zoom; + // Draw background grid. + if (show_grid) { + Vector2 offset = get_scroll_offset() / zoom; Size2 size = get_size() / zoom; - Point2i from = (offset / float(snap)).floor(); - Point2i len = (size / float(snap)).floor() + Vector2(1, 1); + Point2i from_pos = (offset / float(snapping_distance)).floor(); + Point2i len = (size / float(snapping_distance)).floor() + Vector2(1, 1); Color grid_minor = get_theme_color(SNAME("grid_minor")); Color grid_major = get_theme_color(SNAME("grid_major")); - for (int i = from.x; i < from.x + len.x; i++) { + for (int i = from_pos.x; i < from_pos.x + len.x; i++) { Color color; - if (ABS(i) % 10 == 0) { + if (ABS(i) % GRID_MINOR_STEPS_PER_MAJOR_LINE == 0) { color = grid_major; } else { color = grid_minor; } - float base_ofs = i * snap * zoom - offset.x * zoom; - draw_line(Vector2(base_ofs, 0), Vector2(base_ofs, get_size().height), color); + float base_offset = i * snapping_distance * zoom - offset.x * zoom; + draw_line(Vector2(base_offset, 0), Vector2(base_offset, get_size().height), color); } - for (int i = from.y; i < from.y + len.y; i++) { + for (int i = from_pos.y; i < from_pos.y + len.y; i++) { Color color; - if (ABS(i) % 10 == 0) { + if (ABS(i) % GRID_MINOR_STEPS_PER_MAJOR_LINE == 0) { color = grid_major; } else { color = grid_minor; } - float base_ofs = i * snap * zoom - offset.y * zoom; - draw_line(Vector2(0, base_ofs), Vector2(get_size().width, base_ofs), color); + float base_offset = i * snapping_distance * zoom - offset.y * zoom; + draw_line(Vector2(0, base_offset), Vector2(get_size().width, base_offset), color); } } } break; @@ -548,65 +563,38 @@ void GraphEdit::_notification(int p_what) { } } -void GraphEdit::_update_comment_enclosed_nodes_list(GraphNode *p_node, HashMap<StringName, Vector<GraphNode *>> &p_comment_enclosed_nodes) { - Rect2 comment_node_rect = p_node->get_rect(); - - Vector<GraphNode *> enclosed_nodes; - for (int i = 0; i < get_child_count(); i++) { - GraphNode *gn = Object::cast_to<GraphNode>(get_child(i)); - if (!gn || gn->is_selected()) { - continue; - } - - Rect2 node_rect = gn->get_rect(); - - bool included = comment_node_rect.encloses(node_rect); - if (included) { - enclosed_nodes.push_back(gn); - } - } - - p_comment_enclosed_nodes.insert(p_node->get_name(), enclosed_nodes); -} - -void GraphEdit::_set_drag_comment_enclosed_nodes(GraphNode *p_node, HashMap<StringName, Vector<GraphNode *>> &p_comment_enclosed_nodes, bool p_drag) { - for (int i = 0; i < p_comment_enclosed_nodes[p_node->get_name()].size(); i++) { - p_comment_enclosed_nodes[p_node->get_name()][i]->set_drag(p_drag); - } -} - -void GraphEdit::_set_position_of_comment_enclosed_nodes(GraphNode *p_node, HashMap<StringName, Vector<GraphNode *>> &p_comment_enclosed_nodes, Vector2 p_drag_accum) { - for (int i = 0; i < p_comment_enclosed_nodes[p_node->get_name()].size(); i++) { - Vector2 pos = (p_comment_enclosed_nodes[p_node->get_name()][i]->get_drag_from() * zoom + drag_accum) / zoom; - if (is_using_snap() ^ Input::get_singleton()->is_key_pressed(Key::CTRL)) { - const int snap = get_snap(); - pos = pos.snapped(Vector2(snap, snap)); - } - p_comment_enclosed_nodes[p_node->get_name()][i]->set_position_offset(pos); - } -} - bool GraphEdit::_filter_input(const Point2 &p_point) { Ref<Texture2D> port_icon = get_theme_icon(SNAME("port"), SNAME("GraphNode")); for (int i = get_child_count() - 1; i >= 0; i--) { - GraphNode *gn = Object::cast_to<GraphNode>(get_child(i)); - if (!gn) { + GraphNode *graph_node = Object::cast_to<GraphNode>(get_child(i)); + if (!graph_node || !graph_node->is_visible_in_tree()) { continue; } - for (int j = 0; j < gn->get_connection_input_count(); j++) { + for (int j = 0; j < graph_node->get_connection_input_count(); j++) { Vector2i port_size = Vector2i(port_icon->get_width(), port_icon->get_height()); - port_size.height = MAX(port_size.height, gn->get_connection_input_height(j)); - if (is_in_input_hotzone(gn, j, p_point / zoom, port_size)) { + + // Determine slot height. + int slot_index = graph_node->get_connection_input_slot(j); + Control *child = Object::cast_to<Control>(graph_node->get_child(slot_index)); + + port_size.height = MAX(port_size.height, child ? child->get_size().y : 0); + + if (is_in_input_hotzone(graph_node, j, p_point / zoom, port_size)) { return true; } } - for (int j = 0; j < gn->get_connection_output_count(); j++) { + for (int j = 0; j < graph_node->get_connection_output_count(); j++) { Vector2i port_size = Vector2i(port_icon->get_width(), port_icon->get_height()); - port_size.height = MAX(port_size.height, gn->get_connection_output_height(j)); - if (is_in_output_hotzone(gn, j, p_point / zoom, port_size)) { + + // Determine slot height. + int slot_index = graph_node->get_connection_output_slot(j); + Control *child = Object::cast_to<Control>(graph_node->get_child(slot_index)); + port_size.height = MAX(port_size.height, child ? child->get_size().y : 0); + + if (is_in_output_hotzone(graph_node, j, p_point / zoom, port_size)) { return true; } } @@ -623,24 +611,28 @@ void GraphEdit::_top_layer_input(const Ref<InputEvent> &p_ev) { connecting_valid = false; click_pos = mb->get_position() / zoom; for (int i = get_child_count() - 1; i >= 0; i--) { - GraphNode *gn = Object::cast_to<GraphNode>(get_child(i)); - if (!gn) { + GraphNode *graph_node = Object::cast_to<GraphNode>(get_child(i)); + if (!graph_node || !graph_node->is_visible_in_tree()) { continue; } - for (int j = 0; j < gn->get_connection_output_count(); j++) { - Vector2 pos = gn->get_connection_output_position(j) + gn->get_position(); + for (int j = 0; j < graph_node->get_connection_output_count(); j++) { + Vector2 pos = graph_node->get_connection_output_position(j) * zoom + graph_node->get_position(); Vector2i port_size = Vector2i(port_icon->get_width(), port_icon->get_height()); - port_size.height = MAX(port_size.height, gn->get_connection_output_height(j)); - if (is_in_output_hotzone(gn, j, click_pos, port_size)) { - if (valid_left_disconnect_types.has(gn->get_connection_output_type(j))) { - //check disconnect + // Determine slot height. + int slot_index = graph_node->get_connection_output_slot(j); + Control *child = Object::cast_to<Control>(graph_node->get_child(slot_index)); + port_size.height = MAX(port_size.height, child ? child->get_size().y : 0); + + if (is_in_output_hotzone(graph_node, j, click_pos, port_size)) { + if (valid_left_disconnect_types.has(graph_node->get_connection_output_type(j))) { + // Check disconnect. for (const Connection &E : connections) { - if (E.from == gn->get_name() && E.from_port == j) { - Node *to = get_node(NodePath(E.to)); + if (E.from_node == graph_node->get_name() && E.from_port == j) { + Node *to = get_node(NodePath(E.to_node)); if (Object::cast_to<GraphNode>(to)) { - connecting_from = E.to; + connecting_from = E.to_node; connecting_index = E.to_port; connecting_out = false; connecting_type = Object::cast_to<GraphNode>(to)->get_connection_input_type(E.to_port); @@ -651,7 +643,7 @@ void GraphEdit::_top_layer_input(const Ref<InputEvent> &p_ev) { if (connecting_type >= 0) { just_disconnected = true; - emit_signal(SNAME("disconnection_request"), E.from, E.from_port, E.to, E.to_port); + emit_signal(SNAME("disconnection_request"), E.from_node, E.from_port, E.to_node, E.to_port); to = get_node(NodePath(connecting_from)); // Maybe it was erased. if (Object::cast_to<GraphNode>(to)) { connecting = true; @@ -664,11 +656,11 @@ void GraphEdit::_top_layer_input(const Ref<InputEvent> &p_ev) { } } - connecting_from = gn->get_name(); + connecting_from = graph_node->get_name(); connecting_index = j; connecting_out = true; - connecting_type = gn->get_connection_output_type(j); - connecting_color = gn->get_connection_output_color(j); + connecting_type = graph_node->get_connection_output_type(j); + connecting_color = graph_node->get_connection_output_color(j); connecting_target = false; connecting_to = pos; if (connecting_type >= 0) { @@ -680,20 +672,24 @@ void GraphEdit::_top_layer_input(const Ref<InputEvent> &p_ev) { } } - for (int j = 0; j < gn->get_connection_input_count(); j++) { - Vector2 pos = gn->get_connection_input_position(j) + gn->get_position(); + for (int j = 0; j < graph_node->get_connection_input_count(); j++) { + Vector2 pos = graph_node->get_connection_input_position(j) + graph_node->get_position(); Vector2i port_size = Vector2i(port_icon->get_width(), port_icon->get_height()); - port_size.height = MAX(port_size.height, gn->get_connection_input_height(j)); - if (is_in_input_hotzone(gn, j, click_pos, port_size)) { - if (right_disconnects || valid_right_disconnect_types.has(gn->get_connection_input_type(j))) { + // Determine slot height. + int slot_index = graph_node->get_connection_input_slot(j); + Control *child = Object::cast_to<Control>(graph_node->get_child(slot_index)); + port_size.height = MAX(port_size.height, child ? child->get_size().y : 0); + + if (is_in_input_hotzone(graph_node, j, click_pos, port_size)) { + if (right_disconnects || valid_right_disconnect_types.has(graph_node->get_connection_input_type(j))) { // Check disconnect. for (const Connection &E : connections) { - if (E.to == gn->get_name() && E.to_port == j) { - Node *fr = get_node(NodePath(E.from)); + if (E.to_node == graph_node->get_name() && E.to_port == j) { + Node *fr = get_node(NodePath(E.from_node)); if (Object::cast_to<GraphNode>(fr)) { - connecting_from = E.from; + connecting_from = E.from_node; connecting_index = E.from_port; connecting_out = true; connecting_type = Object::cast_to<GraphNode>(fr)->get_connection_output_type(E.from_port); @@ -703,8 +699,8 @@ void GraphEdit::_top_layer_input(const Ref<InputEvent> &p_ev) { just_disconnected = true; if (connecting_type >= 0) { - emit_signal(SNAME("disconnection_request"), E.from, E.from_port, E.to, E.to_port); - fr = get_node(NodePath(connecting_from)); // Maybe it was erased. + emit_signal(SNAME("disconnection_request"), E.from_node, E.from_port, E.to_node, E.to_port); + fr = get_node(NodePath(connecting_from)); if (Object::cast_to<GraphNode>(fr)) { connecting = true; emit_signal(SNAME("connection_drag_started"), connecting_from, connecting_index, true); @@ -716,11 +712,11 @@ void GraphEdit::_top_layer_input(const Ref<InputEvent> &p_ev) { } } - connecting_from = gn->get_name(); + connecting_from = graph_node->get_name(); connecting_index = j; connecting_out = false; - connecting_type = gn->get_connection_input_type(j); - connecting_color = gn->get_connection_input_color(j); + connecting_type = graph_node->get_connection_input_type(j); + connecting_color = graph_node->get_connection_input_color(j); connecting_target = false; connecting_to = pos; if (connecting_type >= 0) { @@ -740,49 +736,61 @@ void GraphEdit::_top_layer_input(const Ref<InputEvent> &p_ev) { connecting_target = false; top_layer->queue_redraw(); minimap->queue_redraw(); - connecting_valid = just_disconnected || click_pos.distance_to(connecting_to / zoom) > 20.0; + + connecting_valid = just_disconnected || click_pos.distance_to(connecting_to / zoom) > MIN_DRAG_DISTANCE_FOR_VALID_CONNECTION; if (connecting_valid) { Vector2 mpos = mm->get_position() / zoom; for (int i = get_child_count() - 1; i >= 0; i--) { Ref<Texture2D> port_icon = get_theme_icon(SNAME("port"), SNAME("GraphNode")); - GraphNode *gn = Object::cast_to<GraphNode>(get_child(i)); - if (!gn) { + GraphNode *graph_node = Object::cast_to<GraphNode>(get_child(i)); + if (!graph_node || !graph_node->is_visible_in_tree()) { continue; } if (!connecting_out) { - for (int j = 0; j < gn->get_connection_output_count(); j++) { - Vector2 pos = gn->get_connection_output_position(j) + gn->get_position(); + for (int j = 0; j < graph_node->get_connection_output_count(); j++) { + Vector2 pos = graph_node->get_connection_output_position(j) + graph_node->get_position(); Vector2i port_size = Vector2i(port_icon->get_width(), port_icon->get_height()); - port_size.height = MAX(port_size.height, gn->get_connection_output_height(j)); - int type = gn->get_connection_output_type(j); - if ((type == connecting_type || valid_connection_types.has(ConnType(connecting_type, type))) && is_in_output_hotzone(gn, j, mpos, port_size)) { - if (!is_node_hover_valid(gn->get_name(), j, connecting_from, connecting_index)) { + // Determine slot height. + int slot_index = graph_node->get_connection_output_slot(j); + Control *child = Object::cast_to<Control>(graph_node->get_child(slot_index)); + port_size.height = MAX(port_size.height, child ? child->get_size().y : 0); + + int type = graph_node->get_connection_output_type(j); + if ((type == connecting_type || + valid_connection_types.has(ConnectionType(connecting_type, type))) && + is_in_output_hotzone(graph_node, j, mpos, port_size)) { + if (!is_node_hover_valid(graph_node->get_name(), j, connecting_from, connecting_index)) { continue; } connecting_target = true; connecting_to = pos; - connecting_target_to = gn->get_name(); + connecting_target_to = graph_node->get_name(); connecting_target_index = j; return; } } } else { - for (int j = 0; j < gn->get_connection_input_count(); j++) { - Vector2 pos = gn->get_connection_input_position(j) + gn->get_position(); + for (int j = 0; j < graph_node->get_connection_input_count(); j++) { + Vector2 pos = graph_node->get_connection_input_position(j) + graph_node->get_position(); Vector2i port_size = Vector2i(port_icon->get_width(), port_icon->get_height()); - port_size.height = MAX(port_size.height, gn->get_connection_input_height(j)); - int type = gn->get_connection_input_type(j); - if ((type == connecting_type || valid_connection_types.has(ConnType(connecting_type, type))) && is_in_input_hotzone(gn, j, mpos, port_size)) { - if (!is_node_hover_valid(connecting_from, connecting_index, gn->get_name(), j)) { + // Determine slot height. + int slot_index = graph_node->get_connection_input_slot(j); + Control *child = Object::cast_to<Control>(graph_node->get_child(slot_index)); + port_size.height = MAX(port_size.height, child ? child->get_size().y : 0); + + int type = graph_node->get_connection_input_type(j); + if ((type == connecting_type || valid_connection_types.has(ConnectionType(connecting_type, type))) && + is_in_input_hotzone(graph_node, j, mpos, port_size)) { + if (!is_node_hover_valid(connecting_from, connecting_index, graph_node->get_name(), j)) { continue; } connecting_target = true; connecting_to = pos; - connecting_target_to = gn->get_name(); + connecting_target_to = graph_node->get_name(); connecting_target_index = j; return; } @@ -843,30 +851,30 @@ bool GraphEdit::_check_clickable_control(Control *p_control, const Vector2 &mpos } } -bool GraphEdit::is_in_input_hotzone(GraphNode *p_node, int p_port, const Vector2 &p_mouse_pos, const Vector2i &p_port_size) { +bool GraphEdit::is_in_input_hotzone(GraphNode *p_graph_node, int p_port, const Vector2 &p_mouse_pos, const Vector2i &p_port_size) { bool success; - if (GDVIRTUAL_CALL(_is_in_input_hotzone, p_node, p_port, p_mouse_pos, success)) { + if (GDVIRTUAL_CALL(_is_in_input_hotzone, p_graph_node, p_port, p_mouse_pos, success)) { return success; } else { - Vector2 pos = p_node->get_connection_input_position(p_port) + p_node->get_position(); + Vector2 pos = p_graph_node->get_connection_input_position(p_port) + p_graph_node->get_position(); return is_in_port_hotzone(pos / zoom, p_mouse_pos, p_port_size, true); } } -bool GraphEdit::is_in_output_hotzone(GraphNode *p_node, int p_port, const Vector2 &p_mouse_pos, const Vector2i &p_port_size) { - if (p_node->is_resizable()) { - Ref<Texture2D> resizer = p_node->get_theme_icon(SNAME("resizer")); - Rect2 resizer_rect = Rect2(p_node->get_position() / zoom + p_node->get_size() - resizer->get_size(), resizer->get_size()); +bool GraphEdit::is_in_output_hotzone(GraphNode *p_graph_node, int p_port, const Vector2 &p_mouse_pos, const Vector2i &p_port_size) { + if (p_graph_node->is_resizable()) { + Ref<Texture2D> resizer = p_graph_node->get_theme_icon(SNAME("resizer")); + Rect2 resizer_rect = Rect2(p_graph_node->get_position() / zoom + p_graph_node->get_size() - resizer->get_size(), resizer->get_size()); if (resizer_rect.has_point(p_mouse_pos)) { return false; } } bool success; - if (GDVIRTUAL_CALL(_is_in_output_hotzone, p_node, p_port, p_mouse_pos, success)) { + if (GDVIRTUAL_CALL(_is_in_output_hotzone, p_graph_node, p_port, p_mouse_pos, success)) { return success; } else { - Vector2 pos = p_node->get_connection_output_position(p_port) + p_node->get_position(); + Vector2 pos = p_graph_node->get_connection_output_position(p_port) + p_graph_node->get_position(); return is_in_port_hotzone(pos / zoom, p_mouse_pos, p_port_size, false); } } @@ -925,7 +933,7 @@ PackedVector2Array GraphEdit::get_connection_line(const Vector2 &p_from, const V curve.set_point_in(1, Vector2(-cp_offset, 0)); if (lines_curvature > 0) { - return curve.tessellate(5, 2.0); + return curve.tessellate(MAX_CONNECTION_LINE_CURVE_TESSELATION_STAGES, 2.0); } else { return curve.tessellate(1); } @@ -948,31 +956,32 @@ void GraphEdit::_draw_connection_line(CanvasItem *p_where, const Vector2 &p_from void GraphEdit::_connections_layer_draw() { Color activity_color = get_theme_color(SNAME("activity")); + // Draw connections. List<List<Connection>::Element *> to_erase; for (List<Connection>::Element *E = connections.front(); E; E = E->next()) { const Connection &c = E->get(); - Node *from = get_node(NodePath(c.from)); - GraphNode *gfrom = Object::cast_to<GraphNode>(from); + Node *from = get_node(NodePath(c.from_node)); + GraphNode *gnode_from = Object::cast_to<GraphNode>(from); - if (!gfrom) { + if (!gnode_from) { to_erase.push_back(E); continue; } - Node *to = get_node(NodePath(c.to)); - GraphNode *gto = Object::cast_to<GraphNode>(to); + Node *to = get_node(NodePath(c.to_node)); + GraphNode *gnode_to = Object::cast_to<GraphNode>(to); - if (!gto) { + if (!gnode_to) { to_erase.push_back(E); continue; } - Vector2 frompos = gfrom->get_connection_output_position(c.from_port) + gfrom->get_position_offset() * zoom; - Color color = gfrom->get_connection_output_color(c.from_port); - Vector2 topos = gto->get_connection_input_position(c.to_port) + gto->get_position_offset() * zoom; - Color tocolor = gto->get_connection_input_color(c.to_port); + Vector2 frompos = gnode_from->get_connection_output_position(c.from_port) + gnode_from->get_position_offset() * zoom; + Color color = gnode_from->get_connection_output_color(c.from_port); + Vector2 topos = gnode_to->get_connection_input_position(c.to_port) + gnode_to->get_position_offset() * zoom; + Color tocolor = gnode_to->get_connection_input_color(c.to_port); if (c.activity > 0) { color = color.lerp(activity_color, c.activity); @@ -990,33 +999,32 @@ void GraphEdit::_top_layer_draw() { _update_scroll(); if (connecting) { - Node *fromn = get_node_or_null(NodePath(connecting_from)); - ERR_FAIL_NULL(fromn); - GraphNode *from = Object::cast_to<GraphNode>(fromn); - ERR_FAIL_NULL(from); + Node *node_from = get_node_or_null(NodePath(connecting_from)); + ERR_FAIL_NULL(node_from); + GraphNode *graph_node_from = Object::cast_to<GraphNode>(node_from); + ERR_FAIL_NULL(graph_node_from); Vector2 pos; if (connecting_out) { - pos = from->get_connection_output_position(connecting_index); + pos = graph_node_from->get_connection_output_position(connecting_index); } else { - pos = from->get_connection_input_position(connecting_index); + pos = graph_node_from->get_connection_input_position(connecting_index); } - pos += from->get_position(); + pos += graph_node_from->get_position(); - Vector2 topos; - topos = connecting_to; - - Color col = connecting_color; + Vector2 to_pos = connecting_to; + Color line_color = connecting_color; + // Draw the line to the mouse cursor brighter when it's over a valid target port. if (connecting_target) { - col.r += 0.4; - col.g += 0.4; - col.b += 0.4; + line_color.r += CONNECTING_TARGET_LINE_COLOR_BRIGHTENING; + line_color.g += CONNECTING_TARGET_LINE_COLOR_BRIGHTENING; + line_color.b += CONNECTING_TARGET_LINE_COLOR_BRIGHTENING; } if (!connecting_out) { - SWAP(pos, topos); + SWAP(pos, to_pos); } - _draw_connection_line(top_layer, pos, topos, col, col, lines_thickness, zoom); + _draw_connection_line(top_layer, pos, to_pos, line_color, line_color, lines_thickness, zoom); } if (box_selecting) { @@ -1034,51 +1042,28 @@ void GraphEdit::_minimap_draw() { // Draw the minimap background. Rect2 minimap_rect = Rect2(Point2(), minimap->get_size()); - minimap->draw_style_box(minimap->get_theme_stylebox(SNAME("bg")), minimap_rect); + minimap->draw_style_box(minimap->get_theme_stylebox(SNAME("panel")), minimap_rect); Vector2 graph_offset = minimap->_get_graph_offset(); Vector2 minimap_offset = minimap->minimap_offset; - // Draw comment graph nodes. + // Draw graph nodes. for (int i = get_child_count() - 1; i >= 0; i--) { - GraphNode *gn = Object::cast_to<GraphNode>(get_child(i)); - if (!gn || !gn->is_comment()) { + GraphNode *graph_node = Object::cast_to<GraphNode>(get_child(i)); + if (!graph_node || !graph_node->is_visible()) { continue; } - Vector2 node_position = minimap->_convert_from_graph_position(gn->get_position_offset() * zoom - graph_offset) + minimap_offset; - Vector2 node_size = minimap->_convert_from_graph_position(gn->get_size() * zoom); + Vector2 node_position = minimap->_convert_from_graph_position(graph_node->get_position_offset() * zoom - graph_offset) + minimap_offset; + Vector2 node_size = minimap->_convert_from_graph_position(graph_node->get_size() * zoom); Rect2 node_rect = Rect2(node_position, node_size); Ref<StyleBoxFlat> sb_minimap = minimap->get_theme_stylebox(SNAME("node"))->duplicate(); // Override default values with colors provided by the GraphNode's stylebox, if possible. - Ref<StyleBoxFlat> sbf = gn->get_theme_stylebox(gn->is_selected() ? "comment_focus" : "comment"); - if (sbf.is_valid()) { - Color node_color = sbf->get_bg_color(); - sb_minimap->set_bg_color(node_color); - } - - minimap->draw_style_box(sb_minimap, node_rect); - } - - // Draw regular graph nodes. - for (int i = get_child_count() - 1; i >= 0; i--) { - GraphNode *gn = Object::cast_to<GraphNode>(get_child(i)); - if (!gn || gn->is_comment()) { - continue; - } - - Vector2 node_position = minimap->_convert_from_graph_position(gn->get_position_offset() * zoom - graph_offset) + minimap_offset; - Vector2 node_size = minimap->_convert_from_graph_position(gn->get_size() * zoom); - Rect2 node_rect = Rect2(node_position, node_size); - - Ref<StyleBoxFlat> sb_minimap = minimap->get_theme_stylebox(SNAME("node"))->duplicate(); - - // Override default values with colors provided by the GraphNode's stylebox, if possible. - Ref<StyleBoxFlat> sbf = gn->get_theme_stylebox(gn->is_selected() ? "selected_frame" : "frame"); - if (sbf.is_valid()) { - Color node_color = sbf->get_border_color(); + Ref<StyleBoxFlat> sb_frame = graph_node->get_theme_stylebox(graph_node->is_selected() ? "selected_frame" : "frame"); + if (sb_frame.is_valid()) { + Color node_color = sb_frame->get_bg_color(); sb_minimap->set_bg_color(node_color); } @@ -1088,24 +1073,24 @@ void GraphEdit::_minimap_draw() { // Draw node connections. Color activity_color = get_theme_color(SNAME("activity")); for (const Connection &E : connections) { - Node *from = get_node(NodePath(E.from)); - GraphNode *gfrom = Object::cast_to<GraphNode>(from); - if (!gfrom) { + Node *from = get_node(NodePath(E.from_node)); + GraphNode *graph_node_from = Object::cast_to<GraphNode>(from); + if (!graph_node_from) { continue; } - Node *to = get_node(NodePath(E.to)); - GraphNode *gto = Object::cast_to<GraphNode>(to); - if (!gto) { + Node *node_to = get_node(NodePath(E.to_node)); + GraphNode *graph_node_to = Object::cast_to<GraphNode>(node_to); + if (!graph_node_to) { continue; } - Vector2 from_port_position = gfrom->get_position_offset() * zoom + gfrom->get_connection_output_position(E.from_port); + Vector2 from_port_position = graph_node_from->get_position_offset() + graph_node_from->get_connection_output_position(E.from_port); Vector2 from_position = minimap->_convert_from_graph_position(from_port_position - graph_offset) + minimap_offset; - Color from_color = gfrom->get_connection_output_color(E.from_port); - Vector2 to_port_position = gto->get_position_offset() * zoom + gto->get_connection_input_position(E.to_port); + Color from_color = graph_node_from->get_connection_output_color(E.from_port); + Vector2 to_port_position = graph_node_to->get_position_offset() + graph_node_to->get_connection_input_position(E.to_port); Vector2 to_position = minimap->_convert_from_graph_position(to_port_position - graph_offset) + minimap_offset; - Color to_color = gto->get_connection_input_color(E.to_port); + Color to_color = graph_node_to->get_connection_input_color(E.to_port); if (E.activity > 0) { from_color = from_color.lerp(activity_color, E.activity); @@ -1126,12 +1111,12 @@ void GraphEdit::_minimap_draw() { void GraphEdit::set_selected(Node *p_child) { for (int i = get_child_count() - 1; i >= 0; i--) { - GraphNode *gn = Object::cast_to<GraphNode>(get_child(i)); - if (!gn) { + GraphNode *graph_node = Object::cast_to<GraphNode>(get_child(i)); + if (!graph_node) { continue; } - gn->set_selected(gn == p_child); + graph_node->set_selected(graph_node == p_child); } } @@ -1152,21 +1137,17 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) { just_selected = true; drag_accum += mm->get_relative(); for (int i = get_child_count() - 1; i >= 0; i--) { - GraphNode *gn = Object::cast_to<GraphNode>(get_child(i)); - if (gn && gn->is_selected() && gn->is_draggable()) { - Vector2 pos = (gn->get_drag_from() * zoom + drag_accum) / zoom; + GraphNode *graph_node = Object::cast_to<GraphNode>(get_child(i)); + if (graph_node && graph_node->is_selected() && graph_node->is_draggable()) { + Vector2 pos = (graph_node->get_drag_from() * zoom + drag_accum) / zoom; // Snapping can be toggled temporarily by holding down Ctrl. // This is done here as to not toggle the grid when holding down Ctrl. - if (is_using_snap() ^ Input::get_singleton()->is_key_pressed(Key::CTRL)) { - const int snap = get_snap(); - pos = pos.snapped(Vector2(snap, snap)); + if (snapping_enabled ^ Input::get_singleton()->is_key_pressed(Key::CTRL)) { + pos = pos.snapped(Vector2(snapping_distance, snapping_distance)); } - gn->set_position_offset(pos); - if (gn->is_comment()) { - _set_position_of_comment_enclosed_nodes(gn, comment_enclosed_nodes, drag_accum); - } + graph_node->set_position_offset(pos); } } } @@ -1177,18 +1158,18 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) { box_selecting_rect = Rect2(box_selecting_from.min(box_selecting_to), (box_selecting_from - box_selecting_to).abs()); for (int i = get_child_count() - 1; i >= 0; i--) { - GraphNode *gn = Object::cast_to<GraphNode>(get_child(i)); - if (!gn) { + GraphNode *graph_node = Object::cast_to<GraphNode>(get_child(i)); + if (!graph_node) { continue; } - Rect2 r = gn->get_rect(); + Rect2 r = graph_node->get_rect(); bool in_box = r.intersects(box_selecting_rect); if (in_box) { - gn->set_selected(box_selection_mode_additive); + graph_node->set_selected(box_selection_mode_additive); } else { - gn->set_selected(previous_selected.find(gn) != nullptr); + graph_node->set_selected(prev_selected.find(graph_node) != nullptr); } } @@ -1202,12 +1183,12 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) { if (box_selecting) { box_selecting = false; for (int i = get_child_count() - 1; i >= 0; i--) { - GraphNode *gn = Object::cast_to<GraphNode>(get_child(i)); - if (!gn) { + GraphNode *graph_node = Object::cast_to<GraphNode>(get_child(i)); + if (!graph_node) { continue; } - gn->set_selected(previous_selected.find(gn) != nullptr); + graph_node->set_selected(prev_selected.find(graph_node) != nullptr); } top_layer->queue_redraw(); minimap->queue_redraw(); @@ -1222,14 +1203,14 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) { if (mb->get_button_index() == MouseButton::LEFT && !mb->is_pressed() && dragging) { if (!just_selected && drag_accum == Vector2() && Input::get_singleton()->is_key_pressed(Key::CTRL)) { - //deselect current node + // Deselect current node. for (int i = get_child_count() - 1; i >= 0; i--) { - GraphNode *gn = Object::cast_to<GraphNode>(get_child(i)); + GraphNode *graph_node = Object::cast_to<GraphNode>(get_child(i)); - if (gn) { - Rect2 r = gn->get_rect(); + if (graph_node) { + Rect2 r = graph_node->get_rect(); if (r.has_point(mb->get_position())) { - gn->set_selected(false); + graph_node->set_selected(false); } } } @@ -1237,12 +1218,9 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) { if (drag_accum != Vector2()) { for (int i = get_child_count() - 1; i >= 0; i--) { - GraphNode *gn = Object::cast_to<GraphNode>(get_child(i)); - if (gn && gn->is_selected()) { - gn->set_drag(false); - if (gn->is_comment()) { - _set_drag_comment_enclosed_nodes(gn, comment_enclosed_nodes, false); - } + GraphNode *graph_node = Object::cast_to<GraphNode>(get_child(i)); + if (graph_node && graph_node->is_selected()) { + graph_node->set_drag(false); } } } @@ -1260,28 +1238,29 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) { connections_layer->queue_redraw(); } + // Node selection logic. if (mb->get_button_index() == MouseButton::LEFT && mb->is_pressed()) { - GraphNode *gn = nullptr; + GraphNode *graph_node = nullptr; // Find node which was clicked on. for (int i = get_child_count() - 1; i >= 0; i--) { - GraphNode *gn_selected = Object::cast_to<GraphNode>(get_child(i)); + GraphNode *selected_gcontrol = Object::cast_to<GraphNode>(get_child(i)); - if (!gn_selected) { + if (!selected_gcontrol) { continue; } - if (gn_selected->is_resizing()) { + if (selected_gcontrol->is_resizing()) { continue; } - if (gn_selected->has_point((mb->get_position() - gn_selected->get_position()) / zoom)) { - gn = gn_selected; + if (selected_gcontrol->has_point((mb->get_position() - selected_gcontrol->get_position()) / zoom)) { + graph_node = selected_gcontrol; break; } } - if (gn) { + if (graph_node) { if (_filter_input(mb->get_position())) { return; } @@ -1289,19 +1268,19 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) { // Left-clicked on a node, select it. dragging = true; drag_accum = Vector2(); - just_selected = !gn->is_selected(); - if (!gn->is_selected() && !Input::get_singleton()->is_key_pressed(Key::CTRL)) { + just_selected = !graph_node->is_selected(); + if (!graph_node->is_selected() && !Input::get_singleton()->is_key_pressed(Key::CTRL)) { for (int i = 0; i < get_child_count(); i++) { GraphNode *o_gn = Object::cast_to<GraphNode>(get_child(i)); if (!o_gn) { continue; } - o_gn->set_selected(o_gn == gn); + o_gn->set_selected(o_gn == graph_node); } } - gn->set_selected(true); + graph_node->set_selected(true); for (int i = 0; i < get_child_count(); i++) { GraphNode *o_gn = Object::cast_to<GraphNode>(get_child(i)); if (!o_gn) { @@ -1309,10 +1288,6 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) { } if (o_gn->is_selected()) { o_gn->set_drag(true); - if (o_gn->is_comment()) { - _update_comment_enclosed_nodes_list(o_gn, comment_enclosed_nodes); - _set_drag_comment_enclosed_nodes(o_gn, comment_enclosed_nodes, true); - } } } @@ -1329,29 +1304,29 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) { box_selecting_from = mb->get_position(); if (mb->is_ctrl_pressed()) { box_selection_mode_additive = true; - previous_selected.clear(); + prev_selected.clear(); for (int i = get_child_count() - 1; i >= 0; i--) { GraphNode *gn2 = Object::cast_to<GraphNode>(get_child(i)); if (!gn2 || !gn2->is_selected()) { continue; } - previous_selected.push_back(gn2); + prev_selected.push_back(gn2); } } else if (mb->is_shift_pressed()) { box_selection_mode_additive = false; - previous_selected.clear(); + prev_selected.clear(); for (int i = get_child_count() - 1; i >= 0; i--) { GraphNode *gn2 = Object::cast_to<GraphNode>(get_child(i)); if (!gn2 || !gn2->is_selected()) { continue; } - previous_selected.push_back(gn2); + prev_selected.push_back(gn2); } } else { box_selection_mode_additive = true; - previous_selected.clear(); + prev_selected.clear(); for (int i = get_child_count() - 1; i >= 0; i--) { GraphNode *gn2 = Object::cast_to<GraphNode>(get_child(i)); if (!gn2) { @@ -1368,7 +1343,7 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) { // Box selection ended. Nodes were selected during mouse movement. box_selecting = false; box_selecting_rect = Rect2(); - previous_selected.clear(); + prev_selected.clear(); top_layer->queue_redraw(); minimap->queue_redraw(); } @@ -1392,7 +1367,7 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) { if (!gn) { continue; } - if (gn->is_selected() && gn->is_close_button_visible()) { + if (gn->is_selected()) { nodes.push_back(gn->get_name()); } } @@ -1404,8 +1379,8 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) { } void GraphEdit::_pan_callback(Vector2 p_scroll_vec, Ref<InputEvent> p_event) { - h_scroll->set_value(h_scroll->get_value() - p_scroll_vec.x); - v_scroll->set_value(v_scroll->get_value() - p_scroll_vec.y); + h_scrollbar->set_value(h_scrollbar->get_value() - p_scroll_vec.x); + v_scrollbar->set_value(v_scrollbar->get_value() - p_scroll_vec.y); } void GraphEdit::_zoom_callback(float p_zoom_factor, Vector2 p_origin, Ref<InputEvent> p_event) { @@ -1414,9 +1389,9 @@ void GraphEdit::_zoom_callback(float p_zoom_factor, Vector2 p_origin, Ref<InputE void GraphEdit::set_connection_activity(const StringName &p_from, int p_from_port, const StringName &p_to, int p_to_port, float p_activity) { for (Connection &E : connections) { - if (E.from == p_from && E.from_port == p_from_port && E.to == p_to && E.to_port == p_to_port) { + if (E.from_node == p_from && E.from_port == p_from_port && E.to_node == p_to && E.to_port == p_to_port) { if (Math::is_equal_approx(E.activity, p_activity)) { - //update only if changed + // Update only if changed. top_layer->queue_redraw(); minimap->queue_redraw(); connections_layer->queue_redraw(); @@ -1470,22 +1445,22 @@ void GraphEdit::set_zoom_custom(float p_zoom, const Vector2 &p_center) { return; } - Vector2 sbofs = (Vector2(h_scroll->get_value(), v_scroll->get_value()) + p_center) / zoom; + Vector2 scrollbar_offset = (Vector2(h_scrollbar->get_value(), v_scrollbar->get_value()) + p_center) / zoom; zoom = p_zoom; top_layer->queue_redraw(); - zoom_minus->set_disabled(zoom == zoom_min); - zoom_plus->set_disabled(zoom == zoom_max); + zoom_minus_button->set_disabled(zoom == zoom_min); + zoom_plus_button->set_disabled(zoom == zoom_max); _update_scroll(); minimap->queue_redraw(); connections_layer->queue_redraw(); if (is_visible_in_tree()) { - Vector2 ofs = sbofs * zoom - p_center; - h_scroll->set_value(ofs.x); - v_scroll->set_value(ofs.y); + Vector2 offset = scrollbar_offset * zoom - p_center; + h_scrollbar->set_value(offset.x); + v_scrollbar->set_value(offset.y); } _update_zoom_label(); @@ -1583,9 +1558,9 @@ TypedArray<Dictionary> GraphEdit::_get_connection_list() const { TypedArray<Dictionary> arr; for (const Connection &E : conns) { Dictionary d; - d["from"] = E.from; + d["from_node"] = E.from_node; d["from_port"] = E.from_port; - d["to"] = E.to; + d["to_node"] = E.to_node; d["to_port"] = E.to_port; arr.push_back(d); } @@ -1611,47 +1586,71 @@ void GraphEdit::_update_zoom_label() { } void GraphEdit::add_valid_connection_type(int p_type, int p_with_type) { - ConnType ct(p_type, p_with_type); + ConnectionType ct(p_type, p_with_type); valid_connection_types.insert(ct); } void GraphEdit::remove_valid_connection_type(int p_type, int p_with_type) { - ConnType ct(p_type, p_with_type); + ConnectionType ct(p_type, p_with_type); valid_connection_types.erase(ct); } bool GraphEdit::is_valid_connection_type(int p_type, int p_with_type) const { - ConnType ct(p_type, p_with_type); + ConnectionType ct(p_type, p_with_type); return valid_connection_types.has(ct); } -void GraphEdit::set_use_snap(bool p_enable) { - if (snap_button->is_pressed() == p_enable) { +void GraphEdit::set_snapping_enabled(bool p_enable) { + if (snapping_enabled == p_enable) { return; } - snap_button->set_pressed(p_enable); + + snapping_enabled = p_enable; + toggle_snapping_button->set_pressed(p_enable); queue_redraw(); } -bool GraphEdit::is_using_snap() const { - return snap_button->is_pressed(); +bool GraphEdit::is_snapping_enabled() const { + return snapping_enabled; +} + +void GraphEdit::set_snapping_distance(int p_snapping_distance) { + ERR_FAIL_COND_MSG(p_snapping_distance < GRID_MIN_SNAPPING_DISTANCE || p_snapping_distance > GRID_MAX_SNAPPING_DISTANCE, + vformat("GraphEdit's snapping distance must be between %d and %d (inclusive)", GRID_MIN_SNAPPING_DISTANCE, GRID_MAX_SNAPPING_DISTANCE)); + snapping_distance = p_snapping_distance; + snapping_distance_spinbox->set_value(p_snapping_distance); + queue_redraw(); } -int GraphEdit::get_snap() const { - return snap_amount->get_value(); +int GraphEdit::get_snapping_distance() const { + return snapping_distance; } -void GraphEdit::set_snap(int p_snap) { - ERR_FAIL_COND(p_snap < 5); - snap_amount->set_value(p_snap); +void GraphEdit::set_show_grid(bool p_show) { + if (show_grid == p_show) { + return; + } + + show_grid = p_show; + show_grid_button->set_pressed(p_show); queue_redraw(); } -void GraphEdit::_snap_toggled() { +bool GraphEdit::is_showing_grid() const { + return show_grid; +} + +void GraphEdit::_snapping_toggled() { + snapping_enabled = toggle_snapping_button->is_pressed(); +} + +void GraphEdit::_snapping_distance_changed(double) { + snapping_distance = snapping_distance_spinbox->get_value(); queue_redraw(); } -void GraphEdit::_snap_value_changed(double) { +void GraphEdit::_show_grid_toggled() { + show_grid = show_grid_button->is_pressed(); queue_redraw(); } @@ -1660,8 +1659,8 @@ void GraphEdit::set_minimap_size(Vector2 p_size) { Vector2 minimap_size = minimap->get_size(); // The size might've been adjusted by the minimum size. minimap->set_anchors_preset(Control::PRESET_BOTTOM_RIGHT); - minimap->set_offset(Side::SIDE_LEFT, -minimap_size.x - MINIMAP_OFFSET); - minimap->set_offset(Side::SIDE_TOP, -minimap_size.y - MINIMAP_OFFSET); + minimap->set_offset(Side::SIDE_LEFT, -minimap_size.width - MINIMAP_OFFSET); + minimap->set_offset(Side::SIDE_TOP, -minimap_size.height - MINIMAP_OFFSET); minimap->set_offset(Side::SIDE_RIGHT, -MINIMAP_OFFSET); minimap->set_offset(Side::SIDE_BOTTOM, -MINIMAP_OFFSET); minimap->queue_redraw(); @@ -1752,8 +1751,8 @@ bool GraphEdit::is_connection_lines_antialiased() const { return lines_antialiased; } -HBoxContainer *GraphEdit::get_zoom_hbox() { - return zoom_hb; +HBoxContainer *GraphEdit::get_menu_hbox() { + return menu_hbox; } Ref<ViewPanner> GraphEdit::get_panner() { @@ -1764,526 +1763,8 @@ void GraphEdit::set_warped_panning(bool p_warped) { warped_panning = p_warped; } -int GraphEdit::_set_operations(SET_OPERATIONS p_operation, HashSet<StringName> &r_u, const HashSet<StringName> &r_v) { - switch (p_operation) { - case GraphEdit::IS_EQUAL: { - for (const StringName &E : r_u) { - if (!r_v.has(E)) { - return 0; - } - } - return r_u.size() == r_v.size(); - } break; - case GraphEdit::IS_SUBSET: { - if (r_u.size() == r_v.size() && !r_u.size()) { - return 1; - } - for (const StringName &E : r_u) { - if (!r_v.has(E)) { - return 0; - } - } - return 1; - } break; - case GraphEdit::DIFFERENCE: { - for (HashSet<StringName>::Iterator E = r_u.begin(); E;) { - HashSet<StringName>::Iterator N = E; - ++N; - if (r_v.has(*E)) { - r_u.remove(E); - } - E = N; - } - return r_u.size(); - } break; - case GraphEdit::UNION: { - for (const StringName &E : r_v) { - if (!r_u.has(E)) { - r_u.insert(E); - } - } - return r_u.size(); - } break; - default: - break; - } - return -1; -} - -HashMap<int, Vector<StringName>> GraphEdit::_layering(const HashSet<StringName> &r_selected_nodes, const HashMap<StringName, HashSet<StringName>> &r_upper_neighbours) { - HashMap<int, Vector<StringName>> l; - - HashSet<StringName> p = r_selected_nodes, q = r_selected_nodes, u, z; - int current_layer = 0; - bool selected = false; - - while (!_set_operations(GraphEdit::IS_EQUAL, q, u)) { - _set_operations(GraphEdit::DIFFERENCE, p, u); - for (const StringName &E : p) { - HashSet<StringName> n = r_upper_neighbours[E]; - if (_set_operations(GraphEdit::IS_SUBSET, n, z)) { - Vector<StringName> t; - t.push_back(E); - if (!l.has(current_layer)) { - l.insert(current_layer, Vector<StringName>{}); - } - selected = true; - t.append_array(l[current_layer]); - l.insert(current_layer, t); - HashSet<StringName> V; - V.insert(E); - _set_operations(GraphEdit::UNION, u, V); - } - } - if (!selected) { - current_layer++; - uint32_t previous_size_z = z.size(); - _set_operations(GraphEdit::UNION, z, u); - if (z.size() == previous_size_z) { - WARN_PRINT("Graph contains cycle(s). The cycle(s) will not be rearranged accurately."); - Vector<StringName> t; - if (l.has(0)) { - t.append_array(l[0]); - } - for (const StringName &E : p) { - t.push_back(E); - } - l.insert(0, t); - break; - } - } - selected = false; - } - - return l; -} - -Vector<StringName> GraphEdit::_split(const Vector<StringName> &r_layer, const HashMap<StringName, Dictionary> &r_crossings) { - if (!r_layer.size()) { - return Vector<StringName>(); - } - - StringName p = r_layer[Math::random(0, r_layer.size() - 1)]; - Vector<StringName> left; - Vector<StringName> right; - - for (int i = 0; i < r_layer.size(); i++) { - if (p != r_layer[i]) { - StringName q = r_layer[i]; - int cross_pq = r_crossings[p][q]; - int cross_qp = r_crossings[q][p]; - if (cross_pq > cross_qp) { - left.push_back(q); - } else { - right.push_back(q); - } - } - } - - left.push_back(p); - left.append_array(right); - return left; -} - -void GraphEdit::_horizontal_alignment(Dictionary &r_root, Dictionary &r_align, const HashMap<int, Vector<StringName>> &r_layers, const HashMap<StringName, HashSet<StringName>> &r_upper_neighbours, const HashSet<StringName> &r_selected_nodes) { - for (const StringName &E : r_selected_nodes) { - r_root[E] = E; - r_align[E] = E; - } - - if (r_layers.size() == 1) { - return; - } - - for (unsigned int i = 1; i < r_layers.size(); i++) { - Vector<StringName> lower_layer = r_layers[i]; - Vector<StringName> upper_layer = r_layers[i - 1]; - int r = -1; - - for (int j = 0; j < lower_layer.size(); j++) { - Vector<Pair<int, StringName>> up; - StringName current_node = lower_layer[j]; - for (int k = 0; k < upper_layer.size(); k++) { - StringName adjacent_neighbour = upper_layer[k]; - if (r_upper_neighbours[current_node].has(adjacent_neighbour)) { - up.push_back(Pair<int, StringName>(k, adjacent_neighbour)); - } - } - - int start = (up.size() - 1) / 2; - int end = (up.size() - 1) % 2 ? start + 1 : start; - for (int p = start; p <= end; p++) { - StringName Align = r_align[current_node]; - if (Align == current_node && r < up[p].first) { - r_align[up[p].second] = lower_layer[j]; - r_root[current_node] = r_root[up[p].second]; - r_align[current_node] = r_root[up[p].second]; - r = up[p].first; - } - } - } - } -} - -void GraphEdit::_crossing_minimisation(HashMap<int, Vector<StringName>> &r_layers, const HashMap<StringName, HashSet<StringName>> &r_upper_neighbours) { - if (r_layers.size() == 1) { - return; - } - - for (unsigned int i = 1; i < r_layers.size(); i++) { - Vector<StringName> upper_layer = r_layers[i - 1]; - Vector<StringName> lower_layer = r_layers[i]; - HashMap<StringName, Dictionary> c; - - for (int j = 0; j < lower_layer.size(); j++) { - StringName p = lower_layer[j]; - Dictionary d; - - for (int k = 0; k < lower_layer.size(); k++) { - unsigned int crossings = 0; - StringName q = lower_layer[k]; - - if (j != k) { - for (int h = 1; h < upper_layer.size(); h++) { - if (r_upper_neighbours[p].has(upper_layer[h])) { - for (int g = 0; g < h; g++) { - if (r_upper_neighbours[q].has(upper_layer[g])) { - crossings++; - } - } - } - } - } - d[q] = crossings; - } - c.insert(p, d); - } - - r_layers.insert(i, _split(lower_layer, c)); - } -} - -void GraphEdit::_calculate_inner_shifts(Dictionary &r_inner_shifts, const Dictionary &r_root, const Dictionary &r_node_names, const Dictionary &r_align, const HashSet<StringName> &r_block_heads, const HashMap<StringName, Pair<int, int>> &r_port_info) { - for (const StringName &E : r_block_heads) { - real_t left = 0; - StringName u = E; - StringName v = r_align[u]; - while (u != v && (StringName)r_root[u] != v) { - String _connection = String(u) + " " + String(v); - GraphNode *gfrom = Object::cast_to<GraphNode>(r_node_names[u]); - GraphNode *gto = Object::cast_to<GraphNode>(r_node_names[v]); - - Pair<int, int> ports = r_port_info[_connection]; - int pfrom = ports.first; - int pto = ports.second; - Vector2 frompos = gfrom->get_connection_output_position(pfrom); - Vector2 topos = gto->get_connection_input_position(pto); - - real_t s = (real_t)r_inner_shifts[u] + (frompos.y - topos.y) / zoom; - r_inner_shifts[v] = s; - left = MIN(left, s); - - u = v; - v = (StringName)r_align[v]; - } - - u = E; - do { - r_inner_shifts[u] = (real_t)r_inner_shifts[u] - left; - u = (StringName)r_align[u]; - } while (u != E); - } -} - -float GraphEdit::_calculate_threshold(StringName p_v, StringName p_w, const Dictionary &r_node_names, const HashMap<int, Vector<StringName>> &r_layers, const Dictionary &r_root, const Dictionary &r_align, const Dictionary &r_inner_shift, real_t p_current_threshold, const HashMap<StringName, Vector2> &r_node_positions) { -#define MAX_ORDER 2147483647 -#define ORDER(node, layers) \ - for (unsigned int i = 0; i < layers.size(); i++) { \ - int index = layers[i].find(node); \ - if (index > 0) { \ - order = index; \ - break; \ - } \ - order = MAX_ORDER; \ - } - - int order = MAX_ORDER; - float threshold = p_current_threshold; - if (p_v == p_w) { - int min_order = MAX_ORDER; - Connection incoming; - for (List<Connection>::Element *E = connections.front(); E; E = E->next()) { - if (E->get().to == p_w) { - ORDER(E->get().from, r_layers); - if (min_order > order) { - min_order = order; - incoming = E->get(); - } - } - } - - if (incoming.from != StringName()) { - GraphNode *gfrom = Object::cast_to<GraphNode>(r_node_names[incoming.from]); - GraphNode *gto = Object::cast_to<GraphNode>(r_node_names[p_w]); - Vector2 frompos = gfrom->get_connection_output_position(incoming.from_port); - Vector2 topos = gto->get_connection_input_position(incoming.to_port); - - //If connected block node is selected, calculate thershold or add current block to list - if (gfrom->is_selected()) { - Vector2 connected_block_pos = r_node_positions[r_root[incoming.from]]; - if (connected_block_pos.y != FLT_MAX) { - //Connected block is placed. Calculate threshold - threshold = connected_block_pos.y + (real_t)r_inner_shift[incoming.from] - (real_t)r_inner_shift[p_w] + frompos.y - topos.y; - } - } - } - } - if (threshold == FLT_MIN && (StringName)r_align[p_w] == p_v) { - //This time, pick an outgoing edge and repeat as above! - int min_order = MAX_ORDER; - Connection outgoing; - for (List<Connection>::Element *E = connections.front(); E; E = E->next()) { - if (E->get().from == p_w) { - ORDER(E->get().to, r_layers); - if (min_order > order) { - min_order = order; - outgoing = E->get(); - } - } - } - - if (outgoing.to != StringName()) { - GraphNode *gfrom = Object::cast_to<GraphNode>(r_node_names[p_w]); - GraphNode *gto = Object::cast_to<GraphNode>(r_node_names[outgoing.to]); - Vector2 frompos = gfrom->get_connection_output_position(outgoing.from_port); - Vector2 topos = gto->get_connection_input_position(outgoing.to_port); - - //If connected block node is selected, calculate thershold or add current block to list - if (gto->is_selected()) { - Vector2 connected_block_pos = r_node_positions[r_root[outgoing.to]]; - if (connected_block_pos.y != FLT_MAX) { - //Connected block is placed. Calculate threshold - threshold = connected_block_pos.y + (real_t)r_inner_shift[outgoing.to] - (real_t)r_inner_shift[p_w] + frompos.y - topos.y; - } - } - } - } -#undef MAX_ORDER -#undef ORDER - return threshold; -} - -void GraphEdit::_place_block(StringName p_v, float p_delta, const HashMap<int, Vector<StringName>> &r_layers, const Dictionary &r_root, const Dictionary &r_align, const Dictionary &r_node_name, const Dictionary &r_inner_shift, Dictionary &r_sink, Dictionary &r_shift, HashMap<StringName, Vector2> &r_node_positions) { -#define PRED(node, layers) \ - for (unsigned int i = 0; i < layers.size(); i++) { \ - int index = layers[i].find(node); \ - if (index > 0) { \ - predecessor = layers[i][index - 1]; \ - break; \ - } \ - predecessor = StringName(); \ - } - - StringName predecessor; - StringName successor; - Vector2 pos = r_node_positions[p_v]; - - if (pos.y == FLT_MAX) { - pos.y = 0; - bool initial = false; - StringName w = p_v; - real_t threshold = FLT_MIN; - do { - PRED(w, r_layers); - if (predecessor != StringName()) { - StringName u = r_root[predecessor]; - _place_block(u, p_delta, r_layers, r_root, r_align, r_node_name, r_inner_shift, r_sink, r_shift, r_node_positions); - threshold = _calculate_threshold(p_v, w, r_node_name, r_layers, r_root, r_align, r_inner_shift, threshold, r_node_positions); - if ((StringName)r_sink[p_v] == p_v) { - r_sink[p_v] = r_sink[u]; - } - - Vector2 predecessor_root_pos = r_node_positions[u]; - Vector2 predecessor_node_size = Object::cast_to<GraphNode>(r_node_name[predecessor])->get_size(); - if (r_sink[p_v] != r_sink[u]) { - real_t sc = pos.y + (real_t)r_inner_shift[w] - predecessor_root_pos.y - (real_t)r_inner_shift[predecessor] - predecessor_node_size.y - p_delta; - r_shift[r_sink[u]] = MIN(sc, (real_t)r_shift[r_sink[u]]); - } else { - real_t sb = predecessor_root_pos.y + (real_t)r_inner_shift[predecessor] + predecessor_node_size.y - (real_t)r_inner_shift[w] + p_delta; - sb = MAX(sb, threshold); - if (initial) { - pos.y = sb; - } else { - pos.y = MAX(pos.y, sb); - } - initial = false; - } - } - threshold = _calculate_threshold(p_v, w, r_node_name, r_layers, r_root, r_align, r_inner_shift, threshold, r_node_positions); - w = r_align[w]; - } while (w != p_v); - r_node_positions.insert(p_v, pos); - } - -#undef PRED -} - void GraphEdit::arrange_nodes() { - if (!arranging_graph) { - arranging_graph = true; - } else { - return; - } - - Dictionary node_names; - HashSet<StringName> selected_nodes; - - bool arrange_entire_graph = true; - for (int i = get_child_count() - 1; i >= 0; i--) { - GraphNode *gn = Object::cast_to<GraphNode>(get_child(i)); - if (!gn) { - continue; - } - - node_names[gn->get_name()] = gn; - - if (gn->is_selected()) { - arrange_entire_graph = false; - } - } - - HashMap<StringName, HashSet<StringName>> upper_neighbours; - HashMap<StringName, Pair<int, int>> port_info; - Vector2 origin(FLT_MAX, FLT_MAX); - - float gap_v = 100.0f; - float gap_h = 100.0f; - - for (int i = get_child_count() - 1; i >= 0; i--) { - GraphNode *gn = Object::cast_to<GraphNode>(get_child(i)); - if (!gn) { - continue; - } - - if (gn->is_selected() || arrange_entire_graph) { - selected_nodes.insert(gn->get_name()); - HashSet<StringName> s; - for (List<Connection>::Element *E = connections.front(); E; E = E->next()) { - GraphNode *p_from = Object::cast_to<GraphNode>(node_names[E->get().from]); - if (E->get().to == gn->get_name() && (p_from->is_selected() || arrange_entire_graph) && E->get().to != E->get().from) { - if (!s.has(p_from->get_name())) { - s.insert(p_from->get_name()); - } - String s_connection = String(p_from->get_name()) + " " + String(E->get().to); - StringName _connection(s_connection); - Pair<int, int> ports(E->get().from_port, E->get().to_port); - if (port_info.has(_connection)) { - Pair<int, int> p_ports = port_info[_connection]; - if (p_ports.first < ports.first) { - ports = p_ports; - } - } - port_info.insert(_connection, ports); - } - } - upper_neighbours.insert(gn->get_name(), s); - } - } - - if (!selected_nodes.size()) { - arranging_graph = false; - return; - } - - HashMap<int, Vector<StringName>> layers = _layering(selected_nodes, upper_neighbours); - _crossing_minimisation(layers, upper_neighbours); - - Dictionary root, align, sink, shift; - _horizontal_alignment(root, align, layers, upper_neighbours, selected_nodes); - - HashMap<StringName, Vector2> new_positions; - Vector2 default_position(FLT_MAX, FLT_MAX); - Dictionary inner_shift; - HashSet<StringName> block_heads; - - for (const StringName &E : selected_nodes) { - inner_shift[E] = 0.0f; - sink[E] = E; - shift[E] = FLT_MAX; - new_positions.insert(E, default_position); - if ((StringName)root[E] == E) { - block_heads.insert(E); - } - } - - _calculate_inner_shifts(inner_shift, root, node_names, align, block_heads, port_info); - - for (const StringName &E : block_heads) { - _place_block(E, gap_v, layers, root, align, node_names, inner_shift, sink, shift, new_positions); - } - origin.y = Object::cast_to<GraphNode>(node_names[layers[0][0]])->get_position_offset().y - (new_positions[layers[0][0]].y + (float)inner_shift[layers[0][0]]); - origin.x = Object::cast_to<GraphNode>(node_names[layers[0][0]])->get_position_offset().x; - - for (const StringName &E : block_heads) { - StringName u = E; - float start_from = origin.y + new_positions[E].y; - do { - Vector2 cal_pos; - cal_pos.y = start_from + (real_t)inner_shift[u]; - new_positions.insert(u, cal_pos); - u = align[u]; - } while (u != E); - } - - // Compute horizontal coordinates individually for layers to get uniform gap. - float start_from = origin.x; - float largest_node_size = 0.0f; - - for (unsigned int i = 0; i < layers.size(); i++) { - Vector<StringName> layer = layers[i]; - for (int j = 0; j < layer.size(); j++) { - float current_node_size = Object::cast_to<GraphNode>(node_names[layer[j]])->get_size().x; - largest_node_size = MAX(largest_node_size, current_node_size); - } - - for (int j = 0; j < layer.size(); j++) { - float current_node_size = Object::cast_to<GraphNode>(node_names[layer[j]])->get_size().x; - Vector2 cal_pos = new_positions[layer[j]]; - - if (current_node_size == largest_node_size) { - cal_pos.x = start_from; - } else { - float current_node_start_pos = start_from; - if (current_node_size < largest_node_size / 2) { - if (!(i || j)) { - start_from -= (largest_node_size - current_node_size); - } - current_node_start_pos = start_from + largest_node_size - current_node_size; - } - cal_pos.x = current_node_start_pos; - } - new_positions.insert(layer[j], cal_pos); - } - - start_from += largest_node_size + gap_h; - largest_node_size = 0.0f; - } - - emit_signal(SNAME("begin_node_move")); - for (const StringName &E : selected_nodes) { - GraphNode *gn = Object::cast_to<GraphNode>(node_names[E]); - gn->set_drag(true); - Vector2 pos = (new_positions[E]); - - if (is_using_snap()) { - const int snap = get_snap(); - pos = pos.snapped(Vector2(snap, snap)); - } - gn->set_position_offset(pos); - gn->set_drag(false); - } - emit_signal(SNAME("end_node_move")); - arranging_graph = false; + arranger->arrange_nodes(); } void GraphEdit::_bind_methods() { @@ -2294,8 +1775,8 @@ void GraphEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("get_connection_list"), &GraphEdit::_get_connection_list); ClassDB::bind_method(D_METHOD("clear_connections"), &GraphEdit::clear_connections); ClassDB::bind_method(D_METHOD("force_connection_drag_end"), &GraphEdit::force_connection_drag_end); - ClassDB::bind_method(D_METHOD("get_scroll_ofs"), &GraphEdit::get_scroll_ofs); - ClassDB::bind_method(D_METHOD("set_scroll_ofs", "offset"), &GraphEdit::set_scroll_ofs); + ClassDB::bind_method(D_METHOD("get_scroll_offset"), &GraphEdit::get_scroll_offset); + ClassDB::bind_method(D_METHOD("set_scroll_offset", "offset"), &GraphEdit::set_scroll_offset); ClassDB::bind_method(D_METHOD("add_valid_right_disconnect_type", "type"), &GraphEdit::add_valid_right_disconnect_type); ClassDB::bind_method(D_METHOD("remove_valid_right_disconnect_type", "type"), &GraphEdit::remove_valid_right_disconnect_type); @@ -2324,11 +1805,14 @@ void GraphEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("set_show_zoom_label", "enable"), &GraphEdit::set_show_zoom_label); ClassDB::bind_method(D_METHOD("is_showing_zoom_label"), &GraphEdit::is_showing_zoom_label); - ClassDB::bind_method(D_METHOD("set_snap", "pixels"), &GraphEdit::set_snap); - ClassDB::bind_method(D_METHOD("get_snap"), &GraphEdit::get_snap); + ClassDB::bind_method(D_METHOD("set_show_grid", "enable"), &GraphEdit::set_show_grid); + ClassDB::bind_method(D_METHOD("is_showing_grid"), &GraphEdit::is_showing_grid); + + ClassDB::bind_method(D_METHOD("set_snapping_enabled", "enable"), &GraphEdit::set_snapping_enabled); + ClassDB::bind_method(D_METHOD("is_snapping_enabled"), &GraphEdit::is_snapping_enabled); - ClassDB::bind_method(D_METHOD("set_use_snap", "enable"), &GraphEdit::set_use_snap); - ClassDB::bind_method(D_METHOD("is_using_snap"), &GraphEdit::is_using_snap); + ClassDB::bind_method(D_METHOD("set_snapping_distance", "pixels"), &GraphEdit::set_snapping_distance); + ClassDB::bind_method(D_METHOD("get_snapping_distance"), &GraphEdit::get_snapping_distance); ClassDB::bind_method(D_METHOD("set_connection_lines_curvature", "curvature"), &GraphEdit::set_connection_lines_curvature); ClassDB::bind_method(D_METHOD("get_connection_lines_curvature"), &GraphEdit::get_connection_lines_curvature); @@ -2353,11 +1837,10 @@ void GraphEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("set_right_disconnects", "enable"), &GraphEdit::set_right_disconnects); ClassDB::bind_method(D_METHOD("is_right_disconnects_enabled"), &GraphEdit::is_right_disconnects_enabled); - ClassDB::bind_method(D_METHOD("_update_scroll_offset"), &GraphEdit::_update_scroll_offset); GDVIRTUAL_BIND(_is_in_input_hotzone, "in_node", "in_port", "mouse_position"); GDVIRTUAL_BIND(_is_in_output_hotzone, "in_node", "in_port", "mouse_position"); - ClassDB::bind_method(D_METHOD("get_zoom_hbox"), &GraphEdit::get_zoom_hbox); + ClassDB::bind_method(D_METHOD("get_menu_hbox"), &GraphEdit::get_menu_hbox); ClassDB::bind_method(D_METHOD("arrange_nodes"), &GraphEdit::arrange_nodes); @@ -2367,9 +1850,10 @@ void GraphEdit::_bind_methods() { GDVIRTUAL_BIND(_is_node_hover_valid, "from_node", "from_port", "to_node", "to_port"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "right_disconnects"), "set_right_disconnects", "is_right_disconnects_enabled"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "scroll_offset", PROPERTY_HINT_NONE, "suffix:px"), "set_scroll_ofs", "get_scroll_ofs"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "snap_distance", PROPERTY_HINT_NONE, "suffix:px"), "set_snap", "get_snap"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_snap"), "set_use_snap", "is_using_snap"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "scroll_offset", PROPERTY_HINT_NONE, "suffix:px"), "set_scroll_offset", "get_scroll_offset"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_grid"), "set_show_grid", "is_showing_grid"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "snapping_enabled"), "set_snapping_enabled", "is_snapping_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "snapping_distance", PROPERTY_HINT_NONE, "suffix:px"), "set_snapping_distance", "get_snapping_distance"); ADD_PROPERTY(PropertyInfo(Variant::INT, "panning_scheme", PROPERTY_HINT_ENUM, "Scroll Zooms,Scroll Pans"), "set_panning_scheme", "get_panning_scheme"); ADD_GROUP("Connection Lines", "connection_lines"); @@ -2435,93 +1919,103 @@ GraphEdit::GraphEdit() { top_layer->connect("focus_exited", callable_mp(panner.ptr(), &ViewPanner::release_pan_key)); connections_layer = memnew(Control); - add_child(connections_layer, false, INTERNAL_MODE_FRONT); + add_child(connections_layer, false); connections_layer->connect("draw", callable_mp(this, &GraphEdit::_connections_layer_draw)); - connections_layer->set_name("CLAYER"); - connections_layer->set_disable_visibility_clip(true); // so it can draw freely and be offset + connections_layer->set_name("_connection_layer"); + connections_layer->set_disable_visibility_clip(true); // Necessary, so it can draw freely and be offset. connections_layer->set_mouse_filter(MOUSE_FILTER_IGNORE); - h_scroll = memnew(HScrollBar); - h_scroll->set_name("_h_scroll"); - top_layer->add_child(h_scroll); + h_scrollbar = memnew(HScrollBar); + h_scrollbar->set_name("_h_scroll"); + top_layer->add_child(h_scrollbar); - v_scroll = memnew(VScrollBar); - v_scroll->set_name("_v_scroll"); - top_layer->add_child(v_scroll); + v_scrollbar = memnew(VScrollBar); + v_scrollbar->set_name("_v_scroll"); + top_layer->add_child(v_scrollbar); - //set large minmax so it can scroll even if not resized yet - h_scroll->set_min(-10000); - h_scroll->set_max(10000); + // Set large minmax so it can scroll even if not resized yet. + h_scrollbar->set_min(-10000); + h_scrollbar->set_max(10000); - v_scroll->set_min(-10000); - v_scroll->set_max(10000); + v_scrollbar->set_min(-10000); + v_scrollbar->set_max(10000); - h_scroll->connect("value_changed", callable_mp(this, &GraphEdit::_scroll_moved)); - v_scroll->connect("value_changed", callable_mp(this, &GraphEdit::_scroll_moved)); + h_scrollbar->connect("value_changed", callable_mp(this, &GraphEdit::_scroll_moved)); + v_scrollbar->connect("value_changed", callable_mp(this, &GraphEdit::_scroll_moved)); - zoom_hb = memnew(HBoxContainer); - top_layer->add_child(zoom_hb); - zoom_hb->set_position(Vector2(10, 10)); + menu_hbox = memnew(HBoxContainer); + top_layer->add_child(menu_hbox); + menu_hbox->set_position(Vector2(10, 10)); zoom_label = memnew(Label); - zoom_hb->add_child(zoom_label); + menu_hbox->add_child(zoom_label); zoom_label->set_visible(false); zoom_label->set_v_size_flags(Control::SIZE_SHRINK_CENTER); zoom_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER); zoom_label->set_custom_minimum_size(Size2(48, 0)); _update_zoom_label(); - zoom_minus = memnew(Button); - zoom_minus->set_flat(true); - zoom_hb->add_child(zoom_minus); - zoom_minus->set_tooltip_text(RTR("Zoom Out")); - zoom_minus->connect("pressed", callable_mp(this, &GraphEdit::_zoom_minus)); - zoom_minus->set_focus_mode(FOCUS_NONE); - - zoom_reset = memnew(Button); - zoom_reset->set_flat(true); - zoom_hb->add_child(zoom_reset); - zoom_reset->set_tooltip_text(RTR("Zoom Reset")); - zoom_reset->connect("pressed", callable_mp(this, &GraphEdit::_zoom_reset)); - zoom_reset->set_focus_mode(FOCUS_NONE); - - zoom_plus = memnew(Button); - zoom_plus->set_flat(true); - zoom_hb->add_child(zoom_plus); - zoom_plus->set_tooltip_text(RTR("Zoom In")); - zoom_plus->connect("pressed", callable_mp(this, &GraphEdit::_zoom_plus)); - zoom_plus->set_focus_mode(FOCUS_NONE); - - snap_button = memnew(Button); - snap_button->set_flat(true); - snap_button->set_toggle_mode(true); - snap_button->set_tooltip_text(RTR("Enable snap and show grid.")); - snap_button->connect("pressed", callable_mp(this, &GraphEdit::_snap_toggled)); - snap_button->set_pressed(true); - snap_button->set_focus_mode(FOCUS_NONE); - zoom_hb->add_child(snap_button); - - snap_amount = memnew(SpinBox); - snap_amount->set_min(5); - snap_amount->set_max(100); - snap_amount->set_step(1); - snap_amount->set_value(20); - snap_amount->connect("value_changed", callable_mp(this, &GraphEdit::_snap_value_changed)); - zoom_hb->add_child(snap_amount); + zoom_minus_button = memnew(Button); + zoom_minus_button->set_flat(true); + menu_hbox->add_child(zoom_minus_button); + zoom_minus_button->set_tooltip_text(RTR("Zoom Out")); + zoom_minus_button->connect("pressed", callable_mp(this, &GraphEdit::_zoom_minus)); + zoom_minus_button->set_focus_mode(FOCUS_NONE); + + zoom_reset_button = memnew(Button); + zoom_reset_button->set_flat(true); + menu_hbox->add_child(zoom_reset_button); + zoom_reset_button->set_tooltip_text(RTR("Zoom Reset")); + zoom_reset_button->connect("pressed", callable_mp(this, &GraphEdit::_zoom_reset)); + zoom_reset_button->set_focus_mode(FOCUS_NONE); + + zoom_plus_button = memnew(Button); + zoom_plus_button->set_flat(true); + menu_hbox->add_child(zoom_plus_button); + zoom_plus_button->set_tooltip_text(RTR("Zoom In")); + zoom_plus_button->connect("pressed", callable_mp(this, &GraphEdit::_zoom_plus)); + zoom_plus_button->set_focus_mode(FOCUS_NONE); + + show_grid_button = memnew(Button); + show_grid_button->set_flat(true); + show_grid_button->set_toggle_mode(true); + show_grid_button->set_tooltip_text(RTR("Toggle the visual grid.")); + show_grid_button->connect("pressed", callable_mp(this, &GraphEdit::_show_grid_toggled)); + show_grid_button->set_pressed(true); + show_grid_button->set_focus_mode(FOCUS_NONE); + menu_hbox->add_child(show_grid_button); + + toggle_snapping_button = memnew(Button); + toggle_snapping_button->set_flat(true); + toggle_snapping_button->set_toggle_mode(true); + toggle_snapping_button->set_tooltip_text(RTR("Toggle snapping to the grid.")); + toggle_snapping_button->connect("pressed", callable_mp(this, &GraphEdit::_snapping_toggled)); + toggle_snapping_button->set_pressed(snapping_enabled); + toggle_snapping_button->set_focus_mode(FOCUS_NONE); + menu_hbox->add_child(toggle_snapping_button); + + snapping_distance_spinbox = memnew(SpinBox); + snapping_distance_spinbox->set_min(GRID_MIN_SNAPPING_DISTANCE); + snapping_distance_spinbox->set_max(GRID_MAX_SNAPPING_DISTANCE); + snapping_distance_spinbox->set_step(1); + snapping_distance_spinbox->set_value(snapping_distance); + snapping_distance_spinbox->set_tooltip_text(RTR("Change the snapping distance.")); + snapping_distance_spinbox->connect("value_changed", callable_mp(this, &GraphEdit::_snapping_distance_changed)); + menu_hbox->add_child(snapping_distance_spinbox); minimap_button = memnew(Button); minimap_button->set_flat(true); minimap_button->set_toggle_mode(true); - minimap_button->set_tooltip_text(RTR("Enable grid minimap.")); + minimap_button->set_tooltip_text(RTR("Toggle the graph minimap.")); minimap_button->connect("pressed", callable_mp(this, &GraphEdit::_minimap_toggled)); - minimap_button->set_pressed(true); + minimap_button->set_pressed(show_grid); minimap_button->set_focus_mode(FOCUS_NONE); - zoom_hb->add_child(minimap_button); + menu_hbox->add_child(minimap_button); layout_button = memnew(Button); layout_button->set_flat(true); - zoom_hb->add_child(layout_button); - layout_button->set_tooltip_text(RTR("Arrange nodes.")); + menu_hbox->add_child(layout_button); + layout_button->set_tooltip_text(RTR("Automatically arrange selected nodes.")); layout_button->connect("pressed", callable_mp(this, &GraphEdit::arrange_nodes)); layout_button->set_focus_mode(FOCUS_NONE); @@ -2536,11 +2030,13 @@ GraphEdit::GraphEdit() { minimap->set_custom_minimum_size(Vector2(50, 50)); minimap->set_size(minimap_size); minimap->set_anchors_preset(Control::PRESET_BOTTOM_RIGHT); - minimap->set_offset(Side::SIDE_LEFT, -minimap_size.x - MINIMAP_OFFSET); - minimap->set_offset(Side::SIDE_TOP, -minimap_size.y - MINIMAP_OFFSET); + minimap->set_offset(Side::SIDE_LEFT, -minimap_size.width - MINIMAP_OFFSET); + minimap->set_offset(Side::SIDE_TOP, -minimap_size.height - MINIMAP_OFFSET); minimap->set_offset(Side::SIDE_RIGHT, -MINIMAP_OFFSET); minimap->set_offset(Side::SIDE_BOTTOM, -MINIMAP_OFFSET); minimap->connect("draw", callable_mp(this, &GraphEdit::_minimap_draw)); set_clip_contents(true); + + arranger = Ref<GraphEditArranger>(memnew(GraphEditArranger(this))); } diff --git a/scene/gui/graph_edit.h b/scene/gui/graph_edit.h index 8b02fbfddc..614e9b9695 100644 --- a/scene/gui/graph_edit.h +++ b/scene/gui/graph_edit.h @@ -39,6 +39,7 @@ #include "scene/gui/spin_box.h" class GraphEdit; +class GraphEditArranger; class ViewPanner; class GraphEditFilter : public Control { @@ -47,6 +48,7 @@ class GraphEditFilter : public Control { friend class GraphEdit; friend class GraphEditMinimap; GraphEdit *ge = nullptr; + virtual bool has_point(const Point2 &p_point) const override; public: @@ -58,9 +60,9 @@ class GraphEditMinimap : public Control { friend class GraphEdit; friend class GraphEditFilter; + GraphEdit *ge = nullptr; -protected: public: GraphEditMinimap(GraphEdit *p_edit); @@ -97,8 +99,8 @@ class GraphEdit : public Control { public: struct Connection { - StringName from; - StringName to; + StringName from_node; + StringName to_node; int from_port = 0; int to_port = 0; float activity = 0.0; @@ -111,33 +113,57 @@ public: }; private: - Label *zoom_label = nullptr; - Button *zoom_minus = nullptr; - Button *zoom_reset = nullptr; - Button *zoom_plus = nullptr; + struct ConnectionType { + union { + struct { + uint32_t type_a; + uint32_t type_b; + }; + uint64_t key = 0; + }; - Button *snap_button = nullptr; - SpinBox *snap_amount = nullptr; + static uint32_t hash(const ConnectionType &p_conn) { + return hash_one_uint64(p_conn.key); + } + bool operator==(const ConnectionType &p_type) const { + return key == p_type.key; + } + + ConnectionType(uint32_t a = 0, uint32_t b = 0) { + type_a = a; + type_b = b; + } + }; + + Label *zoom_label = nullptr; + Button *zoom_minus_button = nullptr; + Button *zoom_reset_button = nullptr; + Button *zoom_plus_button = nullptr; + Button *toggle_snapping_button = nullptr; + SpinBox *snapping_distance_spinbox = nullptr; + Button *show_grid_button = nullptr; Button *minimap_button = nullptr; Button *layout_button = nullptr; - HScrollBar *h_scroll = nullptr; - VScrollBar *v_scroll = nullptr; + HScrollBar *h_scrollbar = nullptr; + VScrollBar *v_scrollbar = nullptr; float port_hotzone_inner_extent = 0.0; float port_hotzone_outer_extent = 0.0; Ref<ViewPanner> panner; bool warped_panning = true; - void _pan_callback(Vector2 p_scroll_vec, Ref<InputEvent> p_event); - void _zoom_callback(float p_zoom_factor, Vector2 p_origin, Ref<InputEvent> p_event); bool arrange_nodes_button_hidden = false; + bool snapping_enabled = true; + int snapping_distance = 20; + bool show_grid = true; + bool connecting = false; - StringName connecting_from; + String connecting_from; bool connecting_out = false; int connecting_index = 0; int connecting_type = 0; @@ -146,8 +172,10 @@ private: Vector2 connecting_to; StringName connecting_target_to; int connecting_target_index = 0; + bool just_disconnected = false; bool connecting_valid = false; + Vector2 click_pos; PanningScheme panning_scheme = SCROLL_ZOOMS; @@ -162,19 +190,14 @@ private: float zoom_min = 0.0; float zoom_max = 0.0; - void _zoom_minus(); - void _zoom_reset(); - void _zoom_plus(); - void _update_zoom_label(); - bool box_selecting = false; bool box_selection_mode_additive = false; Point2 box_selecting_from; Point2 box_selecting_to; Rect2 box_selecting_rect; - List<GraphNode *> previous_selected; + List<GraphNode *> prev_selected; - bool setting_scroll_ofs = false; + bool setting_scroll_offset = false; bool right_disconnects = false; bool updating = false; bool awaiting_scroll_offset_update = false; @@ -184,102 +207,69 @@ private: float lines_curvature = 0.5f; bool lines_antialiased = true; + HBoxContainer *menu_hbox = nullptr; + Control *connections_layer = nullptr; + GraphEditFilter *top_layer = nullptr; + GraphEditMinimap *minimap = nullptr; + + Ref<GraphEditArranger> arranger; + + HashSet<ConnectionType, ConnectionType> valid_connection_types; + HashSet<int> valid_left_disconnect_types; + HashSet<int> valid_right_disconnect_types; + + void _scroll_callback(Vector2 p_scroll_vec, bool p_alt); + void _pan_callback(Vector2 p_scroll_vec, Ref<InputEvent> p_event); + void _zoom_callback(float p_zoom_factor, Vector2 p_origin, Ref<InputEvent> p_event); + + void _zoom_minus(); + void _zoom_reset(); + void _zoom_plus(); + void _update_zoom_label(); + PackedVector2Array get_connection_line(const Vector2 &p_from, const Vector2 &p_to); void _draw_connection_line(CanvasItem *p_where, const Vector2 &p_from, const Vector2 &p_to, const Color &p_color, const Color &p_to_color, float p_width, float p_zoom); void _graph_node_selected(Node *p_gn); void _graph_node_deselected(Node *p_gn); - void _graph_node_raised(Node *p_gn); + void _graph_node_moved_to_front(Node *p_gn); + void _graph_node_resized(Vector2 p_new_minsize, Node *p_gn); void _graph_node_moved(Node *p_gn); void _graph_node_slot_updated(int p_index, Node *p_gn); void _update_scroll(); + void _update_scroll_offset(); void _scroll_moved(double); virtual void gui_input(const Ref<InputEvent> &p_ev) override; - - Control *connections_layer = nullptr; - GraphEditFilter *top_layer = nullptr; - GraphEditMinimap *minimap = nullptr; void _top_layer_input(const Ref<InputEvent> &p_ev); - bool is_in_input_hotzone(GraphNode *p_node, int p_port, const Vector2 &p_mouse_pos, const Vector2i &p_port_size); - bool is_in_output_hotzone(GraphNode *p_node, int p_port, const Vector2 &p_mouse_pos, const Vector2i &p_port_size); - bool is_in_port_hotzone(const Vector2 &pos, const Vector2 &p_mouse_pos, const Vector2i &p_port_size, bool p_left); + bool is_in_input_hotzone(GraphNode *p_graph_node, int p_port, const Vector2 &p_mouse_pos, const Vector2i &p_port_size); + bool is_in_output_hotzone(GraphNode *p_graph_node, int p_port, const Vector2 &p_mouse_pos, const Vector2i &p_port_size); + bool is_in_port_hotzone(const Vector2 &p_pos, const Vector2 &p_mouse_pos, const Vector2i &p_port_size, bool p_left); void _top_layer_draw(); void _connections_layer_draw(); void _minimap_draw(); - void _update_scroll_offset(); TypedArray<Dictionary> _get_connection_list() const; - bool lines_on_bg = false; - - struct ConnType { - union { - struct { - uint32_t type_a; - uint32_t type_b; - }; - uint64_t key = 0; - }; - - static uint32_t hash(const ConnType &p_conn) { - return hash_one_uint64(p_conn.key); - } - bool operator==(const ConnType &p_type) const { - return key == p_type.key; - } - - ConnType(uint32_t a = 0, uint32_t b = 0) { - type_a = a; - type_b = b; - } - }; - - HashSet<ConnType, ConnType> valid_connection_types; - HashSet<int> valid_left_disconnect_types; - HashSet<int> valid_right_disconnect_types; - - HashMap<StringName, Vector<GraphNode *>> comment_enclosed_nodes; - void _update_comment_enclosed_nodes_list(GraphNode *p_node, HashMap<StringName, Vector<GraphNode *>> &p_comment_enclosed_nodes); - void _set_drag_comment_enclosed_nodes(GraphNode *p_node, HashMap<StringName, Vector<GraphNode *>> &p_comment_enclosed_nodes, bool p_drag); - void _set_position_of_comment_enclosed_nodes(GraphNode *p_node, HashMap<StringName, Vector<GraphNode *>> &p_comment_enclosed_nodes, Vector2 p_pos); - - HBoxContainer *zoom_hb = nullptr; - friend class GraphEditFilter; bool _filter_input(const Point2 &p_point); - void _snap_toggled(); - void _snap_value_changed(double); + void _snapping_toggled(); + void _snapping_distance_changed(double); + void _show_grid_toggled(); friend class GraphEditMinimap; void _minimap_toggled(); bool _check_clickable_control(Control *p_control, const Vector2 &r_mouse_pos, const Vector2 &p_offset); - bool arranging_graph = false; - - enum SET_OPERATIONS { - IS_EQUAL, - IS_SUBSET, - DIFFERENCE, - UNION, - }; - - int _set_operations(SET_OPERATIONS p_operation, HashSet<StringName> &r_u, const HashSet<StringName> &r_v); - HashMap<int, Vector<StringName>> _layering(const HashSet<StringName> &r_selected_nodes, const HashMap<StringName, HashSet<StringName>> &r_upper_neighbours); - Vector<StringName> _split(const Vector<StringName> &r_layer, const HashMap<StringName, Dictionary> &r_crossings); - void _horizontal_alignment(Dictionary &r_root, Dictionary &r_align, const HashMap<int, Vector<StringName>> &r_layers, const HashMap<StringName, HashSet<StringName>> &r_upper_neighbours, const HashSet<StringName> &r_selected_nodes); - void _crossing_minimisation(HashMap<int, Vector<StringName>> &r_layers, const HashMap<StringName, HashSet<StringName>> &r_upper_neighbours); - void _calculate_inner_shifts(Dictionary &r_inner_shifts, const Dictionary &r_root, const Dictionary &r_node_names, const Dictionary &r_align, const HashSet<StringName> &r_block_heads, const HashMap<StringName, Pair<int, int>> &r_port_info); - float _calculate_threshold(StringName p_v, StringName p_w, const Dictionary &r_node_names, const HashMap<int, Vector<StringName>> &r_layers, const Dictionary &r_root, const Dictionary &r_align, const Dictionary &r_inner_shift, real_t p_current_threshold, const HashMap<StringName, Vector2> &r_node_positions); - void _place_block(StringName p_v, float p_delta, const HashMap<int, Vector<StringName>> &r_layers, const Dictionary &r_root, const Dictionary &r_align, const Dictionary &r_node_name, const Dictionary &r_inner_shift, Dictionary &r_sink, Dictionary &r_shift, HashMap<StringName, Vector2> &r_node_positions); - protected: static void _bind_methods(); + virtual void add_child_notify(Node *p_child) override; virtual void remove_child_notify(Node *p_child) override; + void _notification(int p_what); GDVIRTUAL2RC(Vector<Vector2>, _get_connection_line, Vector2, Vector2) @@ -337,6 +327,7 @@ public: GraphEditFilter *get_top_layer() const { return top_layer; } GraphEditMinimap *get_minimap() const { return minimap; } + void get_connection_list(List<Connection> *r_connections) const; void set_right_disconnects(bool p_enable); @@ -348,16 +339,19 @@ public: void add_valid_left_disconnect_type(int p_type); void remove_valid_left_disconnect_type(int p_type); - void set_scroll_ofs(const Vector2 &p_ofs); - Vector2 get_scroll_ofs() const; + void set_scroll_offset(const Vector2 &p_ofs); + Vector2 get_scroll_offset() const; void set_selected(Node *p_child); - void set_use_snap(bool p_enable); - bool is_using_snap() const; + void set_snapping_enabled(bool p_enable); + bool is_snapping_enabled() const; + + void set_snapping_distance(int p_snapping_distance); + int get_snapping_distance() const; - int get_snap() const; - void set_snap(int p_snap); + void set_show_grid(bool p_enable); + bool is_showing_grid() const; void set_connection_lines_curvature(float p_curvature); float get_connection_lines_curvature() const; @@ -368,7 +362,7 @@ public: void set_connection_lines_antialiased(bool p_antialiased); bool is_connection_lines_antialiased() const; - HBoxContainer *get_zoom_hbox(); + HBoxContainer *get_menu_hbox(); Ref<ViewPanner> get_panner(); void set_warped_panning(bool p_warped); diff --git a/scene/gui/graph_edit_arranger.cpp b/scene/gui/graph_edit_arranger.cpp new file mode 100644 index 0000000000..f4d9dcbf95 --- /dev/null +++ b/scene/gui/graph_edit_arranger.cpp @@ -0,0 +1,565 @@ +/**************************************************************************/ +/* graph_edit_arranger.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "graph_edit_arranger.h" + +#include "scene/gui/graph_edit.h" + +void GraphEditArranger::arrange_nodes() { + ERR_FAIL_COND(!graph_edit); + + if (!arranging_graph) { + arranging_graph = true; + } else { + return; + } + + Dictionary node_names; + HashSet<StringName> selected_nodes; + + bool arrange_entire_graph = true; + for (int i = graph_edit->get_child_count() - 1; i >= 0; i--) { + GraphNode *graph_element = Object::cast_to<GraphNode>(graph_edit->get_child(i)); + if (!graph_element) { + continue; + } + + node_names[graph_element->get_name()] = graph_element; + + if (graph_element->is_selected()) { + arrange_entire_graph = false; + } + } + + HashMap<StringName, HashSet<StringName>> upper_neighbours; + HashMap<StringName, Pair<int, int>> port_info; + Vector2 origin(FLT_MAX, FLT_MAX); + + float gap_v = 100.0f; + float gap_h = 100.0f; + + for (int i = graph_edit->get_child_count() - 1; i >= 0; i--) { + GraphNode *graph_element = Object::cast_to<GraphNode>(graph_edit->get_child(i)); + if (!graph_element) { + continue; + } + + if (graph_element->is_selected() || arrange_entire_graph) { + selected_nodes.insert(graph_element->get_name()); + HashSet<StringName> s; + List<GraphEdit::Connection> connection_list; + graph_edit->get_connection_list(&connection_list); + for (List<GraphEdit::Connection>::Element *E = connection_list.front(); E; E = E->next()) { + GraphNode *p_from = Object::cast_to<GraphNode>(node_names[E->get().from_node]); + if (E->get().to_node == graph_element->get_name() && (p_from->is_selected() || arrange_entire_graph) && E->get().to_node != E->get().from_node) { + if (!s.has(p_from->get_name())) { + s.insert(p_from->get_name()); + } + String s_connection = String(p_from->get_name()) + " " + String(E->get().to_node); + StringName _connection(s_connection); + Pair<int, int> ports(E->get().from_port, E->get().to_port); + if (port_info.has(_connection)) { + Pair<int, int> p_ports = port_info[_connection]; + if (p_ports.first < ports.first) { + ports = p_ports; + } + } + port_info.insert(_connection, ports); + } + } + upper_neighbours.insert(graph_element->get_name(), s); + } + } + + if (!selected_nodes.size()) { + arranging_graph = false; + return; + } + + HashMap<int, Vector<StringName>> layers = _layering(selected_nodes, upper_neighbours); + _crossing_minimisation(layers, upper_neighbours); + + Dictionary root, align, sink, shift; + _horizontal_alignment(root, align, layers, upper_neighbours, selected_nodes); + + HashMap<StringName, Vector2> new_positions; + Vector2 default_position(FLT_MAX, FLT_MAX); + Dictionary inner_shift; + HashSet<StringName> block_heads; + + for (const StringName &E : selected_nodes) { + inner_shift[E] = 0.0f; + sink[E] = E; + shift[E] = FLT_MAX; + new_positions.insert(E, default_position); + if ((StringName)root[E] == E) { + block_heads.insert(E); + } + } + + _calculate_inner_shifts(inner_shift, root, node_names, align, block_heads, port_info); + + for (const StringName &E : block_heads) { + _place_block(E, gap_v, layers, root, align, node_names, inner_shift, sink, shift, new_positions); + } + origin.y = Object::cast_to<GraphNode>(node_names[layers[0][0]])->get_position_offset().y - (new_positions[layers[0][0]].y + (float)inner_shift[layers[0][0]]); + origin.x = Object::cast_to<GraphNode>(node_names[layers[0][0]])->get_position_offset().x; + + for (const StringName &E : block_heads) { + StringName u = E; + float start_from = origin.y + new_positions[E].y; + do { + Vector2 cal_pos; + cal_pos.y = start_from + (real_t)inner_shift[u]; + new_positions.insert(u, cal_pos); + u = align[u]; + } while (u != E); + } + + // Compute horizontal coordinates individually for layers to get uniform gap. + float start_from = origin.x; + float largest_node_size = 0.0f; + + for (unsigned int i = 0; i < layers.size(); i++) { + Vector<StringName> layer = layers[i]; + for (int j = 0; j < layer.size(); j++) { + float current_node_size = Object::cast_to<GraphNode>(node_names[layer[j]])->get_size().x; + largest_node_size = MAX(largest_node_size, current_node_size); + } + + for (int j = 0; j < layer.size(); j++) { + float current_node_size = Object::cast_to<GraphNode>(node_names[layer[j]])->get_size().x; + Vector2 cal_pos = new_positions[layer[j]]; + + if (current_node_size == largest_node_size) { + cal_pos.x = start_from; + } else { + float current_node_start_pos = start_from; + if (current_node_size < largest_node_size / 2) { + if (!(i || j)) { + start_from -= (largest_node_size - current_node_size); + } + current_node_start_pos = start_from + largest_node_size - current_node_size; + } + cal_pos.x = current_node_start_pos; + } + new_positions.insert(layer[j], cal_pos); + } + + start_from += largest_node_size + gap_h; + largest_node_size = 0.0f; + } + + graph_edit->emit_signal(SNAME("begin_node_move")); + for (const StringName &E : selected_nodes) { + GraphNode *graph_node = Object::cast_to<GraphNode>(node_names[E]); + graph_node->set_drag(true); + Vector2 pos = (new_positions[E]); + + if (graph_edit->is_snapping_enabled()) { + float snapping_distance = graph_edit->get_snapping_distance(); + pos = pos.snapped(Vector2(snapping_distance, snapping_distance)); + } + graph_node->set_position_offset(pos); + graph_node->set_drag(false); + } + graph_edit->emit_signal(SNAME("end_node_move")); + arranging_graph = false; +} + +int GraphEditArranger::_set_operations(SET_OPERATIONS p_operation, HashSet<StringName> &r_u, const HashSet<StringName> &r_v) { + switch (p_operation) { + case GraphEditArranger::IS_EQUAL: { + for (const StringName &E : r_u) { + if (!r_v.has(E)) { + return 0; + } + } + return r_u.size() == r_v.size(); + } break; + case GraphEditArranger::IS_SUBSET: { + if (r_u.size() == r_v.size() && !r_u.size()) { + return 1; + } + for (const StringName &E : r_u) { + if (!r_v.has(E)) { + return 0; + } + } + return 1; + } break; + case GraphEditArranger::DIFFERENCE: { + for (HashSet<StringName>::Iterator E = r_u.begin(); E;) { + HashSet<StringName>::Iterator N = E; + ++N; + if (r_v.has(*E)) { + r_u.remove(E); + } + E = N; + } + return r_u.size(); + } break; + case GraphEditArranger::UNION: { + for (const StringName &E : r_v) { + if (!r_u.has(E)) { + r_u.insert(E); + } + } + return r_u.size(); + } break; + default: + break; + } + return -1; +} + +HashMap<int, Vector<StringName>> GraphEditArranger::_layering(const HashSet<StringName> &r_selected_nodes, const HashMap<StringName, HashSet<StringName>> &r_upper_neighbours) { + HashMap<int, Vector<StringName>> l; + + HashSet<StringName> p = r_selected_nodes, q = r_selected_nodes, u, z; + int current_layer = 0; + bool selected = false; + + while (!_set_operations(GraphEditArranger::IS_EQUAL, q, u)) { + _set_operations(GraphEditArranger::DIFFERENCE, p, u); + for (const StringName &E : p) { + HashSet<StringName> n = r_upper_neighbours[E]; + if (_set_operations(GraphEditArranger::IS_SUBSET, n, z)) { + Vector<StringName> t; + t.push_back(E); + if (!l.has(current_layer)) { + l.insert(current_layer, Vector<StringName>{}); + } + selected = true; + t.append_array(l[current_layer]); + l.insert(current_layer, t); + HashSet<StringName> V; + V.insert(E); + _set_operations(GraphEditArranger::UNION, u, V); + } + } + if (!selected) { + current_layer++; + uint32_t previous_size_z = z.size(); + _set_operations(GraphEditArranger::UNION, z, u); + if (z.size() == previous_size_z) { + WARN_PRINT("Graph contains cycle(s). The cycle(s) will not be rearranged accurately."); + Vector<StringName> t; + if (l.has(0)) { + t.append_array(l[0]); + } + for (const StringName &E : p) { + t.push_back(E); + } + l.insert(0, t); + break; + } + } + selected = false; + } + + return l; +} + +Vector<StringName> GraphEditArranger::_split(const Vector<StringName> &r_layer, const HashMap<StringName, Dictionary> &r_crossings) { + if (!r_layer.size()) { + return Vector<StringName>(); + } + + StringName p = r_layer[Math::random(0, r_layer.size() - 1)]; + Vector<StringName> left; + Vector<StringName> right; + + for (int i = 0; i < r_layer.size(); i++) { + if (p != r_layer[i]) { + StringName q = r_layer[i]; + int cross_pq = r_crossings[p][q]; + int cross_qp = r_crossings[q][p]; + if (cross_pq > cross_qp) { + left.push_back(q); + } else { + right.push_back(q); + } + } + } + + left.push_back(p); + left.append_array(right); + return left; +} + +void GraphEditArranger::_horizontal_alignment(Dictionary &r_root, Dictionary &r_align, const HashMap<int, Vector<StringName>> &r_layers, const HashMap<StringName, HashSet<StringName>> &r_upper_neighbours, const HashSet<StringName> &r_selected_nodes) { + for (const StringName &E : r_selected_nodes) { + r_root[E] = E; + r_align[E] = E; + } + + if (r_layers.size() == 1) { + return; + } + + for (unsigned int i = 1; i < r_layers.size(); i++) { + Vector<StringName> lower_layer = r_layers[i]; + Vector<StringName> upper_layer = r_layers[i - 1]; + int r = -1; + + for (int j = 0; j < lower_layer.size(); j++) { + Vector<Pair<int, StringName>> up; + StringName current_node = lower_layer[j]; + for (int k = 0; k < upper_layer.size(); k++) { + StringName adjacent_neighbour = upper_layer[k]; + if (r_upper_neighbours[current_node].has(adjacent_neighbour)) { + up.push_back(Pair<int, StringName>(k, adjacent_neighbour)); + } + } + + int start = (up.size() - 1) / 2; + int end = (up.size() - 1) % 2 ? start + 1 : start; + for (int p = start; p <= end; p++) { + StringName Align = r_align[current_node]; + if (Align == current_node && r < up[p].first) { + r_align[up[p].second] = lower_layer[j]; + r_root[current_node] = r_root[up[p].second]; + r_align[current_node] = r_root[up[p].second]; + r = up[p].first; + } + } + } + } +} + +void GraphEditArranger::_crossing_minimisation(HashMap<int, Vector<StringName>> &r_layers, const HashMap<StringName, HashSet<StringName>> &r_upper_neighbours) { + if (r_layers.size() == 1) { + return; + } + + for (unsigned int i = 1; i < r_layers.size(); i++) { + Vector<StringName> upper_layer = r_layers[i - 1]; + Vector<StringName> lower_layer = r_layers[i]; + HashMap<StringName, Dictionary> c; + + for (int j = 0; j < lower_layer.size(); j++) { + StringName p = lower_layer[j]; + Dictionary d; + + for (int k = 0; k < lower_layer.size(); k++) { + unsigned int crossings = 0; + StringName q = lower_layer[k]; + + if (j != k) { + for (int h = 1; h < upper_layer.size(); h++) { + if (r_upper_neighbours[p].has(upper_layer[h])) { + for (int g = 0; g < h; g++) { + if (r_upper_neighbours[q].has(upper_layer[g])) { + crossings++; + } + } + } + } + } + d[q] = crossings; + } + c.insert(p, d); + } + + r_layers.insert(i, _split(lower_layer, c)); + } +} + +void GraphEditArranger::_calculate_inner_shifts(Dictionary &r_inner_shifts, const Dictionary &r_root, const Dictionary &r_node_names, const Dictionary &r_align, const HashSet<StringName> &r_block_heads, const HashMap<StringName, Pair<int, int>> &r_port_info) { + for (const StringName &E : r_block_heads) { + real_t left = 0; + StringName u = E; + StringName v = r_align[u]; + while (u != v && (StringName)r_root[u] != v) { + String _connection = String(u) + " " + String(v); + + GraphNode *gnode_from = Object::cast_to<GraphNode>(r_node_names[u]); + GraphNode *gnode_to = Object::cast_to<GraphNode>(r_node_names[v]); + + Pair<int, int> ports = r_port_info[_connection]; + int port_from = ports.first; + int port_to = ports.second; + + Vector2 pos_from = gnode_from->get_connection_output_position(port_from) * graph_edit->get_zoom(); + Vector2 pos_to = gnode_to->get_connection_input_position(port_to) * graph_edit->get_zoom(); + + real_t s = (real_t)r_inner_shifts[u] + (pos_from.y - pos_to.y) / graph_edit->get_zoom(); + r_inner_shifts[v] = s; + left = MIN(left, s); + + u = v; + v = (StringName)r_align[v]; + } + + u = E; + do { + r_inner_shifts[u] = (real_t)r_inner_shifts[u] - left; + u = (StringName)r_align[u]; + } while (u != E); + } +} + +float GraphEditArranger::_calculate_threshold(StringName p_v, StringName p_w, const Dictionary &r_node_names, const HashMap<int, Vector<StringName>> &r_layers, const Dictionary &r_root, const Dictionary &r_align, const Dictionary &r_inner_shift, real_t p_current_threshold, const HashMap<StringName, Vector2> &r_node_positions) { +#define MAX_ORDER 2147483647 +#define ORDER(node, layers) \ + for (unsigned int i = 0; i < layers.size(); i++) { \ + int index = layers[i].find(node); \ + if (index > 0) { \ + order = index; \ + break; \ + } \ + order = MAX_ORDER; \ + } + + int order = MAX_ORDER; + float threshold = p_current_threshold; + if (p_v == p_w) { + int min_order = MAX_ORDER; + GraphEdit::Connection incoming; + List<GraphEdit::Connection> connection_list; + graph_edit->get_connection_list(&connection_list); + for (List<GraphEdit::Connection>::Element *E = connection_list.front(); E; E = E->next()) { + if (E->get().to_node == p_w) { + ORDER(E->get().from_node, r_layers); + if (min_order > order) { + min_order = order; + incoming = E->get(); + } + } + } + + if (incoming.from_node != StringName()) { + GraphNode *gnode_from = Object::cast_to<GraphNode>(r_node_names[incoming.from_node]); + GraphNode *gnode_to = Object::cast_to<GraphNode>(r_node_names[p_w]); + Vector2 pos_from = gnode_from->get_connection_output_position(incoming.from_port) * graph_edit->get_zoom(); + Vector2 pos_to = gnode_to->get_connection_input_position(incoming.to_port) * graph_edit->get_zoom(); + + // If connected block node is selected, calculate thershold or add current block to list. + if (gnode_from->is_selected()) { + Vector2 connected_block_pos = r_node_positions[r_root[incoming.from_node]]; + if (connected_block_pos.y != FLT_MAX) { + //Connected block is placed, calculate threshold. + threshold = connected_block_pos.y + (real_t)r_inner_shift[incoming.from_node] - (real_t)r_inner_shift[p_w] + pos_from.y - pos_to.y; + } + } + } + } + if (threshold == FLT_MIN && (StringName)r_align[p_w] == p_v) { + // This time, pick an outgoing edge and repeat as above! + int min_order = MAX_ORDER; + GraphEdit::Connection outgoing; + List<GraphEdit::Connection> connection_list; + graph_edit->get_connection_list(&connection_list); + for (List<GraphEdit::Connection>::Element *E = connection_list.front(); E; E = E->next()) { + if (E->get().from_node == p_w) { + ORDER(E->get().to_node, r_layers); + if (min_order > order) { + min_order = order; + outgoing = E->get(); + } + } + } + + if (outgoing.to_node != StringName()) { + GraphNode *gnode_from = Object::cast_to<GraphNode>(r_node_names[p_w]); + GraphNode *gnode_to = Object::cast_to<GraphNode>(r_node_names[outgoing.to_node]); + Vector2 pos_from = gnode_from->get_connection_output_position(outgoing.from_port) * graph_edit->get_zoom(); + Vector2 pos_to = gnode_to->get_connection_input_position(outgoing.to_port) * graph_edit->get_zoom(); + + // If connected block node is selected, calculate thershold or add current block to list. + if (gnode_to->is_selected()) { + Vector2 connected_block_pos = r_node_positions[r_root[outgoing.to_node]]; + if (connected_block_pos.y != FLT_MAX) { + //Connected block is placed. Calculate threshold + threshold = connected_block_pos.y + (real_t)r_inner_shift[outgoing.to_node] - (real_t)r_inner_shift[p_w] + pos_from.y - pos_to.y; + } + } + } + } +#undef MAX_ORDER +#undef ORDER + return threshold; +} + +void GraphEditArranger::_place_block(StringName p_v, float p_delta, const HashMap<int, Vector<StringName>> &r_layers, const Dictionary &r_root, const Dictionary &r_align, const Dictionary &r_node_name, const Dictionary &r_inner_shift, Dictionary &r_sink, Dictionary &r_shift, HashMap<StringName, Vector2> &r_node_positions) { +#define PRED(node, layers) \ + for (unsigned int i = 0; i < layers.size(); i++) { \ + int index = layers[i].find(node); \ + if (index > 0) { \ + predecessor = layers[i][index - 1]; \ + break; \ + } \ + predecessor = StringName(); \ + } + + StringName predecessor; + StringName successor; + Vector2 pos = r_node_positions[p_v]; + + if (pos.y == FLT_MAX) { + pos.y = 0; + bool initial = false; + StringName w = p_v; + real_t threshold = FLT_MIN; + do { + PRED(w, r_layers); + if (predecessor != StringName()) { + StringName u = r_root[predecessor]; + _place_block(u, p_delta, r_layers, r_root, r_align, r_node_name, r_inner_shift, r_sink, r_shift, r_node_positions); + threshold = _calculate_threshold(p_v, w, r_node_name, r_layers, r_root, r_align, r_inner_shift, threshold, r_node_positions); + if ((StringName)r_sink[p_v] == p_v) { + r_sink[p_v] = r_sink[u]; + } + + Vector2 predecessor_root_pos = r_node_positions[u]; + Vector2 predecessor_node_size = Object::cast_to<GraphNode>(r_node_name[predecessor])->get_size(); + if (r_sink[p_v] != r_sink[u]) { + real_t sc = pos.y + (real_t)r_inner_shift[w] - predecessor_root_pos.y - (real_t)r_inner_shift[predecessor] - predecessor_node_size.y - p_delta; + r_shift[r_sink[u]] = MIN(sc, (real_t)r_shift[r_sink[u]]); + } else { + real_t sb = predecessor_root_pos.y + (real_t)r_inner_shift[predecessor] + predecessor_node_size.y - (real_t)r_inner_shift[w] + p_delta; + sb = MAX(sb, threshold); + if (initial) { + pos.y = sb; + } else { + pos.y = MAX(pos.y, sb); + } + initial = false; + } + } + threshold = _calculate_threshold(p_v, w, r_node_name, r_layers, r_root, r_align, r_inner_shift, threshold, r_node_positions); + w = r_align[w]; + } while (w != p_v); + r_node_positions.insert(p_v, pos); + } + +#undef PRED +} diff --git a/scene/gui/graph_edit_arranger.h b/scene/gui/graph_edit_arranger.h new file mode 100644 index 0000000000..e79944e5dd --- /dev/null +++ b/scene/gui/graph_edit_arranger.h @@ -0,0 +1,67 @@ +/**************************************************************************/ +/* graph_edit_arranger.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef GRAPH_EDIT_ARRANGER_H +#define GRAPH_EDIT_ARRANGER_H + +#include "core/object/ref_counted.h" +#include "core/templates/hash_map.h" +#include "core/templates/hash_set.h" + +class GraphEdit; + +class GraphEditArranger : public RefCounted { + enum SET_OPERATIONS { + IS_EQUAL, + IS_SUBSET, + DIFFERENCE, + UNION, + }; + + GraphEdit *graph_edit = nullptr; + bool arranging_graph = false; + + int _set_operations(SET_OPERATIONS p_operation, HashSet<StringName> &r_u, const HashSet<StringName> &r_v); + HashMap<int, Vector<StringName>> _layering(const HashSet<StringName> &r_selected_nodes, const HashMap<StringName, HashSet<StringName>> &r_upper_neighbours); + Vector<StringName> _split(const Vector<StringName> &r_layer, const HashMap<StringName, Dictionary> &r_crossings); + void _horizontal_alignment(Dictionary &r_root, Dictionary &r_align, const HashMap<int, Vector<StringName>> &r_layers, const HashMap<StringName, HashSet<StringName>> &r_upper_neighbours, const HashSet<StringName> &r_selected_nodes); + void _crossing_minimisation(HashMap<int, Vector<StringName>> &r_layers, const HashMap<StringName, HashSet<StringName>> &r_upper_neighbours); + void _calculate_inner_shifts(Dictionary &r_inner_shifts, const Dictionary &r_root, const Dictionary &r_node_names, const Dictionary &r_align, const HashSet<StringName> &r_block_heads, const HashMap<StringName, Pair<int, int>> &r_port_info); + float _calculate_threshold(StringName p_v, StringName p_w, const Dictionary &r_node_names, const HashMap<int, Vector<StringName>> &r_layers, const Dictionary &r_root, const Dictionary &r_align, const Dictionary &r_inner_shift, real_t p_current_threshold, const HashMap<StringName, Vector2> &r_node_positions); + void _place_block(StringName p_v, float p_delta, const HashMap<int, Vector<StringName>> &r_layers, const Dictionary &r_root, const Dictionary &r_align, const Dictionary &r_node_name, const Dictionary &r_inner_shift, Dictionary &r_sink, Dictionary &r_shift, HashMap<StringName, Vector2> &r_node_positions); + +public: + void arrange_nodes(); + + GraphEditArranger(GraphEdit *p_graph_edit) : + graph_edit(p_graph_edit) {} +}; + +#endif // GRAPH_EDIT_ARRANGER_H diff --git a/scene/gui/graph_node.cpp b/scene/gui/graph_node.cpp index b0517caab0..2223beafda 100644 --- a/scene/gui/graph_node.cpp +++ b/scene/gui/graph_node.cpp @@ -286,37 +286,12 @@ void GraphNode::_resort() { connpos_dirty = true; } -bool GraphNode::has_point(const Point2 &p_point) const { - if (comment) { - Ref<StyleBox> comment_sb = get_theme_stylebox(SNAME("comment")); - Ref<Texture2D> resizer = get_theme_icon(SNAME("resizer")); - - if (Rect2(get_size() - resizer->get_size(), resizer->get_size()).has_point(p_point)) { - return true; - } - - if (Rect2(0, 0, get_size().width, comment_sb->get_margin(SIDE_TOP)).has_point(p_point)) { - return true; - } - - return false; - - } else { - return Control::has_point(p_point); - } -} - void GraphNode::_notification(int p_what) { switch (p_what) { case NOTIFICATION_DRAW: { Ref<StyleBox> sb; - if (comment) { - sb = get_theme_stylebox(selected ? SNAME("comment_focus") : SNAME("comment")); - - } else { - sb = get_theme_stylebox(selected ? SNAME("selected_frame") : SNAME("frame")); - } + sb = get_theme_stylebox(selected ? SNAME("selected_frame") : SNAME("frame")); Ref<StyleBox> sb_slot = get_theme_stylebox(SNAME("slot")); @@ -440,7 +415,7 @@ void GraphNode::_shape() { void GraphNode::_edit_set_position(const Point2 &p_position) { GraphEdit *graph = Object::cast_to<GraphEdit>(get_parent()); if (graph) { - Point2 offset = (p_position + graph->get_scroll_ofs()) * graph->get_zoom(); + Point2 offset = (p_position + graph->get_scroll_offset()) * graph->get_zoom(); set_position_offset(offset); } set_position(p_position); @@ -1003,19 +978,6 @@ GraphNode::Overlay GraphNode::get_overlay() const { return overlay; } -void GraphNode::set_comment(bool p_enable) { - if (comment == p_enable) { - return; - } - - comment = p_enable; - queue_redraw(); -} - -bool GraphNode::is_comment() const { - return comment; -} - void GraphNode::set_resizable(bool p_enable) { if (resizable == p_enable) { return; @@ -1115,9 +1077,6 @@ void GraphNode::_bind_methods() { ClassDB::bind_method(D_METHOD("set_position_offset", "offset"), &GraphNode::set_position_offset); ClassDB::bind_method(D_METHOD("get_position_offset"), &GraphNode::get_position_offset); - ClassDB::bind_method(D_METHOD("set_comment", "comment"), &GraphNode::set_comment); - ClassDB::bind_method(D_METHOD("is_comment"), &GraphNode::is_comment); - ClassDB::bind_method(D_METHOD("set_resizable", "resizable"), &GraphNode::set_resizable); ClassDB::bind_method(D_METHOD("is_resizable"), &GraphNode::is_resizable); @@ -1157,7 +1116,6 @@ void GraphNode::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draggable"), "set_draggable", "is_draggable"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "selectable"), "set_selectable", "is_selectable"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "selected"), "set_selected", "is_selected"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "comment"), "set_comment", "is_comment"); ADD_PROPERTY(PropertyInfo(Variant::INT, "overlay", PROPERTY_HINT_ENUM, "Disabled,Breakpoint,Position"), "set_overlay", "get_overlay"); ADD_GROUP("BiDi", ""); diff --git a/scene/gui/graph_node.h b/scene/gui/graph_node.h index 7ba2e6db94..873683bc62 100644 --- a/scene/gui/graph_node.h +++ b/scene/gui/graph_node.h @@ -124,8 +124,6 @@ protected: void _validate_property(PropertyInfo &p_property) const; public: - bool has_point(const Point2 &p_point) const override; - void set_slot(int p_idx, bool p_enable_left, int p_type_left, const Color &p_color_left, bool p_enable_right, int p_type_right, const Color &p_color_right, const Ref<Texture2D> &p_custom_left = Ref<Texture2D>(), const Ref<Texture2D> &p_custom_right = Ref<Texture2D>(), bool p_draw_stylebox = true); void clear_slot(int p_idx); void clear_all_slots(); @@ -189,9 +187,6 @@ public: void set_overlay(Overlay p_overlay); Overlay get_overlay() const; - void set_comment(bool p_enable); - bool is_comment() const; - void set_resizable(bool p_enable); bool is_resizable() const; diff --git a/scene/gui/label.cpp b/scene/gui/label.cpp index 1b48b9165d..cbaf1c372e 100644 --- a/scene/gui/label.cpp +++ b/scene/gui/label.cpp @@ -31,7 +31,6 @@ #include "label.h" #include "core/config/project_settings.h" -#include "core/core_string_names.h" #include "core/string/print_string.h" #include "core/string/translation.h" @@ -784,11 +783,11 @@ void Label::_invalidate() { void Label::set_label_settings(const Ref<LabelSettings> &p_settings) { if (settings != p_settings) { if (settings.is_valid()) { - settings->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(this, &Label::_invalidate)); + settings->disconnect_changed(callable_mp(this, &Label::_invalidate)); } settings = p_settings; if (settings.is_valid()) { - settings->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &Label::_invalidate), CONNECT_REFERENCE_COUNTED); + settings->connect_changed(callable_mp(this, &Label::_invalidate), CONNECT_REFERENCE_COUNTED); } _invalidate(); } diff --git a/scene/gui/nine_patch_rect.cpp b/scene/gui/nine_patch_rect.cpp index 68e2db7cfd..e2ae824e60 100644 --- a/scene/gui/nine_patch_rect.cpp +++ b/scene/gui/nine_patch_rect.cpp @@ -30,7 +30,6 @@ #include "nine_patch_rect.h" -#include "core/core_string_names.h" #include "scene/scene_string_names.h" #include "servers/rendering_server.h" @@ -101,13 +100,13 @@ void NinePatchRect::set_texture(const Ref<Texture2D> &p_tex) { } if (texture.is_valid()) { - texture->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(this, &NinePatchRect::_texture_changed)); + texture->disconnect_changed(callable_mp(this, &NinePatchRect::_texture_changed)); } texture = p_tex; if (texture.is_valid()) { - texture->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &NinePatchRect::_texture_changed)); + texture->connect_changed(callable_mp(this, &NinePatchRect::_texture_changed)); } queue_redraw(); diff --git a/scene/gui/option_button.cpp b/scene/gui/option_button.cpp index 71d64c8bff..8138a66f64 100644 --- a/scene/gui/option_button.cpp +++ b/scene/gui/option_button.cpp @@ -445,13 +445,11 @@ void OptionButton::_select_int(int p_which) { void OptionButton::_refresh_size_cache() { cache_refresh_pending = false; - if (!fit_to_longest_item) { - return; - } - - _cached_size = Vector2(); - for (int i = 0; i < get_item_count(); i++) { - _cached_size = _cached_size.max(get_minimum_size_for_text_and_icon(popup->get_item_xl_text(i), get_item_icon(i))); + if (fit_to_longest_item) { + _cached_size = Vector2(); + for (int i = 0; i < get_item_count(); i++) { + _cached_size = _cached_size.max(get_minimum_size_for_text_and_icon(popup->get_item_xl_text(i), get_item_icon(i))); + } } update_minimum_size(); } diff --git a/scene/gui/popup_menu.cpp b/scene/gui/popup_menu.cpp index e2324f3fe5..40db8deaac 100644 --- a/scene/gui/popup_menu.cpp +++ b/scene/gui/popup_menu.cpp @@ -1968,7 +1968,7 @@ void PopupMenu::clear() { void PopupMenu::_ref_shortcut(Ref<Shortcut> p_sc) { if (!shortcut_refcount.has(p_sc)) { shortcut_refcount[p_sc] = 1; - p_sc->connect("changed", callable_mp(this, &PopupMenu::_shortcut_changed)); + p_sc->connect_changed(callable_mp(this, &PopupMenu::_shortcut_changed)); } else { shortcut_refcount[p_sc] += 1; } @@ -1978,7 +1978,7 @@ void PopupMenu::_unref_shortcut(Ref<Shortcut> p_sc) { ERR_FAIL_COND(!shortcut_refcount.has(p_sc)); shortcut_refcount[p_sc]--; if (shortcut_refcount[p_sc] == 0) { - p_sc->disconnect("changed", callable_mp(this, &PopupMenu::_shortcut_changed)); + p_sc->disconnect_changed(callable_mp(this, &PopupMenu::_shortcut_changed)); shortcut_refcount.erase(p_sc); } } @@ -2308,7 +2308,7 @@ void PopupMenu::_bind_methods() { ADD_SIGNAL(MethodInfo("menu_changed")); } -void PopupMenu::popup(const Rect2 &p_bounds) { +void PopupMenu::popup(const Rect2i &p_bounds) { moved = Vector2(); popup_time_msec = OS::get_singleton()->get_ticks_msec(); Popup::popup(p_bounds); diff --git a/scene/gui/popup_menu.h b/scene/gui/popup_menu.h index b4655f13ae..5ad9cd4303 100644 --- a/scene/gui/popup_menu.h +++ b/scene/gui/popup_menu.h @@ -312,7 +312,7 @@ public: void set_allow_search(bool p_allow); bool get_allow_search() const; - virtual void popup(const Rect2 &p_bounds = Rect2()); + virtual void popup(const Rect2i &p_bounds = Rect2i()) override; void take_mouse_focus(); diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp index 37cb1d2dba..db7196fb07 100644 --- a/scene/gui/rich_text_label.cpp +++ b/scene/gui/rich_text_label.cpp @@ -35,7 +35,8 @@ #include "core/os/keyboard.h" #include "core/os/os.h" #include "core/string/translation.h" -#include "label.h" +#include "scene/gui/label.h" +#include "scene/resources/atlas_texture.h" #include "scene/scene_string_names.h" #include "servers/display_server.h" @@ -3454,6 +3455,7 @@ void RichTextLabel::push_fade(int p_start_index, int p_length) { _stop_thread(); MutexLock data_lock(data_mutex); + ERR_FAIL_COND(current->type == ITEM_TABLE); ItemFade *item = memnew(ItemFade); item->starting_index = p_start_index; item->length = p_length; @@ -3464,6 +3466,7 @@ void RichTextLabel::push_shake(int p_strength = 10, float p_rate = 24.0f, bool p _stop_thread(); MutexLock data_lock(data_mutex); + ERR_FAIL_COND(current->type == ITEM_TABLE); ItemShake *item = memnew(ItemShake); item->strength = p_strength; item->rate = p_rate; @@ -3475,6 +3478,7 @@ void RichTextLabel::push_wave(float p_frequency = 1.0f, float p_amplitude = 10.0 _stop_thread(); MutexLock data_lock(data_mutex); + ERR_FAIL_COND(current->type == ITEM_TABLE); ItemWave *item = memnew(ItemWave); item->frequency = p_frequency; item->amplitude = p_amplitude; @@ -3486,6 +3490,7 @@ void RichTextLabel::push_tornado(float p_frequency = 1.0f, float p_radius = 10.0 _stop_thread(); MutexLock data_lock(data_mutex); + ERR_FAIL_COND(current->type == ITEM_TABLE); ItemTornado *item = memnew(ItemTornado); item->frequency = p_frequency; item->radius = p_radius; @@ -3497,6 +3502,7 @@ void RichTextLabel::push_rainbow(float p_saturation, float p_value, float p_freq _stop_thread(); MutexLock data_lock(data_mutex); + ERR_FAIL_COND(current->type == ITEM_TABLE); ItemRainbow *item = memnew(ItemRainbow); item->frequency = p_frequency; item->saturation = p_saturation; @@ -3541,6 +3547,7 @@ void RichTextLabel::push_customfx(Ref<RichTextEffect> p_custom_effect, Dictionar _stop_thread(); MutexLock data_lock(data_mutex); + ERR_FAIL_COND(current->type == ITEM_TABLE); ItemCustomFX *item = memnew(ItemCustomFX); item->custom_effect = p_custom_effect; item->char_fx_transform->environment = p_environment; @@ -3549,6 +3556,15 @@ void RichTextLabel::push_customfx(Ref<RichTextEffect> p_custom_effect, Dictionar set_process_internal(true); } +void RichTextLabel::push_context() { + _stop_thread(); + MutexLock data_lock(data_mutex); + + ERR_FAIL_COND(current->type == ITEM_TABLE); + ItemContext *item = memnew(ItemContext); + _add_item(item, true); +} + void RichTextLabel::set_table_column_expand(int p_column, bool p_expand, int p_ratio) { _stop_thread(); MutexLock data_lock(data_mutex); @@ -3642,6 +3658,31 @@ void RichTextLabel::pop() { current = current->parent; } +void RichTextLabel::pop_context() { + _stop_thread(); + MutexLock data_lock(data_mutex); + + ERR_FAIL_NULL(current->parent); + + while (current->parent && current != main) { + if (current->type == ITEM_FRAME) { + current_frame = static_cast<ItemFrame *>(current)->parent_frame; + } else if (current->type == ITEM_CONTEXT) { + current = current->parent; + return; + } + current = current->parent; + } +} + +void RichTextLabel::pop_all() { + _stop_thread(); + MutexLock data_lock(data_mutex); + + current = main; + current_frame = main; +} + void RichTextLabel::clear() { _stop_thread(); MutexLock data_lock(data_mutex); @@ -5551,7 +5592,10 @@ void RichTextLabel::_bind_methods() { ClassDB::bind_method(D_METHOD("push_fgcolor", "fgcolor"), &RichTextLabel::push_fgcolor); ClassDB::bind_method(D_METHOD("push_bgcolor", "bgcolor"), &RichTextLabel::push_bgcolor); ClassDB::bind_method(D_METHOD("push_customfx", "effect", "env"), &RichTextLabel::push_customfx); + ClassDB::bind_method(D_METHOD("push_context"), &RichTextLabel::push_context); + ClassDB::bind_method(D_METHOD("pop_context"), &RichTextLabel::pop_context); ClassDB::bind_method(D_METHOD("pop"), &RichTextLabel::pop); + ClassDB::bind_method(D_METHOD("pop_all"), &RichTextLabel::pop_all); ClassDB::bind_method(D_METHOD("clear"), &RichTextLabel::clear); @@ -5768,10 +5812,12 @@ int RichTextLabel::get_character_line(int p_char) { int to_line = main->first_invalid_line.load(); for (int i = 0; i < to_line; i++) { MutexLock lock(main->lines[i].text_buf->get_mutex()); - if (main->lines[i].char_offset < p_char && p_char <= main->lines[i].char_offset + main->lines[i].char_count) { + int char_offset = main->lines[i].char_offset; + int char_count = main->lines[i].char_count; + if (char_offset <= p_char && p_char < char_offset + char_count) { for (int j = 0; j < main->lines[i].text_buf->get_line_count(); j++) { Vector2i range = main->lines[i].text_buf->get_line_range(j); - if (main->lines[i].char_offset + range.x < p_char && p_char <= main->lines[i].char_offset + range.y) { + if (char_offset + range.x <= p_char && p_char <= char_offset + range.y) { return line_count; } line_count++; @@ -5786,13 +5832,11 @@ int RichTextLabel::get_character_line(int p_char) { int RichTextLabel::get_character_paragraph(int p_char) { _validate_line_caches(); - int para_count = 0; int to_line = main->first_invalid_line.load(); for (int i = 0; i < to_line; i++) { - if (main->lines[i].char_offset < p_char && p_char <= main->lines[i].char_offset + main->lines[i].char_count) { - return para_count; - } else { - para_count++; + int char_offset = main->lines[i].char_offset; + if (char_offset <= p_char && p_char < char_offset + main->lines[i].char_count) { + return i; } } return -1; diff --git a/scene/gui/rich_text_label.h b/scene/gui/rich_text_label.h index 1c543c625a..30adf03f84 100644 --- a/scene/gui/rich_text_label.h +++ b/scene/gui/rich_text_label.h @@ -32,8 +32,8 @@ #define RICH_TEXT_LABEL_H #include "core/object/worker_thread_pool.h" -#include "rich_text_effect.h" #include "scene/gui/popup_menu.h" +#include "scene/gui/rich_text_effect.h" #include "scene/gui/scroll_bar.h" #include "scene/resources/text_paragraph.h" @@ -76,7 +76,8 @@ public: ITEM_META, ITEM_HINT, ITEM_DROPCAP, - ITEM_CUSTOMFX + ITEM_CUSTOMFX, + ITEM_CONTEXT }; enum MenuItems { @@ -379,6 +380,10 @@ private: } }; + struct ItemContext : public Item { + ItemContext() { type = ITEM_CONTEXT; } + }; + ItemFrame *main = nullptr; Item *current = nullptr; ItemFrame *current_frame = nullptr; @@ -624,6 +629,7 @@ public: void push_bgcolor(const Color &p_color); void push_fgcolor(const Color &p_color); void push_customfx(Ref<RichTextEffect> p_custom_effect, Dictionary p_environment); + void push_context(); void set_table_column_expand(int p_column, bool p_expand, int p_ratio = 1); void set_cell_row_background_color(const Color &p_odd_row_bg, const Color &p_even_row_bg); void set_cell_border_color(const Color &p_color); @@ -632,6 +638,8 @@ public: int get_current_table_column() const; void push_cell(); void pop(); + void pop_context(); + void pop_all(); void clear(); diff --git a/scene/gui/subviewport_container.cpp b/scene/gui/subviewport_container.cpp index 9105837486..8142fc204a 100644 --- a/scene/gui/subviewport_container.cpp +++ b/scene/gui/subviewport_container.cpp @@ -154,6 +154,18 @@ void SubViewportContainer::_notification(int p_what) { case NOTIFICATION_MOUSE_EXIT: { _notify_viewports(NOTIFICATION_VP_MOUSE_EXIT); } break; + + case NOTIFICATION_FOCUS_ENTER: { + // If focused, send InputEvent to the SubViewport before the Gui-Input stage. + set_process_input(true); + set_process_unhandled_input(false); + } break; + + case NOTIFICATION_FOCUS_EXIT: { + // A different Control has focus and should receive Gui-Input before the InputEvent is sent to the SubViewport. + set_process_input(false); + set_process_unhandled_input(true); + } break; } } @@ -168,6 +180,14 @@ void SubViewportContainer::_notify_viewports(int p_notification) { } void SubViewportContainer::input(const Ref<InputEvent> &p_event) { + _propagate_nonpositional_event(p_event); +} + +void SubViewportContainer::unhandled_input(const Ref<InputEvent> &p_event) { + _propagate_nonpositional_event(p_event); +} + +void SubViewportContainer::_propagate_nonpositional_event(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); if (Engine::get_singleton()->is_editor_hint()) { @@ -262,5 +282,6 @@ void SubViewportContainer::_bind_methods() { } SubViewportContainer::SubViewportContainer() { - set_process_input(true); + set_process_unhandled_input(true); + set_focus_mode(FOCUS_CLICK); } diff --git a/scene/gui/subviewport_container.h b/scene/gui/subviewport_container.h index 8e5f5d157d..3c6cd09d66 100644 --- a/scene/gui/subviewport_container.h +++ b/scene/gui/subviewport_container.h @@ -41,6 +41,7 @@ class SubViewportContainer : public Container { void _notify_viewports(int p_notification); bool _is_propagated_in_gui_input(const Ref<InputEvent> &p_event); void _send_event_to_viewports(const Ref<InputEvent> &p_event); + void _propagate_nonpositional_event(const Ref<InputEvent> &p_event); protected: void _notification(int p_what); @@ -54,6 +55,7 @@ public: bool is_stretch_enabled() const; virtual void input(const Ref<InputEvent> &p_event) override; + virtual void unhandled_input(const Ref<InputEvent> &p_event) override; virtual void gui_input(const Ref<InputEvent> &p_event) override; void set_stretch_shrink(int p_shrink); int get_stretch_shrink() const; diff --git a/scene/gui/texture_button.cpp b/scene/gui/texture_button.cpp index b07a3d7669..dcbb25c41d 100644 --- a/scene/gui/texture_button.cpp +++ b/scene/gui/texture_button.cpp @@ -30,7 +30,6 @@ #include "texture_button.h" -#include "core/core_string_names.h" #include "core/typedefs.h" #include <stdlib.h> @@ -353,12 +352,12 @@ void TextureButton::_set_texture(Ref<Texture2D> *p_destination, const Ref<Textur return; } if (destination.is_valid()) { - destination->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(this, &TextureButton::_texture_changed)); + destination->disconnect_changed(callable_mp(this, &TextureButton::_texture_changed)); } destination = p_texture; if (destination.is_valid()) { // Pass `CONNECT_REFERENCE_COUNTED` to avoid early disconnect in case the same texture is assigned to different "slots". - destination->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &TextureButton::_texture_changed), CONNECT_REFERENCE_COUNTED); + destination->connect_changed(callable_mp(this, &TextureButton::_texture_changed), CONNECT_REFERENCE_COUNTED); } _texture_changed(); } diff --git a/scene/gui/texture_progress_bar.cpp b/scene/gui/texture_progress_bar.cpp index 2464e005ee..70c2cc9d65 100644 --- a/scene/gui/texture_progress_bar.cpp +++ b/scene/gui/texture_progress_bar.cpp @@ -31,7 +31,7 @@ #include "texture_progress_bar.h" #include "core/config/engine.h" -#include "core/core_string_names.h" +#include "scene/resources/atlas_texture.h" void TextureProgressBar::set_under_texture(const Ref<Texture2D> &p_texture) { _set_texture(&under, p_texture); @@ -161,12 +161,12 @@ void TextureProgressBar::_set_texture(Ref<Texture2D> *p_destination, const Ref<T return; } if (destination.is_valid()) { - destination->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(this, &TextureProgressBar::_texture_changed)); + destination->disconnect_changed(callable_mp(this, &TextureProgressBar::_texture_changed)); } destination = p_texture; if (destination.is_valid()) { // Pass `CONNECT_REFERENCE_COUNTED` to avoid early disconnect in case the same texture is assigned to different "slots". - destination->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &TextureProgressBar::_texture_changed), CONNECT_REFERENCE_COUNTED); + destination->connect_changed(callable_mp(this, &TextureProgressBar::_texture_changed), CONNECT_REFERENCE_COUNTED); } _texture_changed(); } diff --git a/scene/gui/texture_rect.cpp b/scene/gui/texture_rect.cpp index 20472ab46e..d94b11789f 100644 --- a/scene/gui/texture_rect.cpp +++ b/scene/gui/texture_rect.cpp @@ -30,7 +30,7 @@ #include "texture_rect.h" -#include "core/core_string_names.h" +#include "scene/resources/atlas_texture.h" #include "servers/rendering_server.h" void TextureRect::_notification(int p_what) { @@ -201,13 +201,13 @@ void TextureRect::set_texture(const Ref<Texture2D> &p_tex) { } if (texture.is_valid()) { - texture->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(this, &TextureRect::_texture_changed)); + texture->disconnect_changed(callable_mp(this, &TextureRect::_texture_changed)); } texture = p_tex; if (texture.is_valid()) { - texture->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &TextureRect::_texture_changed)); + texture->connect_changed(callable_mp(this, &TextureRect::_texture_changed)); } queue_redraw(); diff --git a/scene/gui/video_stream_player.cpp b/scene/gui/video_stream_player.cpp index 49a24f217d..f6e558fe57 100644 --- a/scene/gui/video_stream_player.cpp +++ b/scene/gui/video_stream_player.cpp @@ -31,6 +31,7 @@ #include "video_stream_player.h" #include "core/os/os.h" +#include "scene/resources/image_texture.h" #include "scene/scene_string_names.h" #include "servers/audio_server.h" diff --git a/scene/gui/video_stream_player.h b/scene/gui/video_stream_player.h index 7a922b2d92..0b83608f0b 100644 --- a/scene/gui/video_stream_player.h +++ b/scene/gui/video_stream_player.h @@ -36,6 +36,8 @@ #include "servers/audio/audio_rb_resampler.h" #include "servers/audio_server.h" +class ImageTexture; + class VideoStreamPlayer : public Control { GDCLASS(VideoStreamPlayer, Control); diff --git a/scene/main/canvas_item.h b/scene/main/canvas_item.h index fcb951a89d..7f6bbb17c3 100644 --- a/scene/main/canvas_item.h +++ b/scene/main/canvas_item.h @@ -170,6 +170,7 @@ protected: _FORCE_INLINE_ void set_hide_clip_children(bool p_value) { hide_clip_children = p_value; } GDVIRTUAL0(_draw) + public: enum { NOTIFICATION_TRANSFORM_CHANGED = SceneTree::NOTIFICATION_TRANSFORM_CHANGED, //unique @@ -179,7 +180,6 @@ public: NOTIFICATION_EXIT_CANVAS = 33, NOTIFICATION_LOCAL_TRANSFORM_CHANGED = 35, NOTIFICATION_WORLD_2D_CHANGED = 36, - }; /* EDITOR */ @@ -189,7 +189,7 @@ public: // Save and restore a CanvasItem state virtual void _edit_set_state(const Dictionary &p_state) {} - virtual Dictionary _edit_get_state() const { return Dictionary(); }; + virtual Dictionary _edit_get_state() const { return Dictionary(); } // Used to move the node virtual void _edit_set_position(const Point2 &p_position) = 0; diff --git a/scene/main/node.cpp b/scene/main/node.cpp index 6b18e47f2d..cb72e4ec08 100644 --- a/scene/main/node.cpp +++ b/scene/main/node.cpp @@ -1139,7 +1139,6 @@ void Node::_set_name_nocheck(const StringName &p_name) { void Node::set_name(const String &p_name) { ERR_FAIL_COND_MSG(data.inside_tree && !Thread::is_main_thread(), "Changing the name to nodes inside the SceneTree is only allowed from the main thread. Use `set_name.call_deferred(new_name)`."); - ERR_FAIL_COND_MSG(data.parent && data.parent->data.blocked > 0, "Parent node is busy setting up children, `set_name(new_name)` failed. Consider using `set_name.call_deferred(new_name)` instead."); String name = p_name.validate_node_name(); ERR_FAIL_COND(name.is_empty()); @@ -1151,9 +1150,9 @@ void Node::set_name(const String &p_name) { data.name = name; if (data.parent) { - data.parent->data.children.erase(old_name); data.parent->_validate_child_name(this, true); - data.parent->data.children.insert(data.name, this); + bool success = data.parent->data.children.replace_key(old_name, data.name); + ERR_FAIL_COND_MSG(!success, "Renaming child in hashtable failed, this is a bug."); } if (data.unique_name_in_owner && data.owner) { diff --git a/scene/main/scene_tree.cpp b/scene/main/scene_tree.cpp index fe5c8f2c1d..db9c1efa68 100644 --- a/scene/main/scene_tree.cpp +++ b/scene/main/scene_tree.cpp @@ -50,6 +50,7 @@ #include "scene/main/viewport.h" #include "scene/resources/environment.h" #include "scene/resources/font.h" +#include "scene/resources/image_texture.h" #include "scene/resources/material.h" #include "scene/resources/mesh.h" #include "scene/resources/packed_scene.h" @@ -549,6 +550,10 @@ bool SceneTree::process(double p_time) { #endif // _3D_DISABLED #endif // TOOLS_ENABLED + if (unlikely(pending_new_scene)) { + _flush_scene_change(); + } + return _quit; } @@ -1375,27 +1380,16 @@ Node *SceneTree::get_current_scene() const { return current_scene; } -void SceneTree::_change_scene(Node *p_to) { - ERR_FAIL_COND_MSG(!Thread::is_main_thread(), "Changing scene can only be done from the main thread."); - if (current_scene) { - memdelete(current_scene); - current_scene = nullptr; - } - - // If we're quitting, abort. - if (unlikely(_quit)) { - if (p_to) { // Prevent memory leak. - memdelete(p_to); - } - return; - } - - if (p_to) { - current_scene = p_to; - root->add_child(p_to); - // Update display for cursor instantly. - root->update_mouse_cursor_state(); +void SceneTree::_flush_scene_change() { + if (prev_scene) { + memdelete(prev_scene); + prev_scene = nullptr; } + current_scene = pending_new_scene; + root->add_child(pending_new_scene); + pending_new_scene = nullptr; + // Update display for cursor instantly. + root->update_mouse_cursor_state(); } Error SceneTree::change_scene_to_file(const String &p_path) { @@ -1414,7 +1408,22 @@ Error SceneTree::change_scene_to_packed(const Ref<PackedScene> &p_scene) { Node *new_scene = p_scene->instantiate(); ERR_FAIL_NULL_V(new_scene, ERR_CANT_CREATE); - call_deferred(SNAME("_change_scene"), new_scene); + // If called again while a change is pending. + if (pending_new_scene) { + queue_delete(pending_new_scene); + pending_new_scene = nullptr; + } + + prev_scene = current_scene; + + if (current_scene) { + // Let as many side effects as possible happen or be queued now, + // so they are run before the scene is actually deleted. + root->remove_child(current_scene); + } + DEV_ASSERT(!current_scene); + + pending_new_scene = new_scene; return OK; } @@ -1592,8 +1601,6 @@ void SceneTree::_bind_methods() { ClassDB::bind_method(D_METHOD("reload_current_scene"), &SceneTree::reload_current_scene); ClassDB::bind_method(D_METHOD("unload_current_scene"), &SceneTree::unload_current_scene); - ClassDB::bind_method(D_METHOD("_change_scene"), &SceneTree::_change_scene); - ClassDB::bind_method(D_METHOD("set_multiplayer", "multiplayer", "root_path"), &SceneTree::set_multiplayer, DEFVAL(NodePath())); ClassDB::bind_method(D_METHOD("get_multiplayer", "for_path"), &SceneTree::get_multiplayer, DEFVAL(NodePath())); ClassDB::bind_method(D_METHOD("set_multiplayer_poll_enabled", "enabled"), &SceneTree::set_multiplayer_poll_enabled); @@ -1832,6 +1839,14 @@ SceneTree::SceneTree() { } SceneTree::~SceneTree() { + if (prev_scene) { + memdelete(prev_scene); + prev_scene = nullptr; + } + if (pending_new_scene) { + memdelete(pending_new_scene); + pending_new_scene = nullptr; + } if (root) { root->_set_tree(nullptr); root->_propagate_after_exit_tree(); diff --git a/scene/main/scene_tree.h b/scene/main/scene_tree.h index 9b550f7135..e1597d3890 100644 --- a/scene/main/scene_tree.h +++ b/scene/main/scene_tree.h @@ -178,6 +178,8 @@ private: TypedArray<Node> _get_nodes_in_group(const StringName &p_group); Node *current_scene = nullptr; + Node *prev_scene = nullptr; + Node *pending_new_scene = nullptr; Color debug_collisions_color; Color debug_collision_contact_color; @@ -188,7 +190,7 @@ private: Ref<Material> collision_material; int collision_debug_contacts; - void _change_scene(Node *p_to); + void _flush_scene_change(); List<Ref<SceneTreeTimer>> timers; List<Ref<Tween>> tweens; diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp index e0e18f8ef3..9f05d62d9c 100644 --- a/scene/main/viewport.cpp +++ b/scene/main/viewport.cpp @@ -31,7 +31,6 @@ #include "viewport.h" #include "core/config/project_settings.h" -#include "core/core_string_names.h" #include "core/debugger/engine_debugger.h" #include "core/object/message_queue.h" #include "core/string/translation.h" @@ -315,7 +314,7 @@ void Viewport::_sub_window_update(Window *p_window) { Rect2i r = Rect2i(p_window->get_position(), sw.window->get_size()); if (!p_window->get_flag(Window::FLAG_BORDERLESS)) { - Ref<StyleBox> panel = p_window->get_theme_stylebox(SNAME("embedded_border")); + Ref<StyleBox> panel = p_window->get_theme_stylebox(gui.subwindow_focused == p_window ? SNAME("embedded_border") : SNAME("embedded_unfocused_border")); panel->draw(sw.canvas_item, r); // Draw the title bar text. @@ -726,6 +725,7 @@ void Viewport::_process_picking() { } while (physics_picking_events.size()) { + local_input_handled = false; Ref<InputEvent> ev = physics_picking_events.front()->get(); physics_picking_events.pop_front(); @@ -3035,18 +3035,18 @@ void Viewport::_push_unhandled_input_internal(const Ref<InputEvent> &p_event) { get_tree()->_call_input_pause(shortcut_input_group, SceneTree::CALL_INPUT_TYPE_SHORTCUT_INPUT, p_event, this); } - // Unhandled Input. - if (!is_input_handled()) { - ERR_FAIL_COND(!is_inside_tree()); - get_tree()->_call_input_pause(unhandled_input_group, SceneTree::CALL_INPUT_TYPE_UNHANDLED_INPUT, p_event, this); - } - // Unhandled key Input - Used for performance reasons - This is called a lot less than _unhandled_input since it ignores MouseMotion, and to handle Unicode input with Alt / Ctrl modifiers after handling shortcuts. if (!is_input_handled() && (Object::cast_to<InputEventKey>(*p_event) != nullptr)) { ERR_FAIL_COND(!is_inside_tree()); get_tree()->_call_input_pause(unhandled_key_input_group, SceneTree::CALL_INPUT_TYPE_UNHANDLED_KEY_INPUT, p_event, this); } + // Unhandled Input. + if (!is_input_handled()) { + ERR_FAIL_COND(!is_inside_tree()); + get_tree()->_call_input_pause(unhandled_input_group, SceneTree::CALL_INPUT_TYPE_UNHANDLED_INPUT, p_event, this); + } + if (physics_object_picking && !is_input_handled()) { if (Input::get_singleton()->get_mouse_mode() != Input::MOUSE_MODE_CAPTURED && (Object::cast_to<InputEventMouse>(*p_event) || @@ -3873,7 +3873,7 @@ void Viewport::set_world_3d(const Ref<World3D> &p_world_3d) { } if (own_world_3d.is_valid() && world_3d.is_valid()) { - world_3d->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(this, &Viewport::_own_world_3d_changed)); + world_3d->disconnect_changed(callable_mp(this, &Viewport::_own_world_3d_changed)); } world_3d = p_world_3d; @@ -3881,7 +3881,7 @@ void Viewport::set_world_3d(const Ref<World3D> &p_world_3d) { if (own_world_3d.is_valid()) { if (world_3d.is_valid()) { own_world_3d = world_3d->duplicate(); - world_3d->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &Viewport::_own_world_3d_changed)); + world_3d->connect_changed(callable_mp(this, &Viewport::_own_world_3d_changed)); } else { own_world_3d = Ref<World3D>(memnew(World3D)); } @@ -3932,14 +3932,14 @@ void Viewport::set_use_own_world_3d(bool p_use_own_world_3d) { if (p_use_own_world_3d) { if (world_3d.is_valid()) { own_world_3d = world_3d->duplicate(); - world_3d->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &Viewport::_own_world_3d_changed)); + world_3d->connect_changed(callable_mp(this, &Viewport::_own_world_3d_changed)); } else { own_world_3d = Ref<World3D>(memnew(World3D)); } } else { own_world_3d = Ref<World3D>(); if (world_3d.is_valid()) { - world_3d->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(this, &Viewport::_own_world_3d_changed)); + world_3d->disconnect_changed(callable_mp(this, &Viewport::_own_world_3d_changed)); } } diff --git a/scene/main/window.cpp b/scene/main/window.cpp index be88b757fd..3ea53da141 100644 --- a/scene/main/window.cpp +++ b/scene/main/window.cpp @@ -54,21 +54,21 @@ bool Window::_set(const StringName &p_name, const Variant &p_value) { if (name.begins_with("theme_override_icons/")) { String dname = name.get_slicec('/', 1); if (theme_icon_override.has(dname)) { - theme_icon_override[dname]->disconnect("changed", callable_mp(this, &Window::_notify_theme_override_changed)); + theme_icon_override[dname]->disconnect_changed(callable_mp(this, &Window::_notify_theme_override_changed)); } theme_icon_override.erase(dname); _notify_theme_override_changed(); } else if (name.begins_with("theme_override_styles/")) { String dname = name.get_slicec('/', 1); if (theme_style_override.has(dname)) { - theme_style_override[dname]->disconnect("changed", callable_mp(this, &Window::_notify_theme_override_changed)); + theme_style_override[dname]->disconnect_changed(callable_mp(this, &Window::_notify_theme_override_changed)); } theme_style_override.erase(dname); _notify_theme_override_changed(); } else if (name.begins_with("theme_override_fonts/")) { String dname = name.get_slicec('/', 1); if (theme_font_override.has(dname)) { - theme_font_override[dname]->disconnect("changed", callable_mp(this, &Window::_notify_theme_override_changed)); + theme_font_override[dname]->disconnect_changed(callable_mp(this, &Window::_notify_theme_override_changed)); } theme_font_override.erase(dname); _notify_theme_override_changed(); @@ -1823,13 +1823,13 @@ void Window::set_theme(const Ref<Theme> &p_theme) { } if (theme.is_valid()) { - theme->disconnect("changed", callable_mp(this, &Window::_theme_changed)); + theme->disconnect_changed(callable_mp(this, &Window::_theme_changed)); } theme = p_theme; if (theme.is_valid()) { theme_owner->propagate_theme_changed(this, this, is_inside_tree(), true); - theme->connect("changed", callable_mp(this, &Window::_theme_changed), CONNECT_DEFERRED); + theme->connect_changed(callable_mp(this, &Window::_theme_changed), CONNECT_DEFERRED); return; } @@ -2165,11 +2165,11 @@ void Window::add_theme_icon_override(const StringName &p_name, const Ref<Texture ERR_FAIL_COND(!p_icon.is_valid()); if (theme_icon_override.has(p_name)) { - theme_icon_override[p_name]->disconnect("changed", callable_mp(this, &Window::_notify_theme_override_changed)); + theme_icon_override[p_name]->disconnect_changed(callable_mp(this, &Window::_notify_theme_override_changed)); } theme_icon_override[p_name] = p_icon; - theme_icon_override[p_name]->connect("changed", callable_mp(this, &Window::_notify_theme_override_changed), CONNECT_REFERENCE_COUNTED); + theme_icon_override[p_name]->connect_changed(callable_mp(this, &Window::_notify_theme_override_changed), CONNECT_REFERENCE_COUNTED); _notify_theme_override_changed(); } @@ -2178,11 +2178,11 @@ void Window::add_theme_style_override(const StringName &p_name, const Ref<StyleB ERR_FAIL_COND(!p_style.is_valid()); if (theme_style_override.has(p_name)) { - theme_style_override[p_name]->disconnect("changed", callable_mp(this, &Window::_notify_theme_override_changed)); + theme_style_override[p_name]->disconnect_changed(callable_mp(this, &Window::_notify_theme_override_changed)); } theme_style_override[p_name] = p_style; - theme_style_override[p_name]->connect("changed", callable_mp(this, &Window::_notify_theme_override_changed), CONNECT_REFERENCE_COUNTED); + theme_style_override[p_name]->connect_changed(callable_mp(this, &Window::_notify_theme_override_changed), CONNECT_REFERENCE_COUNTED); _notify_theme_override_changed(); } @@ -2191,11 +2191,11 @@ void Window::add_theme_font_override(const StringName &p_name, const Ref<Font> & ERR_FAIL_COND(!p_font.is_valid()); if (theme_font_override.has(p_name)) { - theme_font_override[p_name]->disconnect("changed", callable_mp(this, &Window::_notify_theme_override_changed)); + theme_font_override[p_name]->disconnect_changed(callable_mp(this, &Window::_notify_theme_override_changed)); } theme_font_override[p_name] = p_font; - theme_font_override[p_name]->connect("changed", callable_mp(this, &Window::_notify_theme_override_changed), CONNECT_REFERENCE_COUNTED); + theme_font_override[p_name]->connect_changed(callable_mp(this, &Window::_notify_theme_override_changed), CONNECT_REFERENCE_COUNTED); _notify_theme_override_changed(); } @@ -2220,7 +2220,7 @@ void Window::add_theme_constant_override(const StringName &p_name, int p_constan void Window::remove_theme_icon_override(const StringName &p_name) { ERR_MAIN_THREAD_GUARD; if (theme_icon_override.has(p_name)) { - theme_icon_override[p_name]->disconnect("changed", callable_mp(this, &Window::_notify_theme_override_changed)); + theme_icon_override[p_name]->disconnect_changed(callable_mp(this, &Window::_notify_theme_override_changed)); } theme_icon_override.erase(p_name); @@ -2230,7 +2230,7 @@ void Window::remove_theme_icon_override(const StringName &p_name) { void Window::remove_theme_style_override(const StringName &p_name) { ERR_MAIN_THREAD_GUARD; if (theme_style_override.has(p_name)) { - theme_style_override[p_name]->disconnect("changed", callable_mp(this, &Window::_notify_theme_override_changed)); + theme_style_override[p_name]->disconnect_changed(callable_mp(this, &Window::_notify_theme_override_changed)); } theme_style_override.erase(p_name); @@ -2240,7 +2240,7 @@ void Window::remove_theme_style_override(const StringName &p_name) { void Window::remove_theme_font_override(const StringName &p_name) { ERR_MAIN_THREAD_GUARD; if (theme_font_override.has(p_name)) { - theme_font_override[p_name]->disconnect("changed", callable_mp(this, &Window::_notify_theme_override_changed)); + theme_font_override[p_name]->disconnect_changed(callable_mp(this, &Window::_notify_theme_override_changed)); } theme_font_override.erase(p_name); @@ -2768,13 +2768,13 @@ Window::~Window() { // Resources need to be disconnected. for (KeyValue<StringName, Ref<Texture2D>> &E : theme_icon_override) { - E.value->disconnect("changed", callable_mp(this, &Window::_notify_theme_override_changed)); + E.value->disconnect_changed(callable_mp(this, &Window::_notify_theme_override_changed)); } for (KeyValue<StringName, Ref<StyleBox>> &E : theme_style_override) { - E.value->disconnect("changed", callable_mp(this, &Window::_notify_theme_override_changed)); + E.value->disconnect_changed(callable_mp(this, &Window::_notify_theme_override_changed)); } for (KeyValue<StringName, Ref<Font>> &E : theme_font_override) { - E.value->disconnect("changed", callable_mp(this, &Window::_notify_theme_override_changed)); + E.value->disconnect_changed(callable_mp(this, &Window::_notify_theme_override_changed)); } // Then override maps can be simply cleared. diff --git a/scene/main/window.h b/scene/main/window.h index b98b888380..7a10499d9b 100644 --- a/scene/main/window.h +++ b/scene/main/window.h @@ -315,7 +315,7 @@ public: Window *get_parent_visible_window() const; Viewport *get_parent_viewport() const; - void popup(const Rect2i &p_screen_rect = Rect2i()); + virtual void popup(const Rect2i &p_screen_rect = Rect2i()); void popup_on_parent(const Rect2i &p_parent_rect); void popup_centered(const Size2i &p_minsize = Size2i()); void popup_centered_ratio(float p_ratio = 0.8); diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp index 9706fb1a5d..4d5d287fbe 100644 --- a/scene/register_scene_types.cpp +++ b/scene/register_scene_types.cpp @@ -139,30 +139,38 @@ #include "scene/main/timer.h" #include "scene/main/viewport.h" #include "scene/main/window.h" +#include "scene/resources/animated_texture.h" #include "scene/resources/animation_library.h" +#include "scene/resources/atlas_texture.h" #include "scene/resources/audio_stream_polyphonic.h" #include "scene/resources/audio_stream_wav.h" #include "scene/resources/bit_map.h" #include "scene/resources/bone_map.h" #include "scene/resources/box_shape_3d.h" #include "scene/resources/camera_attributes.h" +#include "scene/resources/camera_texture.h" #include "scene/resources/capsule_shape_2d.h" #include "scene/resources/capsule_shape_3d.h" #include "scene/resources/circle_shape_2d.h" +#include "scene/resources/compressed_texture.h" #include "scene/resources/concave_polygon_shape_2d.h" #include "scene/resources/concave_polygon_shape_3d.h" #include "scene/resources/convex_polygon_shape_2d.h" #include "scene/resources/convex_polygon_shape_3d.h" +#include "scene/resources/curve_texture.h" #include "scene/resources/cylinder_shape_3d.h" #include "scene/resources/default_theme/default_theme.h" #include "scene/resources/environment.h" #include "scene/resources/font.h" #include "scene/resources/gradient.h" +#include "scene/resources/gradient_texture.h" #include "scene/resources/height_map_shape_3d.h" +#include "scene/resources/image_texture.h" #include "scene/resources/immediate_mesh.h" #include "scene/resources/label_settings.h" #include "scene/resources/material.h" #include "scene/resources/mesh_data_tool.h" +#include "scene/resources/mesh_texture.h" #include "scene/resources/multimesh.h" #include "scene/resources/navigation_mesh.h" #include "scene/resources/navigation_mesh_source_geometry_data_3d.h" @@ -170,7 +178,9 @@ #include "scene/resources/packed_scene.h" #include "scene/resources/particle_process_material.h" #include "scene/resources/physics_material.h" +#include "scene/resources/placeholder_textures.h" #include "scene/resources/polygon_path_finder.h" +#include "scene/resources/portable_compressed_texture.h" #include "scene/resources/primitive_meshes.h" #include "scene/resources/rectangle_shape_2d.h" #include "scene/resources/resource_format_text.h" @@ -192,12 +202,16 @@ #include "scene/resources/sky_material.h" #include "scene/resources/sphere_shape_3d.h" #include "scene/resources/style_box.h" +#include "scene/resources/style_box_flat.h" +#include "scene/resources/style_box_line.h" +#include "scene/resources/style_box_texture.h" #include "scene/resources/surface_tool.h" #include "scene/resources/syntax_highlighter.h" #include "scene/resources/text_file.h" #include "scene/resources/text_line.h" #include "scene/resources/text_paragraph.h" #include "scene/resources/texture.h" +#include "scene/resources/texture_rd.h" #include "scene/resources/theme.h" #include "scene/resources/tile_set.h" #include "scene/resources/video_stream.h" @@ -662,6 +676,8 @@ void register_scene_types() { GDREGISTER_CLASS(VisualShaderNodeTexture3DParameter); GDREGISTER_CLASS(VisualShaderNodeCubemapParameter); GDREGISTER_CLASS(VisualShaderNodeLinearSceneDepth); + GDREGISTER_CLASS(VisualShaderNodeWorldPositionFromDepth); + GDREGISTER_CLASS(VisualShaderNodeScreenNormalWorldSpace); GDREGISTER_CLASS(VisualShaderNodeIf); GDREGISTER_CLASS(VisualShaderNodeSwitch); GDREGISTER_CLASS(VisualShaderNodeFresnel); @@ -675,6 +691,7 @@ void register_scene_types() { GDREGISTER_CLASS(VisualShaderNodeProximityFade); GDREGISTER_CLASS(VisualShaderNodeRandomRange); GDREGISTER_CLASS(VisualShaderNodeRemap); + GDREGISTER_CLASS(VisualShaderNodeRotationByAxis); GDREGISTER_ABSTRACT_CLASS(VisualShaderNodeVarying); GDREGISTER_CLASS(VisualShaderNodeVaryingSetter); GDREGISTER_CLASS(VisualShaderNodeVaryingGetter); @@ -874,6 +891,14 @@ void register_scene_types() { GDREGISTER_CLASS(PlaceholderCubemap); GDREGISTER_CLASS(PlaceholderCubemapArray); + // These classes are part of renderer_rd + GDREGISTER_CLASS(Texture2DRD); + GDREGISTER_ABSTRACT_CLASS(TextureLayeredRD); + GDREGISTER_CLASS(Texture2DArrayRD); + GDREGISTER_CLASS(TextureCubemapRD); + GDREGISTER_CLASS(TextureCubemapArrayRD); + GDREGISTER_CLASS(Texture3DRD); + GDREGISTER_CLASS(Animation); GDREGISTER_CLASS(AnimationLibrary); diff --git a/scene/resources/animated_texture.cpp b/scene/resources/animated_texture.cpp new file mode 100644 index 0000000000..842a398327 --- /dev/null +++ b/scene/resources/animated_texture.cpp @@ -0,0 +1,286 @@ +/**************************************************************************/ +/* animated_texture.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "animated_texture.h" + +void AnimatedTexture::_update_proxy() { + RWLockRead r(rw_lock); + + float delta; + if (prev_ticks == 0) { + delta = 0; + prev_ticks = OS::get_singleton()->get_ticks_usec(); + } else { + uint64_t ticks = OS::get_singleton()->get_ticks_usec(); + delta = float(double(ticks - prev_ticks) / 1000000.0); + prev_ticks = ticks; + } + + time += delta; + + float speed = speed_scale == 0 ? 0 : abs(1.0 / speed_scale); + + int iter_max = frame_count; + while (iter_max && !pause) { + float frame_limit = frames[current_frame].duration * speed; + + if (time > frame_limit) { + if (speed_scale > 0.0) { + current_frame++; + } else { + current_frame--; + } + if (current_frame >= frame_count) { + if (one_shot) { + current_frame = frame_count - 1; + } else { + current_frame = 0; + } + } else if (current_frame < 0) { + if (one_shot) { + current_frame = 0; + } else { + current_frame = frame_count - 1; + } + } + time -= frame_limit; + + } else { + break; + } + iter_max--; + } + + if (frames[current_frame].texture.is_valid()) { + RenderingServer::get_singleton()->texture_proxy_update(proxy, frames[current_frame].texture->get_rid()); + } +} + +void AnimatedTexture::set_frames(int p_frames) { + ERR_FAIL_COND(p_frames < 1 || p_frames > MAX_FRAMES); + + RWLockWrite r(rw_lock); + + frame_count = p_frames; +} + +int AnimatedTexture::get_frames() const { + return frame_count; +} + +void AnimatedTexture::set_current_frame(int p_frame) { + ERR_FAIL_COND(p_frame < 0 || p_frame >= frame_count); + + RWLockWrite r(rw_lock); + + current_frame = p_frame; + time = 0; +} + +int AnimatedTexture::get_current_frame() const { + return current_frame; +} + +void AnimatedTexture::set_pause(bool p_pause) { + RWLockWrite r(rw_lock); + pause = p_pause; +} + +bool AnimatedTexture::get_pause() const { + return pause; +} + +void AnimatedTexture::set_one_shot(bool p_one_shot) { + RWLockWrite r(rw_lock); + one_shot = p_one_shot; +} + +bool AnimatedTexture::get_one_shot() const { + return one_shot; +} + +void AnimatedTexture::set_frame_texture(int p_frame, const Ref<Texture2D> &p_texture) { + ERR_FAIL_COND(p_texture == this); + ERR_FAIL_INDEX(p_frame, MAX_FRAMES); + + RWLockWrite w(rw_lock); + + frames[p_frame].texture = p_texture; +} + +Ref<Texture2D> AnimatedTexture::get_frame_texture(int p_frame) const { + ERR_FAIL_INDEX_V(p_frame, MAX_FRAMES, Ref<Texture2D>()); + + RWLockRead r(rw_lock); + + return frames[p_frame].texture; +} + +void AnimatedTexture::set_frame_duration(int p_frame, float p_duration) { + ERR_FAIL_INDEX(p_frame, MAX_FRAMES); + + RWLockWrite r(rw_lock); + + frames[p_frame].duration = p_duration; +} + +float AnimatedTexture::get_frame_duration(int p_frame) const { + ERR_FAIL_INDEX_V(p_frame, MAX_FRAMES, 0); + + RWLockRead r(rw_lock); + + return frames[p_frame].duration; +} + +void AnimatedTexture::set_speed_scale(float p_scale) { + ERR_FAIL_COND(p_scale < -1000 || p_scale >= 1000); + + RWLockWrite r(rw_lock); + + speed_scale = p_scale; +} + +float AnimatedTexture::get_speed_scale() const { + return speed_scale; +} + +int AnimatedTexture::get_width() const { + RWLockRead r(rw_lock); + + if (!frames[current_frame].texture.is_valid()) { + return 1; + } + + return frames[current_frame].texture->get_width(); +} + +int AnimatedTexture::get_height() const { + RWLockRead r(rw_lock); + + if (!frames[current_frame].texture.is_valid()) { + return 1; + } + + return frames[current_frame].texture->get_height(); +} + +RID AnimatedTexture::get_rid() const { + return proxy; +} + +bool AnimatedTexture::has_alpha() const { + RWLockRead r(rw_lock); + + if (!frames[current_frame].texture.is_valid()) { + return false; + } + + return frames[current_frame].texture->has_alpha(); +} + +Ref<Image> AnimatedTexture::get_image() const { + RWLockRead r(rw_lock); + + if (!frames[current_frame].texture.is_valid()) { + return Ref<Image>(); + } + + return frames[current_frame].texture->get_image(); +} + +bool AnimatedTexture::is_pixel_opaque(int p_x, int p_y) const { + RWLockRead r(rw_lock); + + if (frames[current_frame].texture.is_valid()) { + return frames[current_frame].texture->is_pixel_opaque(p_x, p_y); + } + return true; +} + +void AnimatedTexture::_validate_property(PropertyInfo &p_property) const { + String prop = p_property.name; + if (prop.begins_with("frame_")) { + int frame = prop.get_slicec('/', 0).get_slicec('_', 1).to_int(); + if (frame >= frame_count) { + p_property.usage = PROPERTY_USAGE_NONE; + } + } +} + +void AnimatedTexture::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_frames", "frames"), &AnimatedTexture::set_frames); + ClassDB::bind_method(D_METHOD("get_frames"), &AnimatedTexture::get_frames); + + ClassDB::bind_method(D_METHOD("set_current_frame", "frame"), &AnimatedTexture::set_current_frame); + ClassDB::bind_method(D_METHOD("get_current_frame"), &AnimatedTexture::get_current_frame); + + ClassDB::bind_method(D_METHOD("set_pause", "pause"), &AnimatedTexture::set_pause); + ClassDB::bind_method(D_METHOD("get_pause"), &AnimatedTexture::get_pause); + + ClassDB::bind_method(D_METHOD("set_one_shot", "one_shot"), &AnimatedTexture::set_one_shot); + ClassDB::bind_method(D_METHOD("get_one_shot"), &AnimatedTexture::get_one_shot); + + ClassDB::bind_method(D_METHOD("set_speed_scale", "scale"), &AnimatedTexture::set_speed_scale); + ClassDB::bind_method(D_METHOD("get_speed_scale"), &AnimatedTexture::get_speed_scale); + + ClassDB::bind_method(D_METHOD("set_frame_texture", "frame", "texture"), &AnimatedTexture::set_frame_texture); + ClassDB::bind_method(D_METHOD("get_frame_texture", "frame"), &AnimatedTexture::get_frame_texture); + + ClassDB::bind_method(D_METHOD("set_frame_duration", "frame", "duration"), &AnimatedTexture::set_frame_duration); + ClassDB::bind_method(D_METHOD("get_frame_duration", "frame"), &AnimatedTexture::get_frame_duration); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "frames", PROPERTY_HINT_RANGE, "1," + itos(MAX_FRAMES), PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), "set_frames", "get_frames"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "current_frame", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_current_frame", "get_current_frame"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "pause"), "set_pause", "get_pause"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "one_shot"), "set_one_shot", "get_one_shot"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "speed_scale", PROPERTY_HINT_RANGE, "-60,60,0.1,or_less,or_greater"), "set_speed_scale", "get_speed_scale"); + + for (int i = 0; i < MAX_FRAMES; i++) { + ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "frame_" + itos(i) + "/texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_INTERNAL), "set_frame_texture", "get_frame_texture", i); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "frame_" + itos(i) + "/duration", PROPERTY_HINT_RANGE, "0.0,16.0,0.01,or_greater,suffix:s", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_INTERNAL), "set_frame_duration", "get_frame_duration", i); + } + + BIND_CONSTANT(MAX_FRAMES); +} + +AnimatedTexture::AnimatedTexture() { + //proxy = RS::get_singleton()->texture_create(); + proxy_ph = RS::get_singleton()->texture_2d_placeholder_create(); + proxy = RS::get_singleton()->texture_proxy_create(proxy_ph); + + RenderingServer::get_singleton()->texture_set_force_redraw_if_visible(proxy, true); + RenderingServer::get_singleton()->connect("frame_pre_draw", callable_mp(this, &AnimatedTexture::_update_proxy)); +} + +AnimatedTexture::~AnimatedTexture() { + ERR_FAIL_NULL(RenderingServer::get_singleton()); + RS::get_singleton()->free(proxy); + RS::get_singleton()->free(proxy_ph); +} diff --git a/scene/resources/animated_texture.h b/scene/resources/animated_texture.h new file mode 100644 index 0000000000..844959c810 --- /dev/null +++ b/scene/resources/animated_texture.h @@ -0,0 +1,109 @@ +/**************************************************************************/ +/* animated_texture.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef ANIMATED_TEXTURE_H +#define ANIMATED_TEXTURE_H + +#include "scene/resources/texture.h" + +class AnimatedTexture : public Texture2D { + GDCLASS(AnimatedTexture, Texture2D); + + // Use readers writers lock for this, since its far more times read than written to. + RWLock rw_lock; + +public: + enum { + MAX_FRAMES = 256 + }; + +private: + RID proxy_ph; + RID proxy; + + struct Frame { + Ref<Texture2D> texture; + float duration = 1.0; + }; + + Frame frames[MAX_FRAMES]; + int frame_count = 1.0; + int current_frame = 0; + bool pause = false; + bool one_shot = false; + float speed_scale = 1.0; + + float time = 0.0; + + uint64_t prev_ticks = 0; + + void _update_proxy(); + +protected: + static void _bind_methods(); + void _validate_property(PropertyInfo &p_property) const; + +public: + void set_frames(int p_frames); + int get_frames() const; + + void set_current_frame(int p_frame); + int get_current_frame() const; + + void set_pause(bool p_pause); + bool get_pause() const; + + void set_one_shot(bool p_one_shot); + bool get_one_shot() const; + + void set_frame_texture(int p_frame, const Ref<Texture2D> &p_texture); + Ref<Texture2D> get_frame_texture(int p_frame) const; + + void set_frame_duration(int p_frame, float p_duration); + float get_frame_duration(int p_frame) const; + + void set_speed_scale(float p_scale); + float get_speed_scale() const; + + virtual int get_width() const override; + virtual int get_height() const override; + virtual RID get_rid() const override; + + virtual bool has_alpha() const override; + + virtual Ref<Image> get_image() const override; + + bool is_pixel_opaque(int p_x, int p_y) const override; + + AnimatedTexture(); + ~AnimatedTexture(); +}; + +#endif // ANIMATED_TEXTURE_H diff --git a/scene/resources/animation.cpp b/scene/resources/animation.cpp index 1ab6e714a8..860e48a361 100644 --- a/scene/resources/animation.cpp +++ b/scene/resources/animation.cpp @@ -5508,6 +5508,9 @@ Variant Animation::add_variant(const Variant &a, const Variant &b) { const ::AABB ab = b.operator ::AABB(); return ::AABB(aa.position + ab.position, aa.size + ab.size); } + case Variant::BASIS: { + return (a.operator Basis()) * (b.operator Basis()); + } case Variant::QUATERNION: { return (a.operator Quaternion()) * (b.operator Quaternion()); } @@ -5555,14 +5558,17 @@ Variant Animation::subtract_variant(const Variant &a, const Variant &b) { const ::AABB ab = b.operator ::AABB(); return ::AABB(aa.position - ab.position, aa.size - ab.size); } + case Variant::BASIS: { + return (b.operator Basis()).inverse() * (a.operator Basis()); + } case Variant::QUATERNION: { return (b.operator Quaternion()).inverse() * (a.operator Quaternion()); } case Variant::TRANSFORM2D: { - return (b.operator Transform2D()).inverse() * (a.operator Transform2D()); + return (b.operator Transform2D()).affine_inverse() * (a.operator Transform2D()); } case Variant::TRANSFORM3D: { - return (b.operator Transform3D()).inverse() * (a.operator Transform3D()); + return (b.operator Transform3D()).affine_inverse() * (a.operator Transform3D()); } default: { return Variant::evaluate(Variant::OP_SUBTRACT, a, b); diff --git a/scene/resources/animation_library.cpp b/scene/resources/animation_library.cpp index 206e9df71a..a69b1818d2 100644 --- a/scene/resources/animation_library.cpp +++ b/scene/resources/animation_library.cpp @@ -52,13 +52,13 @@ Error AnimationLibrary::add_animation(const StringName &p_name, const Ref<Animat ERR_FAIL_COND_V(p_animation.is_null(), ERR_INVALID_PARAMETER); if (animations.has(p_name)) { - animations.get(p_name)->disconnect(SNAME("changed"), callable_mp(this, &AnimationLibrary::_animation_changed)); + animations.get(p_name)->disconnect_changed(callable_mp(this, &AnimationLibrary::_animation_changed)); animations.erase(p_name); emit_signal(SNAME("animation_removed"), p_name); } animations.insert(p_name, p_animation); - animations.get(p_name)->connect(SNAME("changed"), callable_mp(this, &AnimationLibrary::_animation_changed).bind(p_name)); + animations.get(p_name)->connect_changed(callable_mp(this, &AnimationLibrary::_animation_changed).bind(p_name)); emit_signal(SNAME("animation_added"), p_name); notify_property_list_changed(); return OK; @@ -67,7 +67,7 @@ Error AnimationLibrary::add_animation(const StringName &p_name, const Ref<Animat void AnimationLibrary::remove_animation(const StringName &p_name) { ERR_FAIL_COND_MSG(!animations.has(p_name), vformat("Animation not found: %s.", p_name)); - animations.get(p_name)->disconnect(SNAME("changed"), callable_mp(this, &AnimationLibrary::_animation_changed)); + animations.get(p_name)->disconnect_changed(callable_mp(this, &AnimationLibrary::_animation_changed)); animations.erase(p_name); emit_signal(SNAME("animation_removed"), p_name); notify_property_list_changed(); @@ -78,8 +78,8 @@ void AnimationLibrary::rename_animation(const StringName &p_name, const StringNa ERR_FAIL_COND_MSG(!is_valid_animation_name(p_new_name), "Invalid animation name: '" + String(p_new_name) + "'."); ERR_FAIL_COND_MSG(animations.has(p_new_name), vformat("Animation name \"%s\" already exists in library.", p_new_name)); - animations.get(p_name)->disconnect(SNAME("changed"), callable_mp(this, &AnimationLibrary::_animation_changed)); - animations.get(p_name)->connect(SNAME("changed"), callable_mp(this, &AnimationLibrary::_animation_changed).bind(p_new_name)); + animations.get(p_name)->disconnect_changed(callable_mp(this, &AnimationLibrary::_animation_changed)); + animations.get(p_name)->connect_changed(callable_mp(this, &AnimationLibrary::_animation_changed).bind(p_new_name)); animations.insert(p_new_name, animations[p_name]); animations.erase(p_name); emit_signal(SNAME("animation_renamed"), p_name, p_new_name); @@ -125,7 +125,7 @@ void AnimationLibrary::get_animation_list(List<StringName> *p_animations) const void AnimationLibrary::_set_data(const Dictionary &p_data) { for (KeyValue<StringName, Ref<Animation>> &K : animations) { - K.value->disconnect(SNAME("changed"), callable_mp(this, &AnimationLibrary::_animation_changed)); + K.value->disconnect_changed(callable_mp(this, &AnimationLibrary::_animation_changed)); } animations.clear(); List<Variant> keys; diff --git a/scene/resources/atlas_texture.cpp b/scene/resources/atlas_texture.cpp new file mode 100644 index 0000000000..24bf25f2db --- /dev/null +++ b/scene/resources/atlas_texture.cpp @@ -0,0 +1,255 @@ +/**************************************************************************/ +/* atlas_texture.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "atlas_texture.h" + +#include "core/core_string_names.h" + +int AtlasTexture::get_width() const { + if (region.size.width == 0) { + if (atlas.is_valid()) { + return atlas->get_width(); + } + return 1; + } else { + return region.size.width + margin.size.width; + } +} + +int AtlasTexture::get_height() const { + if (region.size.height == 0) { + if (atlas.is_valid()) { + return atlas->get_height(); + } + return 1; + } else { + return region.size.height + margin.size.height; + } +} + +RID AtlasTexture::get_rid() const { + if (atlas.is_valid()) { + return atlas->get_rid(); + } + + return RID(); +} + +bool AtlasTexture::has_alpha() const { + if (atlas.is_valid()) { + return atlas->has_alpha(); + } + + return false; +} + +void AtlasTexture::set_atlas(const Ref<Texture2D> &p_atlas) { + ERR_FAIL_COND(p_atlas == this); + if (atlas == p_atlas) { + return; + } + // Support recursive AtlasTextures. + if (Ref<AtlasTexture>(atlas).is_valid()) { + atlas->disconnect_changed(callable_mp((Resource *)this, &AtlasTexture::emit_changed)); + } + atlas = p_atlas; + if (Ref<AtlasTexture>(atlas).is_valid()) { + atlas->connect_changed(callable_mp((Resource *)this, &AtlasTexture::emit_changed)); + } + + emit_changed(); +} + +Ref<Texture2D> AtlasTexture::get_atlas() const { + return atlas; +} + +void AtlasTexture::set_region(const Rect2 &p_region) { + if (region == p_region) { + return; + } + region = p_region; + emit_changed(); +} + +Rect2 AtlasTexture::get_region() const { + return region; +} + +void AtlasTexture::set_margin(const Rect2 &p_margin) { + if (margin == p_margin) { + return; + } + margin = p_margin; + emit_changed(); +} + +Rect2 AtlasTexture::get_margin() const { + return margin; +} + +void AtlasTexture::set_filter_clip(const bool p_enable) { + filter_clip = p_enable; + emit_changed(); +} + +bool AtlasTexture::has_filter_clip() const { + return filter_clip; +} + +void AtlasTexture::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_atlas", "atlas"), &AtlasTexture::set_atlas); + ClassDB::bind_method(D_METHOD("get_atlas"), &AtlasTexture::get_atlas); + + ClassDB::bind_method(D_METHOD("set_region", "region"), &AtlasTexture::set_region); + ClassDB::bind_method(D_METHOD("get_region"), &AtlasTexture::get_region); + + ClassDB::bind_method(D_METHOD("set_margin", "margin"), &AtlasTexture::set_margin); + ClassDB::bind_method(D_METHOD("get_margin"), &AtlasTexture::get_margin); + + ClassDB::bind_method(D_METHOD("set_filter_clip", "enable"), &AtlasTexture::set_filter_clip); + ClassDB::bind_method(D_METHOD("has_filter_clip"), &AtlasTexture::has_filter_clip); + + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "atlas", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_atlas", "get_atlas"); + ADD_PROPERTY(PropertyInfo(Variant::RECT2, "region", PROPERTY_HINT_NONE, "suffix:px"), "set_region", "get_region"); + ADD_PROPERTY(PropertyInfo(Variant::RECT2, "margin", PROPERTY_HINT_NONE, "suffix:px"), "set_margin", "get_margin"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "filter_clip"), "set_filter_clip", "has_filter_clip"); +} + +void AtlasTexture::draw(RID p_canvas_item, const Point2 &p_pos, const Color &p_modulate, bool p_transpose) const { + if (!atlas.is_valid()) { + return; + } + + Rect2 rc = region; + + if (rc.size.width == 0) { + rc.size.width = atlas->get_width(); + } + + if (rc.size.height == 0) { + rc.size.height = atlas->get_height(); + } + + atlas->draw_rect_region(p_canvas_item, Rect2(p_pos + margin.position, rc.size), rc, p_modulate, p_transpose, filter_clip); +} + +void AtlasTexture::draw_rect(RID p_canvas_item, const Rect2 &p_rect, bool p_tile, const Color &p_modulate, bool p_transpose) const { + if (!atlas.is_valid()) { + return; + } + + Rect2 rc = region; + + if (rc.size.width == 0) { + rc.size.width = atlas->get_width(); + } + + if (rc.size.height == 0) { + rc.size.height = atlas->get_height(); + } + + Vector2 scale = p_rect.size / (region.size + margin.size); + Rect2 dr(p_rect.position + margin.position * scale, rc.size * scale); + + atlas->draw_rect_region(p_canvas_item, dr, rc, p_modulate, p_transpose, filter_clip); +} + +void AtlasTexture::draw_rect_region(RID p_canvas_item, const Rect2 &p_rect, const Rect2 &p_src_rect, const Color &p_modulate, bool p_transpose, bool p_clip_uv) const { + //this might not necessarily work well if using a rect, needs to be fixed properly + if (!atlas.is_valid()) { + return; + } + + Rect2 dr; + Rect2 src_c; + get_rect_region(p_rect, p_src_rect, dr, src_c); + + atlas->draw_rect_region(p_canvas_item, dr, src_c, p_modulate, p_transpose, filter_clip); +} + +bool AtlasTexture::get_rect_region(const Rect2 &p_rect, const Rect2 &p_src_rect, Rect2 &r_rect, Rect2 &r_src_rect) const { + if (!atlas.is_valid()) { + return false; + } + + Rect2 src = p_src_rect; + if (src.size == Size2()) { + src.size = region.size; + } + Vector2 scale = p_rect.size / src.size; + + src.position += (region.position - margin.position); + Rect2 src_clipped = region.intersection(src); + if (src_clipped.size == Size2()) { + return false; + } + + Vector2 ofs = (src_clipped.position - src.position); + if (scale.x < 0) { + ofs.x += (src_clipped.size.x - src.size.x); + } + if (scale.y < 0) { + ofs.y += (src_clipped.size.y - src.size.y); + } + + r_rect = Rect2(p_rect.position + ofs * scale, src_clipped.size * scale); + r_src_rect = src_clipped; + return true; +} + +bool AtlasTexture::is_pixel_opaque(int p_x, int p_y) const { + if (!atlas.is_valid()) { + return true; + } + + int x = p_x + region.position.x - margin.position.x; + int y = p_y + region.position.y - margin.position.y; + + // margin edge may outside of atlas + if (x < 0 || x >= atlas->get_width()) { + return false; + } + if (y < 0 || y >= atlas->get_height()) { + return false; + } + + return atlas->is_pixel_opaque(x, y); +} + +Ref<Image> AtlasTexture::get_image() const { + if (!atlas.is_valid() || !atlas->get_image().is_valid()) { + return Ref<Image>(); + } + + return atlas->get_image()->get_region(region); +} + +AtlasTexture::AtlasTexture() {} diff --git a/scene/resources/atlas_texture.h b/scene/resources/atlas_texture.h new file mode 100644 index 0000000000..5aba098d09 --- /dev/null +++ b/scene/resources/atlas_texture.h @@ -0,0 +1,79 @@ +/**************************************************************************/ +/* atlas_texture.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef ATLAS_TEXTURE_H +#define ATLAS_TEXTURE_H + +#include "scene/resources/texture.h" + +class AtlasTexture : public Texture2D { + GDCLASS(AtlasTexture, Texture2D); + RES_BASE_EXTENSION("atlastex"); + +protected: + Ref<Texture2D> atlas; + Rect2 region; + Rect2 margin; + bool filter_clip = false; + + static void _bind_methods(); + +public: + virtual int get_width() const override; + virtual int get_height() const override; + virtual RID get_rid() const override; + + virtual bool has_alpha() const override; + + void set_atlas(const Ref<Texture2D> &p_atlas); + Ref<Texture2D> get_atlas() const; + + void set_region(const Rect2 &p_region); + Rect2 get_region() const; + + void set_margin(const Rect2 &p_margin); + Rect2 get_margin() const; + + void set_filter_clip(const bool p_enable); + bool has_filter_clip() const; + + virtual void draw(RID p_canvas_item, const Point2 &p_pos, const Color &p_modulate = Color(1, 1, 1), bool p_transpose = false) const override; + virtual void draw_rect(RID p_canvas_item, const Rect2 &p_rect, bool p_tile = false, const Color &p_modulate = Color(1, 1, 1), bool p_transpose = false) const override; + virtual void draw_rect_region(RID p_canvas_item, const Rect2 &p_rect, const Rect2 &p_src_rect, const Color &p_modulate = Color(1, 1, 1), bool p_transpose = false, bool p_clip_uv = true) const override; + virtual bool get_rect_region(const Rect2 &p_rect, const Rect2 &p_src_rect, Rect2 &r_rect, Rect2 &r_src_rect) const override; + + bool is_pixel_opaque(int p_x, int p_y) const override; + + virtual Ref<Image> get_image() const override; + + AtlasTexture(); +}; + +#endif // ATLAS_TEXTURE_H diff --git a/scene/resources/box_shape_3d.cpp b/scene/resources/box_shape_3d.cpp index 6a1f7dc6ea..313aeb1bca 100644 --- a/scene/resources/box_shape_3d.cpp +++ b/scene/resources/box_shape_3d.cpp @@ -80,7 +80,7 @@ void BoxShape3D::set_size(const Vector3 &p_size) { ERR_FAIL_COND_MSG(p_size.x < 0 || p_size.y < 0 || p_size.z < 0, "BoxShape3D size cannot be negative."); size = p_size; _update_shape(); - notify_change_to_owners(); + emit_changed(); } Vector3 BoxShape3D::get_size() const { diff --git a/scene/resources/camera_attributes.cpp b/scene/resources/camera_attributes.cpp index 8f4f804397..7c46729af3 100644 --- a/scene/resources/camera_attributes.cpp +++ b/scene/resources/camera_attributes.cpp @@ -122,7 +122,7 @@ void CameraAttributes::_bind_methods() { ADD_GROUP("Exposure", "exposure_"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "exposure_sensitivity", PROPERTY_HINT_RANGE, "0.1,32000.0,0.1,suffix:ISO"), "set_exposure_sensitivity", "get_exposure_sensitivity"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "exposure_multiplier", PROPERTY_HINT_RANGE, "0.0,2048.0,0.001"), "set_exposure_multiplier", "get_exposure_multiplier"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "exposure_multiplier", PROPERTY_HINT_RANGE, "0.0,8.0,0.001,or_greater"), "set_exposure_multiplier", "get_exposure_multiplier"); ADD_GROUP("Auto Exposure", "auto_exposure_"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "auto_exposure_enabled"), "set_auto_exposure_enabled", "is_auto_exposure_enabled"); diff --git a/scene/resources/camera_texture.cpp b/scene/resources/camera_texture.cpp new file mode 100644 index 0000000000..b575a099ed --- /dev/null +++ b/scene/resources/camera_texture.cpp @@ -0,0 +1,131 @@ +/**************************************************************************/ +/* camera_texture.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "camera_texture.h" + +#include "servers/camera/camera_feed.h" + +void CameraTexture::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_camera_feed_id", "feed_id"), &CameraTexture::set_camera_feed_id); + ClassDB::bind_method(D_METHOD("get_camera_feed_id"), &CameraTexture::get_camera_feed_id); + + ClassDB::bind_method(D_METHOD("set_which_feed", "which_feed"), &CameraTexture::set_which_feed); + ClassDB::bind_method(D_METHOD("get_which_feed"), &CameraTexture::get_which_feed); + + ClassDB::bind_method(D_METHOD("set_camera_active", "active"), &CameraTexture::set_camera_active); + ClassDB::bind_method(D_METHOD("get_camera_active"), &CameraTexture::get_camera_active); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "camera_feed_id"), "set_camera_feed_id", "get_camera_feed_id"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "which_feed"), "set_which_feed", "get_which_feed"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "camera_is_active"), "set_camera_active", "get_camera_active"); +} + +int CameraTexture::get_width() const { + Ref<CameraFeed> feed = CameraServer::get_singleton()->get_feed_by_id(camera_feed_id); + if (feed.is_valid()) { + return feed->get_base_width(); + } else { + return 0; + } +} + +int CameraTexture::get_height() const { + Ref<CameraFeed> feed = CameraServer::get_singleton()->get_feed_by_id(camera_feed_id); + if (feed.is_valid()) { + return feed->get_base_height(); + } else { + return 0; + } +} + +bool CameraTexture::has_alpha() const { + return false; +} + +RID CameraTexture::get_rid() const { + Ref<CameraFeed> feed = CameraServer::get_singleton()->get_feed_by_id(camera_feed_id); + if (feed.is_valid()) { + return feed->get_texture(which_feed); + } else { + if (_texture.is_null()) { + _texture = RenderingServer::get_singleton()->texture_2d_placeholder_create(); + } + return _texture; + } +} + +Ref<Image> CameraTexture::get_image() const { + // not (yet) supported + return Ref<Image>(); +} + +void CameraTexture::set_camera_feed_id(int p_new_id) { + camera_feed_id = p_new_id; + notify_property_list_changed(); +} + +int CameraTexture::get_camera_feed_id() const { + return camera_feed_id; +} + +void CameraTexture::set_which_feed(CameraServer::FeedImage p_which) { + which_feed = p_which; + notify_property_list_changed(); +} + +CameraServer::FeedImage CameraTexture::get_which_feed() const { + return which_feed; +} + +void CameraTexture::set_camera_active(bool p_active) { + Ref<CameraFeed> feed = CameraServer::get_singleton()->get_feed_by_id(camera_feed_id); + if (feed.is_valid()) { + feed->set_active(p_active); + notify_property_list_changed(); + } +} + +bool CameraTexture::get_camera_active() const { + Ref<CameraFeed> feed = CameraServer::get_singleton()->get_feed_by_id(camera_feed_id); + if (feed.is_valid()) { + return feed->is_active(); + } else { + return false; + } +} + +CameraTexture::CameraTexture() {} + +CameraTexture::~CameraTexture() { + if (_texture.is_valid()) { + ERR_FAIL_NULL(RenderingServer::get_singleton()); + RenderingServer::get_singleton()->free(_texture); + } +} diff --git a/scene/resources/camera_texture.h b/scene/resources/camera_texture.h new file mode 100644 index 0000000000..521121f9ea --- /dev/null +++ b/scene/resources/camera_texture.h @@ -0,0 +1,68 @@ +/**************************************************************************/ +/* camera_texture.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef CAMERA_TEXTURE_H +#define CAMERA_TEXTURE_H + +#include "scene/resources/texture.h" + +class CameraTexture : public Texture2D { + GDCLASS(CameraTexture, Texture2D); + +private: + mutable RID _texture; + int camera_feed_id = 0; + CameraServer::FeedImage which_feed = CameraServer::FEED_RGBA_IMAGE; + +protected: + static void _bind_methods(); + +public: + virtual int get_width() const override; + virtual int get_height() const override; + virtual RID get_rid() const override; + virtual bool has_alpha() const override; + + virtual Ref<Image> get_image() const override; + + void set_camera_feed_id(int p_new_id); + int get_camera_feed_id() const; + + void set_which_feed(CameraServer::FeedImage p_which); + CameraServer::FeedImage get_which_feed() const; + + void set_camera_active(bool p_active); + bool get_camera_active() const; + + CameraTexture(); + ~CameraTexture(); +}; + +#endif // CAMERA_TEXTURE_H diff --git a/scene/resources/capsule_shape_3d.cpp b/scene/resources/capsule_shape_3d.cpp index 3edff0cc68..9e16801060 100644 --- a/scene/resources/capsule_shape_3d.cpp +++ b/scene/resources/capsule_shape_3d.cpp @@ -86,7 +86,7 @@ void CapsuleShape3D::set_radius(float p_radius) { height = radius * 2.0; } _update_shape(); - notify_change_to_owners(); + emit_changed(); } float CapsuleShape3D::get_radius() const { @@ -100,7 +100,7 @@ void CapsuleShape3D::set_height(float p_height) { radius = height * 0.5; } _update_shape(); - notify_change_to_owners(); + emit_changed(); } float CapsuleShape3D::get_height() const { diff --git a/scene/resources/compressed_texture.cpp b/scene/resources/compressed_texture.cpp new file mode 100644 index 0000000000..f4395d5c7d --- /dev/null +++ b/scene/resources/compressed_texture.cpp @@ -0,0 +1,907 @@ +/**************************************************************************/ +/* compressed_texture.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "compressed_texture.h" + +#include "scene/resources/bit_map.h" + +Error CompressedTexture2D::_load_data(const String &p_path, int &r_width, int &r_height, Ref<Image> &image, bool &r_request_3d, bool &r_request_normal, bool &r_request_roughness, int &mipmap_limit, int p_size_limit) { + alpha_cache.unref(); + + ERR_FAIL_COND_V(image.is_null(), ERR_INVALID_PARAMETER); + + Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ); + ERR_FAIL_COND_V_MSG(f.is_null(), ERR_CANT_OPEN, vformat("Unable to open file: %s.", p_path)); + + uint8_t header[4]; + f->get_buffer(header, 4); + if (header[0] != 'G' || header[1] != 'S' || header[2] != 'T' || header[3] != '2') { + ERR_FAIL_V_MSG(ERR_FILE_CORRUPT, "Compressed texture file is corrupt (Bad header)."); + } + + uint32_t version = f->get_32(); + + if (version > FORMAT_VERSION) { + ERR_FAIL_V_MSG(ERR_FILE_CORRUPT, "Compressed texture file is too new."); + } + r_width = f->get_32(); + r_height = f->get_32(); + uint32_t df = f->get_32(); //data format + + //skip reserved + mipmap_limit = int(f->get_32()); + //reserved + f->get_32(); + f->get_32(); + f->get_32(); + +#ifdef TOOLS_ENABLED + + r_request_3d = request_3d_callback && df & FORMAT_BIT_DETECT_3D; + r_request_roughness = request_roughness_callback && df & FORMAT_BIT_DETECT_ROUGNESS; + r_request_normal = request_normal_callback && df & FORMAT_BIT_DETECT_NORMAL; + +#else + + r_request_3d = false; + r_request_roughness = false; + r_request_normal = false; + +#endif + if (!(df & FORMAT_BIT_STREAM)) { + p_size_limit = 0; + } + + image = load_image_from_file(f, p_size_limit); + + if (image.is_null() || image->is_empty()) { + return ERR_CANT_OPEN; + } + + return OK; +} + +void CompressedTexture2D::set_path(const String &p_path, bool p_take_over) { + if (texture.is_valid()) { + RenderingServer::get_singleton()->texture_set_path(texture, p_path); + } + + Resource::set_path(p_path, p_take_over); +} + +void CompressedTexture2D::_requested_3d(void *p_ud) { + CompressedTexture2D *ct = (CompressedTexture2D *)p_ud; + Ref<CompressedTexture2D> ctex(ct); + ERR_FAIL_NULL(request_3d_callback); + request_3d_callback(ctex); +} + +void CompressedTexture2D::_requested_roughness(void *p_ud, const String &p_normal_path, RS::TextureDetectRoughnessChannel p_roughness_channel) { + CompressedTexture2D *ct = (CompressedTexture2D *)p_ud; + Ref<CompressedTexture2D> ctex(ct); + ERR_FAIL_NULL(request_roughness_callback); + request_roughness_callback(ctex, p_normal_path, p_roughness_channel); +} + +void CompressedTexture2D::_requested_normal(void *p_ud) { + CompressedTexture2D *ct = (CompressedTexture2D *)p_ud; + Ref<CompressedTexture2D> ctex(ct); + ERR_FAIL_NULL(request_normal_callback); + request_normal_callback(ctex); +} + +CompressedTexture2D::TextureFormatRequestCallback CompressedTexture2D::request_3d_callback = nullptr; +CompressedTexture2D::TextureFormatRoughnessRequestCallback CompressedTexture2D::request_roughness_callback = nullptr; +CompressedTexture2D::TextureFormatRequestCallback CompressedTexture2D::request_normal_callback = nullptr; + +Image::Format CompressedTexture2D::get_format() const { + return format; +} + +Error CompressedTexture2D::load(const String &p_path) { + int lw, lh; + Ref<Image> image; + image.instantiate(); + + bool request_3d; + bool request_normal; + bool request_roughness; + int mipmap_limit; + + Error err = _load_data(p_path, lw, lh, image, request_3d, request_normal, request_roughness, mipmap_limit); + if (err) { + return err; + } + + if (texture.is_valid()) { + RID new_texture = RS::get_singleton()->texture_2d_create(image); + RS::get_singleton()->texture_replace(texture, new_texture); + } else { + texture = RS::get_singleton()->texture_2d_create(image); + } + if (lw || lh) { + RS::get_singleton()->texture_set_size_override(texture, lw, lh); + } + + w = lw; + h = lh; + path_to_file = p_path; + format = image->get_format(); + + if (get_path().is_empty()) { + //temporarily set path if no path set for resource, helps find errors + RenderingServer::get_singleton()->texture_set_path(texture, p_path); + } + +#ifdef TOOLS_ENABLED + + if (request_3d) { + //print_line("request detect 3D at " + p_path); + RS::get_singleton()->texture_set_detect_3d_callback(texture, _requested_3d, this); + } else { + //print_line("not requesting detect 3D at " + p_path); + RS::get_singleton()->texture_set_detect_3d_callback(texture, nullptr, nullptr); + } + + if (request_roughness) { + //print_line("request detect srgb at " + p_path); + RS::get_singleton()->texture_set_detect_roughness_callback(texture, _requested_roughness, this); + } else { + //print_line("not requesting detect srgb at " + p_path); + RS::get_singleton()->texture_set_detect_roughness_callback(texture, nullptr, nullptr); + } + + if (request_normal) { + //print_line("request detect srgb at " + p_path); + RS::get_singleton()->texture_set_detect_normal_callback(texture, _requested_normal, this); + } else { + //print_line("not requesting detect normal at " + p_path); + RS::get_singleton()->texture_set_detect_normal_callback(texture, nullptr, nullptr); + } + +#endif + notify_property_list_changed(); + emit_changed(); + return OK; +} + +String CompressedTexture2D::get_load_path() const { + return path_to_file; +} + +int CompressedTexture2D::get_width() const { + return w; +} + +int CompressedTexture2D::get_height() const { + return h; +} + +RID CompressedTexture2D::get_rid() const { + if (!texture.is_valid()) { + texture = RS::get_singleton()->texture_2d_placeholder_create(); + } + return texture; +} + +void CompressedTexture2D::draw(RID p_canvas_item, const Point2 &p_pos, const Color &p_modulate, bool p_transpose) const { + if ((w | h) == 0) { + return; + } + RenderingServer::get_singleton()->canvas_item_add_texture_rect(p_canvas_item, Rect2(p_pos, Size2(w, h)), texture, false, p_modulate, p_transpose); +} + +void CompressedTexture2D::draw_rect(RID p_canvas_item, const Rect2 &p_rect, bool p_tile, const Color &p_modulate, bool p_transpose) const { + if ((w | h) == 0) { + return; + } + RenderingServer::get_singleton()->canvas_item_add_texture_rect(p_canvas_item, p_rect, texture, p_tile, p_modulate, p_transpose); +} + +void CompressedTexture2D::draw_rect_region(RID p_canvas_item, const Rect2 &p_rect, const Rect2 &p_src_rect, const Color &p_modulate, bool p_transpose, bool p_clip_uv) const { + if ((w | h) == 0) { + return; + } + RenderingServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas_item, p_rect, texture, p_src_rect, p_modulate, p_transpose, p_clip_uv); +} + +bool CompressedTexture2D::has_alpha() const { + return false; +} + +Ref<Image> CompressedTexture2D::get_image() const { + if (texture.is_valid()) { + return RS::get_singleton()->texture_2d_get(texture); + } else { + return Ref<Image>(); + } +} + +bool CompressedTexture2D::is_pixel_opaque(int p_x, int p_y) const { + if (!alpha_cache.is_valid()) { + Ref<Image> img = get_image(); + if (img.is_valid()) { + if (img->is_compressed()) { //must decompress, if compressed + Ref<Image> decom = img->duplicate(); + decom->decompress(); + img = decom; + } + + alpha_cache.instantiate(); + alpha_cache->create_from_image_alpha(img); + } + } + + if (alpha_cache.is_valid()) { + int aw = int(alpha_cache->get_size().width); + int ah = int(alpha_cache->get_size().height); + if (aw == 0 || ah == 0) { + return true; + } + + int x = p_x * aw / w; + int y = p_y * ah / h; + + x = CLAMP(x, 0, aw); + y = CLAMP(y, 0, ah); + + return alpha_cache->get_bit(x, y); + } + + return true; +} + +void CompressedTexture2D::reload_from_file() { + String path = get_path(); + if (!path.is_resource_file()) { + return; + } + + path = ResourceLoader::path_remap(path); //remap for translation + path = ResourceLoader::import_remap(path); //remap for import + if (!path.is_resource_file()) { + return; + } + + load(path); +} + +void CompressedTexture2D::_validate_property(PropertyInfo &p_property) const { +} + +Ref<Image> CompressedTexture2D::load_image_from_file(Ref<FileAccess> f, int p_size_limit) { + uint32_t data_format = f->get_32(); + uint32_t w = f->get_16(); + uint32_t h = f->get_16(); + uint32_t mipmaps = f->get_32(); + Image::Format format = Image::Format(f->get_32()); + + if (data_format == DATA_FORMAT_PNG || data_format == DATA_FORMAT_WEBP) { + //look for a PNG or WebP file inside + + int sw = w; + int sh = h; + + //mipmaps need to be read independently, they will be later combined + Vector<Ref<Image>> mipmap_images; + uint64_t total_size = 0; + + bool first = true; + + for (uint32_t i = 0; i < mipmaps + 1; i++) { + uint32_t size = f->get_32(); + + if (p_size_limit > 0 && i < (mipmaps - 1) && (sw > p_size_limit || sh > p_size_limit)) { + //can't load this due to size limit + sw = MAX(sw >> 1, 1); + sh = MAX(sh >> 1, 1); + f->seek(f->get_position() + size); + continue; + } + + Vector<uint8_t> pv; + pv.resize(size); + { + uint8_t *wr = pv.ptrw(); + f->get_buffer(wr, size); + } + + Ref<Image> img; + if (data_format == DATA_FORMAT_PNG && Image::png_unpacker) { + img = Image::png_unpacker(pv); + } else if (data_format == DATA_FORMAT_WEBP && Image::webp_unpacker) { + img = Image::webp_unpacker(pv); + } + + if (img.is_null() || img->is_empty()) { + ERR_FAIL_COND_V(img.is_null() || img->is_empty(), Ref<Image>()); + } + + if (first) { + //format will actually be the format of the first image, + //as it may have changed on compression + format = img->get_format(); + first = false; + } else if (img->get_format() != format) { + img->convert(format); //all needs to be the same format + } + + total_size += img->get_data().size(); + + mipmap_images.push_back(img); + + sw = MAX(sw >> 1, 1); + sh = MAX(sh >> 1, 1); + } + + //print_line("mipmap read total: " + itos(mipmap_images.size())); + + Ref<Image> image; + image.instantiate(); + + if (mipmap_images.size() == 1) { + //only one image (which will most likely be the case anyway for this format) + image = mipmap_images[0]; + return image; + + } else { + //rarer use case, but needs to be supported + Vector<uint8_t> img_data; + img_data.resize(total_size); + + { + uint8_t *wr = img_data.ptrw(); + + int ofs = 0; + for (int i = 0; i < mipmap_images.size(); i++) { + Vector<uint8_t> id = mipmap_images[i]->get_data(); + int len = id.size(); + const uint8_t *r = id.ptr(); + memcpy(&wr[ofs], r, len); + ofs += len; + } + } + + image->set_data(w, h, true, mipmap_images[0]->get_format(), img_data); + return image; + } + + } else if (data_format == DATA_FORMAT_BASIS_UNIVERSAL) { + int sw = w; + int sh = h; + uint32_t size = f->get_32(); + if (p_size_limit > 0 && (sw > p_size_limit || sh > p_size_limit)) { + //can't load this due to size limit + sw = MAX(sw >> 1, 1); + sh = MAX(sh >> 1, 1); + f->seek(f->get_position() + size); + return Ref<Image>(); + } + Vector<uint8_t> pv; + pv.resize(size); + { + uint8_t *wr = pv.ptrw(); + f->get_buffer(wr, size); + } + Ref<Image> img; + img = Image::basis_universal_unpacker(pv); + if (img.is_null() || img->is_empty()) { + ERR_FAIL_COND_V(img.is_null() || img->is_empty(), Ref<Image>()); + } + format = img->get_format(); + sw = MAX(sw >> 1, 1); + sh = MAX(sh >> 1, 1); + return img; + } else if (data_format == DATA_FORMAT_IMAGE) { + int size = Image::get_image_data_size(w, h, format, mipmaps ? true : false); + + for (uint32_t i = 0; i < mipmaps + 1; i++) { + int tw, th; + int ofs = Image::get_image_mipmap_offset_and_dimensions(w, h, format, i, tw, th); + + if (p_size_limit > 0 && i < mipmaps && (p_size_limit > tw || p_size_limit > th)) { + if (ofs) { + f->seek(f->get_position() + ofs); + } + continue; //oops, size limit enforced, go to next + } + + Vector<uint8_t> data; + data.resize(size - ofs); + + { + uint8_t *wr = data.ptrw(); + f->get_buffer(wr, data.size()); + } + + Ref<Image> image = Image::create_from_data(tw, th, mipmaps - i ? true : false, format, data); + + return image; + } + } + + return Ref<Image>(); +} + +void CompressedTexture2D::_bind_methods() { + ClassDB::bind_method(D_METHOD("load", "path"), &CompressedTexture2D::load); + ClassDB::bind_method(D_METHOD("get_load_path"), &CompressedTexture2D::get_load_path); + + ADD_PROPERTY(PropertyInfo(Variant::STRING, "load_path", PROPERTY_HINT_FILE, "*.ctex"), "load", "get_load_path"); +} + +CompressedTexture2D::CompressedTexture2D() {} + +CompressedTexture2D::~CompressedTexture2D() { + if (texture.is_valid()) { + ERR_FAIL_NULL(RenderingServer::get_singleton()); + RS::get_singleton()->free(texture); + } +} + +Ref<Resource> ResourceFormatLoaderCompressedTexture2D::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, CacheMode p_cache_mode) { + Ref<CompressedTexture2D> st; + st.instantiate(); + Error err = st->load(p_path); + if (r_error) { + *r_error = err; + } + if (err != OK) { + return Ref<Resource>(); + } + + return st; +} + +void ResourceFormatLoaderCompressedTexture2D::get_recognized_extensions(List<String> *p_extensions) const { + p_extensions->push_back("ctex"); +} + +bool ResourceFormatLoaderCompressedTexture2D::handles_type(const String &p_type) const { + return p_type == "CompressedTexture2D"; +} + +String ResourceFormatLoaderCompressedTexture2D::get_resource_type(const String &p_path) const { + if (p_path.get_extension().to_lower() == "ctex") { + return "CompressedTexture2D"; + } + return ""; +} + +void CompressedTexture3D::set_path(const String &p_path, bool p_take_over) { + if (texture.is_valid()) { + RenderingServer::get_singleton()->texture_set_path(texture, p_path); + } + + Resource::set_path(p_path, p_take_over); +} + +Image::Format CompressedTexture3D::get_format() const { + return format; +} + +Error CompressedTexture3D::_load_data(const String &p_path, Vector<Ref<Image>> &r_data, Image::Format &r_format, int &r_width, int &r_height, int &r_depth, bool &r_mipmaps) { + Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ); + ERR_FAIL_COND_V_MSG(f.is_null(), ERR_CANT_OPEN, vformat("Unable to open file: %s.", p_path)); + + uint8_t header[4]; + f->get_buffer(header, 4); + ERR_FAIL_COND_V(header[0] != 'G' || header[1] != 'S' || header[2] != 'T' || header[3] != 'L', ERR_FILE_UNRECOGNIZED); + + //stored as compressed textures (used for lossless and lossy compression) + uint32_t version = f->get_32(); + + if (version > FORMAT_VERSION) { + ERR_FAIL_V_MSG(ERR_FILE_CORRUPT, "Compressed texture file is too new."); + } + + r_depth = f->get_32(); //depth + f->get_32(); //ignored (mode) + f->get_32(); // ignored (data format) + + f->get_32(); //ignored + int mipmap_count = f->get_32(); + f->get_32(); //ignored + f->get_32(); //ignored + + r_mipmaps = mipmap_count != 0; + + r_data.clear(); + + for (int i = 0; i < (r_depth + mipmap_count); i++) { + Ref<Image> image = CompressedTexture2D::load_image_from_file(f, 0); + ERR_FAIL_COND_V(image.is_null() || image->is_empty(), ERR_CANT_OPEN); + if (i == 0) { + r_format = image->get_format(); + r_width = image->get_width(); + r_height = image->get_height(); + } + r_data.push_back(image); + } + + return OK; +} + +Error CompressedTexture3D::load(const String &p_path) { + Vector<Ref<Image>> data; + + int tw, th, td; + Image::Format tfmt; + bool tmm; + + Error err = _load_data(p_path, data, tfmt, tw, th, td, tmm); + if (err) { + return err; + } + + if (texture.is_valid()) { + RID new_texture = RS::get_singleton()->texture_3d_create(tfmt, tw, th, td, tmm, data); + RS::get_singleton()->texture_replace(texture, new_texture); + } else { + texture = RS::get_singleton()->texture_3d_create(tfmt, tw, th, td, tmm, data); + } + + w = tw; + h = th; + d = td; + mipmaps = tmm; + format = tfmt; + + path_to_file = p_path; + + if (get_path().is_empty()) { + //temporarily set path if no path set for resource, helps find errors + RenderingServer::get_singleton()->texture_set_path(texture, p_path); + } + + notify_property_list_changed(); + emit_changed(); + return OK; +} + +String CompressedTexture3D::get_load_path() const { + return path_to_file; +} + +int CompressedTexture3D::get_width() const { + return w; +} + +int CompressedTexture3D::get_height() const { + return h; +} + +int CompressedTexture3D::get_depth() const { + return d; +} + +bool CompressedTexture3D::has_mipmaps() const { + return mipmaps; +} + +RID CompressedTexture3D::get_rid() const { + if (!texture.is_valid()) { + texture = RS::get_singleton()->texture_3d_placeholder_create(); + } + return texture; +} + +Vector<Ref<Image>> CompressedTexture3D::get_data() const { + if (texture.is_valid()) { + return RS::get_singleton()->texture_3d_get(texture); + } else { + return Vector<Ref<Image>>(); + } +} + +void CompressedTexture3D::reload_from_file() { + String path = get_path(); + if (!path.is_resource_file()) { + return; + } + + path = ResourceLoader::path_remap(path); //remap for translation + path = ResourceLoader::import_remap(path); //remap for import + if (!path.is_resource_file()) { + return; + } + + load(path); +} + +void CompressedTexture3D::_validate_property(PropertyInfo &p_property) const { +} + +void CompressedTexture3D::_bind_methods() { + ClassDB::bind_method(D_METHOD("load", "path"), &CompressedTexture3D::load); + ClassDB::bind_method(D_METHOD("get_load_path"), &CompressedTexture3D::get_load_path); + + ADD_PROPERTY(PropertyInfo(Variant::STRING, "load_path", PROPERTY_HINT_FILE, "*.ctex"), "load", "get_load_path"); +} + +CompressedTexture3D::CompressedTexture3D() {} + +CompressedTexture3D::~CompressedTexture3D() { + if (texture.is_valid()) { + ERR_FAIL_NULL(RenderingServer::get_singleton()); + RS::get_singleton()->free(texture); + } +} + +Ref<Resource> ResourceFormatLoaderCompressedTexture3D::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, CacheMode p_cache_mode) { + Ref<CompressedTexture3D> st; + st.instantiate(); + Error err = st->load(p_path); + if (r_error) { + *r_error = err; + } + if (err != OK) { + return Ref<Resource>(); + } + + return st; +} + +void ResourceFormatLoaderCompressedTexture3D::get_recognized_extensions(List<String> *p_extensions) const { + p_extensions->push_back("ctex3d"); +} + +bool ResourceFormatLoaderCompressedTexture3D::handles_type(const String &p_type) const { + return p_type == "CompressedTexture3D"; +} + +String ResourceFormatLoaderCompressedTexture3D::get_resource_type(const String &p_path) const { + if (p_path.get_extension().to_lower() == "ctex3d") { + return "CompressedTexture3D"; + } + return ""; +} + +void CompressedTextureLayered::set_path(const String &p_path, bool p_take_over) { + if (texture.is_valid()) { + RenderingServer::get_singleton()->texture_set_path(texture, p_path); + } + + Resource::set_path(p_path, p_take_over); +} + +Image::Format CompressedTextureLayered::get_format() const { + return format; +} + +Error CompressedTextureLayered::_load_data(const String &p_path, Vector<Ref<Image>> &images, int &mipmap_limit, int p_size_limit) { + ERR_FAIL_COND_V(images.size() != 0, ERR_INVALID_PARAMETER); + + Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ); + ERR_FAIL_COND_V_MSG(f.is_null(), ERR_CANT_OPEN, vformat("Unable to open file: %s.", p_path)); + + uint8_t header[4]; + f->get_buffer(header, 4); + if (header[0] != 'G' || header[1] != 'S' || header[2] != 'T' || header[3] != 'L') { + ERR_FAIL_V_MSG(ERR_FILE_CORRUPT, "Compressed texture layered file is corrupt (Bad header)."); + } + + uint32_t version = f->get_32(); + + if (version > FORMAT_VERSION) { + ERR_FAIL_V_MSG(ERR_FILE_CORRUPT, "Compressed texture file is too new."); + } + + uint32_t layer_count = f->get_32(); //layer count + uint32_t type = f->get_32(); //layer count + ERR_FAIL_COND_V((int)type != layered_type, ERR_INVALID_DATA); + + uint32_t df = f->get_32(); //data format + mipmap_limit = int(f->get_32()); + //reserved + f->get_32(); + f->get_32(); + f->get_32(); + + if (!(df & FORMAT_BIT_STREAM)) { + p_size_limit = 0; + } + + images.resize(layer_count); + + for (uint32_t i = 0; i < layer_count; i++) { + Ref<Image> image = CompressedTexture2D::load_image_from_file(f, p_size_limit); + ERR_FAIL_COND_V(image.is_null() || image->is_empty(), ERR_CANT_OPEN); + images.write[i] = image; + } + + return OK; +} + +Error CompressedTextureLayered::load(const String &p_path) { + Vector<Ref<Image>> images; + + int mipmap_limit; + + Error err = _load_data(p_path, images, mipmap_limit); + if (err) { + return err; + } + + if (texture.is_valid()) { + RID new_texture = RS::get_singleton()->texture_2d_layered_create(images, RS::TextureLayeredType(layered_type)); + RS::get_singleton()->texture_replace(texture, new_texture); + } else { + texture = RS::get_singleton()->texture_2d_layered_create(images, RS::TextureLayeredType(layered_type)); + } + + w = images[0]->get_width(); + h = images[0]->get_height(); + mipmaps = images[0]->has_mipmaps(); + format = images[0]->get_format(); + layers = images.size(); + + path_to_file = p_path; + + if (get_path().is_empty()) { + //temporarily set path if no path set for resource, helps find errors + RenderingServer::get_singleton()->texture_set_path(texture, p_path); + } + + notify_property_list_changed(); + emit_changed(); + return OK; +} + +String CompressedTextureLayered::get_load_path() const { + return path_to_file; +} + +int CompressedTextureLayered::get_width() const { + return w; +} + +int CompressedTextureLayered::get_height() const { + return h; +} + +int CompressedTextureLayered::get_layers() const { + return layers; +} + +bool CompressedTextureLayered::has_mipmaps() const { + return mipmaps; +} + +TextureLayered::LayeredType CompressedTextureLayered::get_layered_type() const { + return layered_type; +} + +RID CompressedTextureLayered::get_rid() const { + if (!texture.is_valid()) { + texture = RS::get_singleton()->texture_2d_layered_placeholder_create(RS::TextureLayeredType(layered_type)); + } + return texture; +} + +Ref<Image> CompressedTextureLayered::get_layer_data(int p_layer) const { + if (texture.is_valid()) { + return RS::get_singleton()->texture_2d_layer_get(texture, p_layer); + } else { + return Ref<Image>(); + } +} + +void CompressedTextureLayered::reload_from_file() { + String path = get_path(); + if (!path.is_resource_file()) { + return; + } + + path = ResourceLoader::path_remap(path); //remap for translation + path = ResourceLoader::import_remap(path); //remap for import + if (!path.is_resource_file()) { + return; + } + + load(path); +} + +void CompressedTextureLayered::_validate_property(PropertyInfo &p_property) const { +} + +void CompressedTextureLayered::_bind_methods() { + ClassDB::bind_method(D_METHOD("load", "path"), &CompressedTextureLayered::load); + ClassDB::bind_method(D_METHOD("get_load_path"), &CompressedTextureLayered::get_load_path); + + ADD_PROPERTY(PropertyInfo(Variant::STRING, "load_path", PROPERTY_HINT_FILE, "*.ctex"), "load", "get_load_path"); +} + +CompressedTextureLayered::CompressedTextureLayered(LayeredType p_type) { + layered_type = p_type; +} + +CompressedTextureLayered::~CompressedTextureLayered() { + if (texture.is_valid()) { + ERR_FAIL_NULL(RenderingServer::get_singleton()); + RS::get_singleton()->free(texture); + } +} + +///////////////////////////////////////////////// + +Ref<Resource> ResourceFormatLoaderCompressedTextureLayered::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, CacheMode p_cache_mode) { + Ref<CompressedTextureLayered> ct; + if (p_path.get_extension().to_lower() == "ctexarray") { + Ref<CompressedTexture2DArray> c; + c.instantiate(); + ct = c; + } else if (p_path.get_extension().to_lower() == "ccube") { + Ref<CompressedCubemap> c; + c.instantiate(); + ct = c; + } else if (p_path.get_extension().to_lower() == "ccubearray") { + Ref<CompressedCubemapArray> c; + c.instantiate(); + ct = c; + } else { + if (r_error) { + *r_error = ERR_FILE_UNRECOGNIZED; + } + return Ref<Resource>(); + } + Error err = ct->load(p_path); + if (r_error) { + *r_error = err; + } + if (err != OK) { + return Ref<Resource>(); + } + + return ct; +} + +void ResourceFormatLoaderCompressedTextureLayered::get_recognized_extensions(List<String> *p_extensions) const { + p_extensions->push_back("ctexarray"); + p_extensions->push_back("ccube"); + p_extensions->push_back("ccubearray"); +} + +bool ResourceFormatLoaderCompressedTextureLayered::handles_type(const String &p_type) const { + return p_type == "CompressedTexture2DArray" || p_type == "CompressedCubemap" || p_type == "CompressedCubemapArray"; +} + +String ResourceFormatLoaderCompressedTextureLayered::get_resource_type(const String &p_path) const { + if (p_path.get_extension().to_lower() == "ctexarray") { + return "CompressedTexture2DArray"; + } + if (p_path.get_extension().to_lower() == "ccube") { + return "CompressedCubemap"; + } + if (p_path.get_extension().to_lower() == "ccubearray") { + return "CompressedCubemapArray"; + } + return ""; +} diff --git a/scene/resources/compressed_texture.h b/scene/resources/compressed_texture.h new file mode 100644 index 0000000000..5297d79cfe --- /dev/null +++ b/scene/resources/compressed_texture.h @@ -0,0 +1,273 @@ +/**************************************************************************/ +/* compressed_texture.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef COMPRESSED_TEXTURE_H +#define COMPRESSED_TEXTURE_H + +#include "scene/resources/texture.h" + +class BitMap; + +class CompressedTexture2D : public Texture2D { + GDCLASS(CompressedTexture2D, Texture2D); + +public: + enum DataFormat { + DATA_FORMAT_IMAGE, + DATA_FORMAT_PNG, + DATA_FORMAT_WEBP, + DATA_FORMAT_BASIS_UNIVERSAL, + }; + + enum { + FORMAT_VERSION = 1 + }; + + enum FormatBits { + FORMAT_BIT_STREAM = 1 << 22, + FORMAT_BIT_HAS_MIPMAPS = 1 << 23, + FORMAT_BIT_DETECT_3D = 1 << 24, + //FORMAT_BIT_DETECT_SRGB = 1 << 25, + FORMAT_BIT_DETECT_NORMAL = 1 << 26, + FORMAT_BIT_DETECT_ROUGNESS = 1 << 27, + }; + +private: + String path_to_file; + mutable RID texture; + Image::Format format = Image::FORMAT_L8; + int w = 0; + int h = 0; + mutable Ref<BitMap> alpha_cache; + + Error _load_data(const String &p_path, int &r_width, int &r_height, Ref<Image> &image, bool &r_request_3d, bool &r_request_normal, bool &r_request_roughness, int &mipmap_limit, int p_size_limit = 0); + virtual void reload_from_file() override; + + static void _requested_3d(void *p_ud); + static void _requested_roughness(void *p_ud, const String &p_normal_path, RS::TextureDetectRoughnessChannel p_roughness_channel); + static void _requested_normal(void *p_ud); + +protected: + static void _bind_methods(); + void _validate_property(PropertyInfo &p_property) const; + +public: + static Ref<Image> load_image_from_file(Ref<FileAccess> p_file, int p_size_limit); + + typedef void (*TextureFormatRequestCallback)(const Ref<CompressedTexture2D> &); + typedef void (*TextureFormatRoughnessRequestCallback)(const Ref<CompressedTexture2D> &, const String &p_normal_path, RS::TextureDetectRoughnessChannel p_roughness_channel); + + static TextureFormatRequestCallback request_3d_callback; + static TextureFormatRoughnessRequestCallback request_roughness_callback; + static TextureFormatRequestCallback request_normal_callback; + + Image::Format get_format() const; + Error load(const String &p_path); + String get_load_path() const; + + int get_width() const override; + int get_height() const override; + virtual RID get_rid() const override; + + virtual void set_path(const String &p_path, bool p_take_over) override; + + virtual void draw(RID p_canvas_item, const Point2 &p_pos, const Color &p_modulate = Color(1, 1, 1), bool p_transpose = false) const override; + virtual void draw_rect(RID p_canvas_item, const Rect2 &p_rect, bool p_tile = false, const Color &p_modulate = Color(1, 1, 1), bool p_transpose = false) const override; + virtual void draw_rect_region(RID p_canvas_item, const Rect2 &p_rect, const Rect2 &p_src_rect, const Color &p_modulate = Color(1, 1, 1), bool p_transpose = false, bool p_clip_uv = true) const override; + + virtual bool has_alpha() const override; + bool is_pixel_opaque(int p_x, int p_y) const override; + + virtual Ref<Image> get_image() const override; + + CompressedTexture2D(); + ~CompressedTexture2D(); +}; + +class ResourceFormatLoaderCompressedTexture2D : public ResourceFormatLoader { +public: + virtual Ref<Resource> load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE); + virtual void get_recognized_extensions(List<String> *p_extensions) const; + virtual bool handles_type(const String &p_type) const; + virtual String get_resource_type(const String &p_path) const; +}; + +class CompressedTextureLayered : public TextureLayered { + GDCLASS(CompressedTextureLayered, TextureLayered); + +public: + enum DataFormat { + DATA_FORMAT_IMAGE, + DATA_FORMAT_PNG, + DATA_FORMAT_WEBP, + DATA_FORMAT_BASIS_UNIVERSAL, + }; + + enum { + FORMAT_VERSION = 1 + }; + + enum FormatBits { + FORMAT_BIT_STREAM = 1 << 22, + FORMAT_BIT_HAS_MIPMAPS = 1 << 23, + }; + +private: + Error _load_data(const String &p_path, Vector<Ref<Image>> &images, int &mipmap_limit, int p_size_limit = 0); + String path_to_file; + mutable RID texture; + Image::Format format = Image::FORMAT_L8; + int w = 0; + int h = 0; + int layers = 0; + bool mipmaps = false; + LayeredType layered_type = LayeredType::LAYERED_TYPE_2D_ARRAY; + + virtual void reload_from_file() override; + +protected: + static void _bind_methods(); + void _validate_property(PropertyInfo &p_property) const; + +public: + Image::Format get_format() const override; + Error load(const String &p_path); + String get_load_path() const; + virtual LayeredType get_layered_type() const override; + + int get_width() const override; + int get_height() const override; + int get_layers() const override; + virtual bool has_mipmaps() const override; + virtual RID get_rid() const override; + + virtual void set_path(const String &p_path, bool p_take_over) override; + + virtual Ref<Image> get_layer_data(int p_layer) const override; + + CompressedTextureLayered(LayeredType p_layered_type); + ~CompressedTextureLayered(); +}; + +class ResourceFormatLoaderCompressedTextureLayered : public ResourceFormatLoader { +public: + virtual Ref<Resource> load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE); + virtual void get_recognized_extensions(List<String> *p_extensions) const; + virtual bool handles_type(const String &p_type) const; + virtual String get_resource_type(const String &p_path) const; +}; + +class CompressedTexture2DArray : public CompressedTextureLayered { + GDCLASS(CompressedTexture2DArray, CompressedTextureLayered) +public: + CompressedTexture2DArray() : + CompressedTextureLayered(LAYERED_TYPE_2D_ARRAY) {} +}; + +class CompressedCubemap : public CompressedTextureLayered { + GDCLASS(CompressedCubemap, CompressedTextureLayered); + +public: + CompressedCubemap() : + CompressedTextureLayered(LAYERED_TYPE_CUBEMAP) {} +}; + +class CompressedCubemapArray : public CompressedTextureLayered { + GDCLASS(CompressedCubemapArray, CompressedTextureLayered); + +public: + CompressedCubemapArray() : + CompressedTextureLayered(LAYERED_TYPE_CUBEMAP_ARRAY) {} +}; + +class CompressedTexture3D : public Texture3D { + GDCLASS(CompressedTexture3D, Texture3D); + +public: + enum DataFormat { + DATA_FORMAT_IMAGE, + DATA_FORMAT_PNG, + DATA_FORMAT_WEBP, + DATA_FORMAT_BASIS_UNIVERSAL, + }; + + enum { + FORMAT_VERSION = 1 + }; + + enum FormatBits { + FORMAT_BIT_STREAM = 1 << 22, + FORMAT_BIT_HAS_MIPMAPS = 1 << 23, + }; + +private: + Error _load_data(const String &p_path, Vector<Ref<Image>> &r_data, Image::Format &r_format, int &r_width, int &r_height, int &r_depth, bool &r_mipmaps); + String path_to_file; + mutable RID texture; + Image::Format format = Image::FORMAT_L8; + int w = 0; + int h = 0; + int d = 0; + bool mipmaps = false; + + virtual void reload_from_file() override; + +protected: + static void _bind_methods(); + void _validate_property(PropertyInfo &p_property) const; + +public: + Image::Format get_format() const override; + Error load(const String &p_path); + String get_load_path() const; + + int get_width() const override; + int get_height() const override; + int get_depth() const override; + virtual bool has_mipmaps() const override; + virtual RID get_rid() const override; + + virtual void set_path(const String &p_path, bool p_take_over) override; + + virtual Vector<Ref<Image>> get_data() const override; + + CompressedTexture3D(); + ~CompressedTexture3D(); +}; + +class ResourceFormatLoaderCompressedTexture3D : public ResourceFormatLoader { +public: + virtual Ref<Resource> load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE); + virtual void get_recognized_extensions(List<String> *p_extensions) const; + virtual bool handles_type(const String &p_type) const; + virtual String get_resource_type(const String &p_path) const; +}; + +#endif // COMPRESSED_TEXTURE_H diff --git a/scene/resources/concave_polygon_shape_3d.cpp b/scene/resources/concave_polygon_shape_3d.cpp index cb5e0fc600..82b125905f 100644 --- a/scene/resources/concave_polygon_shape_3d.cpp +++ b/scene/resources/concave_polygon_shape_3d.cpp @@ -81,7 +81,7 @@ void ConcavePolygonShape3D::_update_shape() { void ConcavePolygonShape3D::set_faces(const Vector<Vector3> &p_faces) { faces = p_faces; _update_shape(); - notify_change_to_owners(); + emit_changed(); } Vector<Vector3> ConcavePolygonShape3D::get_faces() const { @@ -93,7 +93,7 @@ void ConcavePolygonShape3D::set_backface_collision_enabled(bool p_enabled) { if (!faces.is_empty()) { _update_shape(); - notify_change_to_owners(); + emit_changed(); } } diff --git a/scene/resources/convex_polygon_shape_3d.cpp b/scene/resources/convex_polygon_shape_3d.cpp index cc0ef8f583..3bfeeca461 100644 --- a/scene/resources/convex_polygon_shape_3d.cpp +++ b/scene/resources/convex_polygon_shape_3d.cpp @@ -71,7 +71,7 @@ void ConvexPolygonShape3D::_update_shape() { void ConvexPolygonShape3D::set_points(const Vector<Vector3> &p_points) { points = p_points; _update_shape(); - notify_change_to_owners(); + emit_changed(); } Vector<Vector3> ConvexPolygonShape3D::get_points() const { diff --git a/scene/resources/curve_texture.cpp b/scene/resources/curve_texture.cpp new file mode 100644 index 0000000000..3578b46308 --- /dev/null +++ b/scene/resources/curve_texture.cpp @@ -0,0 +1,376 @@ +/**************************************************************************/ +/* curve_texture.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "curve_texture.h" + +#include "core/core_string_names.h" + +void CurveTexture::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_width", "width"), &CurveTexture::set_width); + + ClassDB::bind_method(D_METHOD("set_curve", "curve"), &CurveTexture::set_curve); + ClassDB::bind_method(D_METHOD("get_curve"), &CurveTexture::get_curve); + + ClassDB::bind_method(D_METHOD("set_texture_mode", "texture_mode"), &CurveTexture::set_texture_mode); + ClassDB::bind_method(D_METHOD("get_texture_mode"), &CurveTexture::get_texture_mode); + + ClassDB::bind_method(D_METHOD("_update"), &CurveTexture::_update); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "width", PROPERTY_HINT_RANGE, "1,4096,suffix:px"), "set_width", "get_width"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "texture_mode", PROPERTY_HINT_ENUM, "RGB,Red"), "set_texture_mode", "get_texture_mode"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_curve", "get_curve"); + + BIND_ENUM_CONSTANT(TEXTURE_MODE_RGB); + BIND_ENUM_CONSTANT(TEXTURE_MODE_RED); +} + +void CurveTexture::set_width(int p_width) { + ERR_FAIL_COND(p_width < 32 || p_width > 4096); + + if (_width == p_width) { + return; + } + + _width = p_width; + _update(); +} + +int CurveTexture::get_width() const { + return _width; +} + +void CurveTexture::ensure_default_setup(float p_min, float p_max) { + if (_curve.is_null()) { + Ref<Curve> curve = Ref<Curve>(memnew(Curve)); + curve->add_point(Vector2(0, 1)); + curve->add_point(Vector2(1, 1)); + curve->set_min_value(p_min); + curve->set_max_value(p_max); + set_curve(curve); + // Min and max is 0..1 by default + } +} + +void CurveTexture::set_curve(Ref<Curve> p_curve) { + if (_curve != p_curve) { + if (_curve.is_valid()) { + _curve->disconnect_changed(callable_mp(this, &CurveTexture::_update)); + } + _curve = p_curve; + if (_curve.is_valid()) { + _curve->connect_changed(callable_mp(this, &CurveTexture::_update)); + } + _update(); + } +} + +void CurveTexture::_update() { + Vector<uint8_t> data; + data.resize(_width * sizeof(float) * (texture_mode == TEXTURE_MODE_RGB ? 3 : 1)); + + // The array is locked in that scope + { + uint8_t *wd8 = data.ptrw(); + float *wd = (float *)wd8; + + if (_curve.is_valid()) { + Curve &curve = **_curve; + for (int i = 0; i < _width; ++i) { + float t = i / static_cast<float>(_width); + if (texture_mode == TEXTURE_MODE_RGB) { + wd[i * 3 + 0] = curve.sample_baked(t); + wd[i * 3 + 1] = wd[i * 3 + 0]; + wd[i * 3 + 2] = wd[i * 3 + 0]; + } else { + wd[i] = curve.sample_baked(t); + } + } + + } else { + for (int i = 0; i < _width; ++i) { + if (texture_mode == TEXTURE_MODE_RGB) { + wd[i * 3 + 0] = 0; + wd[i * 3 + 1] = 0; + wd[i * 3 + 2] = 0; + } else { + wd[i] = 0; + } + } + } + } + + Ref<Image> image = memnew(Image(_width, 1, false, texture_mode == TEXTURE_MODE_RGB ? Image::FORMAT_RGBF : Image::FORMAT_RF, data)); + + if (_texture.is_valid()) { + if (_current_texture_mode != texture_mode || _current_width != _width) { + RID new_texture = RS::get_singleton()->texture_2d_create(image); + RS::get_singleton()->texture_replace(_texture, new_texture); + } else { + RS::get_singleton()->texture_2d_update(_texture, image); + } + } else { + _texture = RS::get_singleton()->texture_2d_create(image); + } + _current_texture_mode = texture_mode; + _current_width = _width; + + emit_changed(); +} + +Ref<Curve> CurveTexture::get_curve() const { + return _curve; +} + +void CurveTexture::set_texture_mode(TextureMode p_mode) { + ERR_FAIL_COND(p_mode < TEXTURE_MODE_RGB || p_mode > TEXTURE_MODE_RED); + if (texture_mode == p_mode) { + return; + } + texture_mode = p_mode; + _update(); +} +CurveTexture::TextureMode CurveTexture::get_texture_mode() const { + return texture_mode; +} + +RID CurveTexture::get_rid() const { + if (!_texture.is_valid()) { + _texture = RS::get_singleton()->texture_2d_placeholder_create(); + } + return _texture; +} + +CurveTexture::CurveTexture() {} + +CurveTexture::~CurveTexture() { + if (_texture.is_valid()) { + ERR_FAIL_NULL(RenderingServer::get_singleton()); + RS::get_singleton()->free(_texture); + } +} + +////////////////// + +void CurveXYZTexture::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_width", "width"), &CurveXYZTexture::set_width); + + ClassDB::bind_method(D_METHOD("set_curve_x", "curve"), &CurveXYZTexture::set_curve_x); + ClassDB::bind_method(D_METHOD("get_curve_x"), &CurveXYZTexture::get_curve_x); + + ClassDB::bind_method(D_METHOD("set_curve_y", "curve"), &CurveXYZTexture::set_curve_y); + ClassDB::bind_method(D_METHOD("get_curve_y"), &CurveXYZTexture::get_curve_y); + + ClassDB::bind_method(D_METHOD("set_curve_z", "curve"), &CurveXYZTexture::set_curve_z); + ClassDB::bind_method(D_METHOD("get_curve_z"), &CurveXYZTexture::get_curve_z); + + ClassDB::bind_method(D_METHOD("_update"), &CurveXYZTexture::_update); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "width", PROPERTY_HINT_RANGE, "1,4096,suffix:px"), "set_width", "get_width"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "curve_x", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_curve_x", "get_curve_x"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "curve_y", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_curve_y", "get_curve_y"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "curve_z", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_curve_z", "get_curve_z"); +} + +void CurveXYZTexture::set_width(int p_width) { + ERR_FAIL_COND(p_width < 32 || p_width > 4096); + + if (_width == p_width) { + return; + } + + _width = p_width; + _update(); +} + +int CurveXYZTexture::get_width() const { + return _width; +} + +void CurveXYZTexture::ensure_default_setup(float p_min, float p_max) { + if (_curve_x.is_null()) { + Ref<Curve> curve = Ref<Curve>(memnew(Curve)); + curve->add_point(Vector2(0, 1)); + curve->add_point(Vector2(1, 1)); + curve->set_min_value(p_min); + curve->set_max_value(p_max); + set_curve_x(curve); + } + + if (_curve_y.is_null()) { + Ref<Curve> curve = Ref<Curve>(memnew(Curve)); + curve->add_point(Vector2(0, 1)); + curve->add_point(Vector2(1, 1)); + curve->set_min_value(p_min); + curve->set_max_value(p_max); + set_curve_y(curve); + } + + if (_curve_z.is_null()) { + Ref<Curve> curve = Ref<Curve>(memnew(Curve)); + curve->add_point(Vector2(0, 1)); + curve->add_point(Vector2(1, 1)); + curve->set_min_value(p_min); + curve->set_max_value(p_max); + set_curve_z(curve); + } +} + +void CurveXYZTexture::set_curve_x(Ref<Curve> p_curve) { + if (_curve_x != p_curve) { + if (_curve_x.is_valid()) { + _curve_x->disconnect_changed(callable_mp(this, &CurveXYZTexture::_update)); + } + _curve_x = p_curve; + if (_curve_x.is_valid()) { + _curve_x->connect_changed(callable_mp(this, &CurveXYZTexture::_update), CONNECT_REFERENCE_COUNTED); + } + _update(); + } +} + +void CurveXYZTexture::set_curve_y(Ref<Curve> p_curve) { + if (_curve_y != p_curve) { + if (_curve_y.is_valid()) { + _curve_y->disconnect_changed(callable_mp(this, &CurveXYZTexture::_update)); + } + _curve_y = p_curve; + if (_curve_y.is_valid()) { + _curve_y->connect_changed(callable_mp(this, &CurveXYZTexture::_update), CONNECT_REFERENCE_COUNTED); + } + _update(); + } +} + +void CurveXYZTexture::set_curve_z(Ref<Curve> p_curve) { + if (_curve_z != p_curve) { + if (_curve_z.is_valid()) { + _curve_z->disconnect_changed(callable_mp(this, &CurveXYZTexture::_update)); + } + _curve_z = p_curve; + if (_curve_z.is_valid()) { + _curve_z->connect_changed(callable_mp(this, &CurveXYZTexture::_update), CONNECT_REFERENCE_COUNTED); + } + _update(); + } +} + +void CurveXYZTexture::_update() { + Vector<uint8_t> data; + data.resize(_width * sizeof(float) * 3); + + // The array is locked in that scope + { + uint8_t *wd8 = data.ptrw(); + float *wd = (float *)wd8; + + if (_curve_x.is_valid()) { + Curve &curve_x = **_curve_x; + for (int i = 0; i < _width; ++i) { + float t = i / static_cast<float>(_width); + wd[i * 3 + 0] = curve_x.sample_baked(t); + } + + } else { + for (int i = 0; i < _width; ++i) { + wd[i * 3 + 0] = 0; + } + } + + if (_curve_y.is_valid()) { + Curve &curve_y = **_curve_y; + for (int i = 0; i < _width; ++i) { + float t = i / static_cast<float>(_width); + wd[i * 3 + 1] = curve_y.sample_baked(t); + } + + } else { + for (int i = 0; i < _width; ++i) { + wd[i * 3 + 1] = 0; + } + } + + if (_curve_z.is_valid()) { + Curve &curve_z = **_curve_z; + for (int i = 0; i < _width; ++i) { + float t = i / static_cast<float>(_width); + wd[i * 3 + 2] = curve_z.sample_baked(t); + } + + } else { + for (int i = 0; i < _width; ++i) { + wd[i * 3 + 2] = 0; + } + } + } + + Ref<Image> image = memnew(Image(_width, 1, false, Image::FORMAT_RGBF, data)); + + if (_texture.is_valid()) { + if (_current_width != _width) { + RID new_texture = RS::get_singleton()->texture_2d_create(image); + RS::get_singleton()->texture_replace(_texture, new_texture); + } else { + RS::get_singleton()->texture_2d_update(_texture, image); + } + } else { + _texture = RS::get_singleton()->texture_2d_create(image); + } + _current_width = _width; + + emit_changed(); +} + +Ref<Curve> CurveXYZTexture::get_curve_x() const { + return _curve_x; +} + +Ref<Curve> CurveXYZTexture::get_curve_y() const { + return _curve_y; +} + +Ref<Curve> CurveXYZTexture::get_curve_z() const { + return _curve_z; +} + +RID CurveXYZTexture::get_rid() const { + if (!_texture.is_valid()) { + _texture = RS::get_singleton()->texture_2d_placeholder_create(); + } + return _texture; +} + +CurveXYZTexture::CurveXYZTexture() {} + +CurveXYZTexture::~CurveXYZTexture() { + if (_texture.is_valid()) { + ERR_FAIL_NULL(RenderingServer::get_singleton()); + RS::get_singleton()->free(_texture); + } +} diff --git a/scene/resources/curve_texture.h b/scene/resources/curve_texture.h new file mode 100644 index 0000000000..fd590aefa9 --- /dev/null +++ b/scene/resources/curve_texture.h @@ -0,0 +1,122 @@ +/**************************************************************************/ +/* curve_texture.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef CURVE_TEXTURE_H +#define CURVE_TEXTURE_H + +#include "scene/resources/texture.h" + +class CurveTexture : public Texture2D { + GDCLASS(CurveTexture, Texture2D); + RES_BASE_EXTENSION("curvetex") +public: + enum TextureMode { + TEXTURE_MODE_RGB, + TEXTURE_MODE_RED, + }; + +private: + mutable RID _texture; + Ref<Curve> _curve; + int _width = 256; + int _current_width = 0; + TextureMode texture_mode = TEXTURE_MODE_RGB; + TextureMode _current_texture_mode = TEXTURE_MODE_RGB; + + void _update(); + +protected: + static void _bind_methods(); + +public: + void set_width(int p_width); + int get_width() const override; + + void set_texture_mode(TextureMode p_mode); + TextureMode get_texture_mode() const; + + void ensure_default_setup(float p_min = 0, float p_max = 1); + + void set_curve(Ref<Curve> p_curve); + Ref<Curve> get_curve() const; + + virtual RID get_rid() const override; + + virtual int get_height() const override { return 1; } + virtual bool has_alpha() const override { return false; } + + CurveTexture(); + ~CurveTexture(); +}; + +VARIANT_ENUM_CAST(CurveTexture::TextureMode) + +class CurveXYZTexture : public Texture2D { + GDCLASS(CurveXYZTexture, Texture2D); + RES_BASE_EXTENSION("curvetex") + +private: + mutable RID _texture; + Ref<Curve> _curve_x; + Ref<Curve> _curve_y; + Ref<Curve> _curve_z; + int _width = 256; + int _current_width = 0; + + void _update(); + +protected: + static void _bind_methods(); + +public: + void set_width(int p_width); + int get_width() const override; + + void ensure_default_setup(float p_min = 0, float p_max = 1); + + void set_curve_x(Ref<Curve> p_curve); + Ref<Curve> get_curve_x() const; + + void set_curve_y(Ref<Curve> p_curve); + Ref<Curve> get_curve_y() const; + + void set_curve_z(Ref<Curve> p_curve); + Ref<Curve> get_curve_z() const; + + virtual RID get_rid() const override; + + virtual int get_height() const override { return 1; } + virtual bool has_alpha() const override { return false; } + + CurveXYZTexture(); + ~CurveXYZTexture(); +}; + +#endif // CURVE_TEXTURE_H diff --git a/scene/resources/cylinder_shape_3d.cpp b/scene/resources/cylinder_shape_3d.cpp index aee2963b2e..a91282fd33 100644 --- a/scene/resources/cylinder_shape_3d.cpp +++ b/scene/resources/cylinder_shape_3d.cpp @@ -76,7 +76,7 @@ void CylinderShape3D::set_radius(float p_radius) { ERR_FAIL_COND_MSG(p_radius < 0, "CylinderShape3D radius cannot be negative."); radius = p_radius; _update_shape(); - notify_change_to_owners(); + emit_changed(); } float CylinderShape3D::get_radius() const { @@ -87,7 +87,7 @@ void CylinderShape3D::set_height(float p_height) { ERR_FAIL_COND_MSG(p_height < 0, "CylinderShape3D height cannot be negative."); height = p_height; _update_shape(); - notify_change_to_owners(); + emit_changed(); } float CylinderShape3D::get_height() const { diff --git a/scene/resources/default_theme/default_theme.cpp b/scene/resources/default_theme/default_theme.cpp index 07f38473cb..b6a1737acb 100644 --- a/scene/resources/default_theme/default_theme.cpp +++ b/scene/resources/default_theme/default_theme.cpp @@ -34,6 +34,10 @@ #include "default_font.gen.h" #include "default_theme_icons.gen.h" #include "scene/resources/font.h" +#include "scene/resources/gradient_texture.h" +#include "scene/resources/image_texture.h" +#include "scene/resources/style_box_flat.h" +#include "scene/resources/style_box_line.h" #include "scene/resources/theme.h" #include "scene/theme/theme_db.h" #include "servers/text_server.h" @@ -53,24 +57,24 @@ static const int default_corner_radius = 3; static Ref<StyleBoxFlat> make_flat_stylebox(Color p_color, float p_margin_left = default_margin, float p_margin_top = default_margin, float p_margin_right = default_margin, float p_margin_bottom = default_margin, int p_corner_radius = default_corner_radius, bool p_draw_center = true, int p_border_width = 0) { Ref<StyleBoxFlat> style(memnew(StyleBoxFlat)); style->set_bg_color(p_color); - style->set_content_margin_individual(p_margin_left * scale, p_margin_top * scale, p_margin_right * scale, p_margin_bottom * scale); + style->set_content_margin_individual(Math::round(p_margin_left * scale), Math::round(p_margin_top * scale), Math::round(p_margin_right * scale), Math::round(p_margin_bottom * scale)); - style->set_corner_radius_all(p_corner_radius); + style->set_corner_radius_all(Math::round(p_corner_radius * scale)); style->set_anti_aliased(true); // Adjust level of detail based on the corners' effective sizes. style->set_corner_detail(MIN(Math::ceil(1.5 * p_corner_radius), 6) * scale); style->set_draw_center(p_draw_center); - style->set_border_width_all(p_border_width); + style->set_border_width_all(Math::round(p_border_width * scale)); return style; } static Ref<StyleBoxFlat> sb_expand(Ref<StyleBoxFlat> p_sbox, float p_left, float p_top, float p_right, float p_bottom) { - p_sbox->set_expand_margin(SIDE_LEFT, p_left * scale); - p_sbox->set_expand_margin(SIDE_TOP, p_top * scale); - p_sbox->set_expand_margin(SIDE_RIGHT, p_right * scale); - p_sbox->set_expand_margin(SIDE_BOTTOM, p_bottom * scale); + p_sbox->set_expand_margin(SIDE_LEFT, Math::round(p_left * scale)); + p_sbox->set_expand_margin(SIDE_TOP, Math::round(p_top * scale)); + p_sbox->set_expand_margin(SIDE_RIGHT, Math::round(p_right * scale)); + p_sbox->set_expand_margin(SIDE_BOTTOM, Math::round(p_bottom * scale)); return p_sbox; } @@ -89,7 +93,7 @@ static Ref<ImageTexture> generate_icon(int p_index) { #else // If the SVG module is disabled, we can't really display the UI well, but at least we won't crash. // 16 pixels is used as it's the most common base size for Godot icons. - img = Image::create_empty(16 * scale, 16 * scale, false, Image::FORMAT_RGBA8); + img = Image::create_empty(Math::round(16 * scale), Math::round(16 * scale), false, Image::FORMAT_RGBA8); #endif return ImageTexture::create_from_image(img); @@ -97,7 +101,7 @@ static Ref<ImageTexture> generate_icon(int p_index) { static Ref<StyleBox> make_empty_stylebox(float p_margin_left = -1, float p_margin_top = -1, float p_margin_right = -1, float p_margin_bottom = -1) { Ref<StyleBox> style(memnew(StyleBoxEmpty)); - style->set_content_margin_individual(p_margin_left * scale, p_margin_top * scale, p_margin_right * scale, p_margin_bottom * scale); + style->set_content_margin_individual(Math::round(p_margin_left * scale), Math::round(p_margin_top * scale), Math::round(p_margin_right * scale), Math::round(p_margin_bottom * scale)); return style; } @@ -106,7 +110,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const // Default theme properties. theme->set_default_font(default_font); - theme->set_default_font_size(default_font_size * scale); + theme->set_default_font_size(Math::round(default_font_size * scale)); theme->set_default_base_scale(scale); // Font colors @@ -152,7 +156,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const const Ref<StyleBoxFlat> button_disabled = make_flat_stylebox(style_disabled_color); Ref<StyleBoxFlat> focus = make_flat_stylebox(style_focus_color, default_margin, default_margin, default_margin, default_margin, default_corner_radius, false, 2); // Make the focus outline appear to be flush with the buttons it's focusing. - focus->set_expand_margin_all(2 * scale); + focus->set_expand_margin_all(Math::round(2 * scale)); theme->set_stylebox("normal", "Button", button_normal); theme->set_stylebox("hover", "Button", button_hover); @@ -179,7 +183,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_color("icon_focus_color", "Button", Color(1, 1, 1, 1)); theme->set_color("icon_disabled_color", "Button", Color(1, 1, 1, 0.4)); - theme->set_constant("h_separation", "Button", 2 * scale); + theme->set_constant("h_separation", "Button", Math::round(2 * scale)); theme->set_constant("icon_max_width", "Button", 0); // MenuBar @@ -201,7 +205,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_color("font_disabled_color", "MenuBar", control_font_disabled_color); theme->set_color("font_outline_color", "MenuBar", Color(1, 1, 1)); - theme->set_constant("h_separation", "MenuBar", 4 * scale); + theme->set_constant("h_separation", "MenuBar", Math::round(4 * scale)); // LinkButton @@ -217,7 +221,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_color("font_outline_color", "LinkButton", Color(1, 1, 1)); theme->set_constant("outline_size", "LinkButton", 0); - theme->set_constant("underline_spacing", "LinkButton", 2 * scale); + theme->set_constant("underline_spacing", "LinkButton", Math::round(2 * scale)); // OptionButton theme->set_stylebox("focus", "OptionButton", focus); @@ -255,8 +259,8 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_color("font_disabled_color", "OptionButton", control_font_disabled_color); theme->set_color("font_outline_color", "OptionButton", Color(1, 1, 1)); - theme->set_constant("h_separation", "OptionButton", 2 * scale); - theme->set_constant("arrow_margin", "OptionButton", 4 * scale); + theme->set_constant("h_separation", "OptionButton", Math::round(2 * scale)); + theme->set_constant("arrow_margin", "OptionButton", Math::round(4 * scale)); theme->set_constant("outline_size", "OptionButton", 0); theme->set_constant("modulate_arrow", "OptionButton", false); @@ -278,15 +282,15 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_color("font_disabled_color", "MenuButton", Color(1, 1, 1, 0.3)); theme->set_color("font_outline_color", "MenuButton", Color(1, 1, 1)); - theme->set_constant("h_separation", "MenuButton", 3 * scale); + theme->set_constant("h_separation", "MenuButton", Math::round(3 * scale)); theme->set_constant("outline_size", "MenuButton", 0); // CheckBox Ref<StyleBox> cbx_empty = memnew(StyleBoxEmpty); - cbx_empty->set_content_margin_all(4 * scale); + cbx_empty->set_content_margin_all(Math::round(4 * scale)); Ref<StyleBox> cbx_focus = focus; - cbx_focus->set_content_margin_all(4 * scale); + cbx_focus->set_content_margin_all(Math::round(4 * scale)); theme->set_stylebox("normal", "CheckBox", cbx_empty); theme->set_stylebox("pressed", "CheckBox", cbx_empty); @@ -315,14 +319,14 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_color("font_disabled_color", "CheckBox", control_font_disabled_color); theme->set_color("font_outline_color", "CheckBox", Color(1, 1, 1)); - theme->set_constant("h_separation", "CheckBox", 4 * scale); + theme->set_constant("h_separation", "CheckBox", Math::round(4 * scale)); theme->set_constant("check_v_offset", "CheckBox", 0); theme->set_constant("outline_size", "CheckBox", 0); // CheckButton Ref<StyleBox> cb_empty = memnew(StyleBoxEmpty); - cb_empty->set_content_margin_individual(6 * scale, 4 * scale, 6 * scale, 4 * scale); + cb_empty->set_content_margin_individual(Math::round(6 * scale), Math::round(4 * scale), Math::round(6 * scale), Math::round(4 * scale)); theme->set_stylebox("normal", "CheckButton", cb_empty); theme->set_stylebox("pressed", "CheckButton", cb_empty); @@ -352,7 +356,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_color("font_disabled_color", "CheckButton", control_font_disabled_color); theme->set_color("font_outline_color", "CheckButton", Color(1, 1, 1)); - theme->set_constant("h_separation", "CheckButton", 4 * scale); + theme->set_constant("h_separation", "CheckButton", Math::round(4 * scale)); theme->set_constant("check_v_offset", "CheckButton", 0); theme->set_constant("outline_size", "CheckButton", 0); @@ -366,11 +370,11 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_color("font_shadow_color", "Label", Color(0, 0, 0, 0)); theme->set_color("font_outline_color", "Label", Color(1, 1, 1)); - theme->set_constant("shadow_offset_x", "Label", 1 * scale); - theme->set_constant("shadow_offset_y", "Label", 1 * scale); + theme->set_constant("shadow_offset_x", "Label", Math::round(1 * scale)); + theme->set_constant("shadow_offset_y", "Label", Math::round(1 * scale)); theme->set_constant("outline_size", "Label", 0); - theme->set_constant("shadow_outline_size", "Label", 1 * scale); - theme->set_constant("line_spacing", "Label", 3 * scale); + theme->set_constant("shadow_outline_size", "Label", Math::round(1 * scale)); + theme->set_constant("line_spacing", "Label", Math::round(3 * scale)); theme->set_type_variation("HeaderSmall", "Label"); theme->set_font_size("font_size", "HeaderSmall", default_font_size + 4); @@ -456,7 +460,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_color("search_result_color", "TextEdit", Color(0.3, 0.3, 0.3)); theme->set_color("search_result_border_color", "TextEdit", Color(0.3, 0.3, 0.3, 0.4)); - theme->set_constant("line_spacing", "TextEdit", 4 * scale); + theme->set_constant("line_spacing", "TextEdit", Math::round(4 * scale)); theme->set_constant("outline_size", "TextEdit", 0); theme->set_constant("caret_width", "TextEdit", 1); @@ -509,7 +513,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_constant("completion_lines", "CodeEdit", 7); theme->set_constant("completion_max_width", "CodeEdit", 50); theme->set_constant("completion_scroll_width", "CodeEdit", 6); - theme->set_constant("line_spacing", "CodeEdit", 4 * scale); + theme->set_constant("line_spacing", "CodeEdit", Math::round(4 * scale)); theme->set_constant("outline_size", "CodeEdit", 0); Ref<Texture2D> empty_icon = memnew(ImageTexture); @@ -595,6 +599,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const // Window theme->set_stylebox("embedded_border", "Window", sb_expand(make_flat_stylebox(style_popup_color, 10, 28, 10, 8), 8, 32, 8, 6)); + theme->set_stylebox("embedded_unfocused_border", "Window", sb_expand(make_flat_stylebox(style_popup_hover_color, 10, 28, 10, 8), 8, 32, 8, 6)); theme->set_font("title_font", "Window", Ref<Font>()); theme->set_font_size("title_font_size", "Window", -1); @@ -602,7 +607,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_color("title_outline_modulate", "Window", Color(1, 1, 1)); theme->set_constant("title_outline_size", "Window", 0); theme->set_constant("title_height", "Window", 36 * scale); - theme->set_constant("resize_margin", "Window", 4 * scale); + theme->set_constant("resize_margin", "Window", Math::round(4 * scale)); theme->set_icon("close", "Window", icons["close"]); theme->set_icon("close_pressed", "Window", icons["close_hl"]); @@ -612,8 +617,8 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const // Dialogs // AcceptDialog is currently the base dialog, so this defines styles for all extending nodes. - theme->set_stylebox("panel", "AcceptDialog", make_flat_stylebox(style_popup_color, 8 * scale, 8 * scale, 8 * scale, 8 * scale)); - theme->set_constant("buttons_separation", "AcceptDialog", 10 * scale); + theme->set_stylebox("panel", "AcceptDialog", make_flat_stylebox(style_popup_color, Math::round(8 * scale), Math::round(8 * scale), Math::round(8 * scale), Math::round(8 * scale))); + theme->set_constant("buttons_separation", "AcceptDialog", Math::round(10 * scale)); // File Dialog @@ -684,13 +689,13 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_color("font_outline_color", "PopupMenu", Color(1, 1, 1)); theme->set_color("font_separator_outline_color", "PopupMenu", Color(1, 1, 1)); - theme->set_constant("indent", "PopupMenu", 10 * scale); - theme->set_constant("h_separation", "PopupMenu", 4 * scale); - theme->set_constant("v_separation", "PopupMenu", 4 * scale); + theme->set_constant("indent", "PopupMenu", Math::round(10 * scale)); + theme->set_constant("h_separation", "PopupMenu", Math::round(4 * scale)); + theme->set_constant("v_separation", "PopupMenu", Math::round(4 * scale)); theme->set_constant("outline_size", "PopupMenu", 0); theme->set_constant("separator_outline_size", "PopupMenu", 0); - theme->set_constant("item_start_padding", "PopupMenu", 2 * scale); - theme->set_constant("item_end_padding", "PopupMenu", 2 * scale); + theme->set_constant("item_start_padding", "PopupMenu", Math::round(2 * scale)); + theme->set_constant("item_end_padding", "PopupMenu", Math::round(2 * scale)); theme->set_constant("icon_max_width", "PopupMenu", 0); // GraphNode @@ -711,8 +716,6 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_stylebox("frame", "GraphNode", graphnode_normal); theme->set_stylebox("selected_frame", "GraphNode", graphnode_selected); - theme->set_stylebox("comment", "GraphNode", graphnode_comment_normal); - theme->set_stylebox("comment_focus", "GraphNode", graphnode_comment_selected); theme->set_stylebox("breakpoint", "GraphNode", graphnode_breakpoint); theme->set_stylebox("position", "GraphNode", graphnode_position); theme->set_stylebox("slot", "GraphNode", graphnode_slot); @@ -724,11 +727,11 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_color("title_color", "GraphNode", control_font_color); theme->set_color("close_color", "GraphNode", control_font_color); theme->set_color("resizer_color", "GraphNode", control_font_color); - theme->set_constant("separation", "GraphNode", 2 * scale); - theme->set_constant("title_offset", "GraphNode", 26 * scale); + theme->set_constant("separation", "GraphNode", Math::round(2 * scale)); + theme->set_constant("title_offset", "GraphNode", Math::round(26 * scale)); theme->set_constant("title_h_offset", "GraphNode", 0); - theme->set_constant("close_offset", "GraphNode", 22 * scale); - theme->set_constant("close_h_offset", "GraphNode", 12 * scale); + theme->set_constant("close_offset", "GraphNode", Math::round(22 * scale)); + theme->set_constant("close_h_offset", "GraphNode", Math::round(12 * scale)); theme->set_constant("port_offset", "GraphNode", 0); // Tree @@ -771,14 +774,14 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_color("children_hl_line_color", "Tree", Color(0.27, 0.27, 0.27)); theme->set_color("custom_button_font_highlight", "Tree", control_font_hover_color); - theme->set_constant("h_separation", "Tree", 4 * scale); - theme->set_constant("v_separation", "Tree", 4 * scale); - theme->set_constant("item_margin", "Tree", 16 * scale); + theme->set_constant("h_separation", "Tree", Math::round(4 * scale)); + theme->set_constant("v_separation", "Tree", Math::round(4 * scale)); + theme->set_constant("item_margin", "Tree", Math::round(16 * scale)); theme->set_constant("inner_item_margin_bottom", "Tree", 0); theme->set_constant("inner_item_margin_left", "Tree", 0); theme->set_constant("inner_item_margin_right", "Tree", 0); theme->set_constant("inner_item_margin_top", "Tree", 0); - theme->set_constant("button_margin", "Tree", 4 * scale); + theme->set_constant("button_margin", "Tree", Math::round(4 * scale)); theme->set_constant("draw_relationship_lines", "Tree", 0); theme->set_constant("relationship_line_width", "Tree", 1); theme->set_constant("parent_hl_line_width", "Tree", 1); @@ -793,8 +796,8 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_constant("scrollbar_margin_top", "Tree", -1); theme->set_constant("scrollbar_margin_right", "Tree", -1); theme->set_constant("scrollbar_margin_bottom", "Tree", -1); - theme->set_constant("scrollbar_h_separation", "Tree", 4 * scale); - theme->set_constant("scrollbar_v_separation", "Tree", 4 * scale); + theme->set_constant("scrollbar_h_separation", "Tree", Math::round(4 * scale)); + theme->set_constant("scrollbar_v_separation", "Tree", Math::round(4 * scale)); // ItemList @@ -803,7 +806,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_constant("h_separation", "ItemList", 4); theme->set_constant("v_separation", "ItemList", 2); theme->set_constant("icon_margin", "ItemList", 4); - theme->set_constant("line_separation", "ItemList", 2 * scale); + theme->set_constant("line_separation", "ItemList", Math::round(2 * scale)); theme->set_font("font", "ItemList", Ref<Font>()); theme->set_font_size("font_size", "ItemList", -1); @@ -861,8 +864,8 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_color("font_outline_color", "TabContainer", Color(1, 1, 1)); theme->set_color("drop_mark_color", "TabContainer", Color(1, 1, 1)); - theme->set_constant("side_margin", "TabContainer", 8 * scale); - theme->set_constant("icon_separation", "TabContainer", 4 * scale); + theme->set_constant("side_margin", "TabContainer", Math::round(8 * scale)); + theme->set_constant("icon_separation", "TabContainer", Math::round(4 * scale)); theme->set_constant("icon_max_width", "TabContainer", 0); theme->set_constant("outline_size", "TabContainer", 0); @@ -892,7 +895,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_color("font_outline_color", "TabBar", Color(1, 1, 1)); theme->set_color("drop_mark_color", "TabBar", Color(1, 1, 1)); - theme->set_constant("h_separation", "TabBar", 4 * scale); + theme->set_constant("h_separation", "TabBar", Math::round(4 * scale)); theme->set_constant("icon_max_width", "TabBar", 0); theme->set_constant("outline_size", "TabBar", 0); @@ -905,16 +908,16 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_font("normal", "Fonts", Ref<Font>()); theme->set_font("large", "Fonts", Ref<Font>()); - theme->set_constant("separation", "HSeparator", 4 * scale); - theme->set_constant("separation", "VSeparator", 4 * scale); + theme->set_constant("separation", "HSeparator", Math::round(4 * scale)); + theme->set_constant("separation", "VSeparator", Math::round(4 * scale)); // ColorPicker - theme->set_constant("margin", "ColorPicker", 4 * scale); - theme->set_constant("sv_width", "ColorPicker", 256 * scale); - theme->set_constant("sv_height", "ColorPicker", 256 * scale); - theme->set_constant("h_width", "ColorPicker", 30 * scale); - theme->set_constant("label_width", "ColorPicker", 10 * scale); + theme->set_constant("margin", "ColorPicker", Math::round(4 * scale)); + theme->set_constant("sv_width", "ColorPicker", Math::round(256 * scale)); + theme->set_constant("sv_height", "ColorPicker", Math::round(256 * scale)); + theme->set_constant("h_width", "ColorPicker", Math::round(30 * scale)); + theme->set_constant("label_width", "ColorPicker", Math::round(10 * scale)); theme->set_constant("center_slider_grabbers", "ColorPicker", 1); theme->set_icon("folded_arrow", "ColorPicker", icons["arrow_right"]); @@ -1002,14 +1005,14 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_color("font_disabled_color", "ColorPickerButton", Color(0.9, 0.9, 0.9, 0.3)); theme->set_color("font_outline_color", "ColorPickerButton", Color(1, 1, 1)); - theme->set_constant("h_separation", "ColorPickerButton", 2 * scale); + theme->set_constant("h_separation", "ColorPickerButton", Math::round(2 * scale)); theme->set_constant("outline_size", "ColorPickerButton", 0); // ColorPresetButton Ref<StyleBoxFlat> preset_sb = make_flat_stylebox(Color(1, 1, 1), 2, 2, 2, 2); - preset_sb->set_corner_radius_all(2); - preset_sb->set_corner_detail(2); + preset_sb->set_corner_radius_all(Math::round(2 * scale)); + preset_sb->set_corner_detail(Math::round(2 * scale)); preset_sb->set_anti_aliased(false); theme->set_stylebox("preset_fg", "ColorPresetButton", preset_sb); @@ -1056,13 +1059,13 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_color("font_outline_color", "RichTextLabel", Color(1, 1, 1)); - theme->set_constant("shadow_offset_x", "RichTextLabel", 1 * scale); - theme->set_constant("shadow_offset_y", "RichTextLabel", 1 * scale); - theme->set_constant("shadow_outline_size", "RichTextLabel", 1 * scale); + theme->set_constant("shadow_offset_x", "RichTextLabel", Math::round(1 * scale)); + theme->set_constant("shadow_offset_y", "RichTextLabel", Math::round(1 * scale)); + theme->set_constant("shadow_outline_size", "RichTextLabel", Math::round(1 * scale)); theme->set_constant("line_separation", "RichTextLabel", 0); - theme->set_constant("table_h_separation", "RichTextLabel", 3 * scale); - theme->set_constant("table_v_separation", "RichTextLabel", 3 * scale); + theme->set_constant("table_h_separation", "RichTextLabel", Math::round(3 * scale)); + theme->set_constant("table_v_separation", "RichTextLabel", Math::round(3 * scale)); theme->set_constant("outline_size", "RichTextLabel", 0); @@ -1070,8 +1073,8 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_color("table_even_row_bg", "RichTextLabel", Color(0, 0, 0, 0)); theme->set_color("table_border", "RichTextLabel", Color(0, 0, 0, 0)); - theme->set_constant("text_highlight_h_padding", "RichTextLabel", 3 * scale); - theme->set_constant("text_highlight_v_padding", "RichTextLabel", 3 * scale); + theme->set_constant("text_highlight_h_padding", "RichTextLabel", Math::round(3 * scale)); + theme->set_constant("text_highlight_v_padding", "RichTextLabel", Math::round(3 * scale)); // Containers @@ -1080,40 +1083,43 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_icon("grabber", "VSplitContainer", icons["vsplitter"]); theme->set_icon("grabber", "HSplitContainer", icons["hsplitter"]); - theme->set_constant("separation", "BoxContainer", 4 * scale); - theme->set_constant("separation", "HBoxContainer", 4 * scale); - theme->set_constant("separation", "VBoxContainer", 4 * scale); + theme->set_constant("separation", "BoxContainer", Math::round(4 * scale)); + theme->set_constant("separation", "HBoxContainer", Math::round(4 * scale)); + theme->set_constant("separation", "VBoxContainer", Math::round(4 * scale)); theme->set_constant("margin_left", "MarginContainer", 0); theme->set_constant("margin_top", "MarginContainer", 0); theme->set_constant("margin_right", "MarginContainer", 0); theme->set_constant("margin_bottom", "MarginContainer", 0); - theme->set_constant("h_separation", "GridContainer", 4 * scale); - theme->set_constant("v_separation", "GridContainer", 4 * scale); - theme->set_constant("separation", "SplitContainer", 12 * scale); - theme->set_constant("separation", "HSplitContainer", 12 * scale); - theme->set_constant("separation", "VSplitContainer", 12 * scale); - theme->set_constant("minimum_grab_thickness", "SplitContainer", 6 * scale); - theme->set_constant("minimum_grab_thickness", "HSplitContainer", 6 * scale); - theme->set_constant("minimum_grab_thickness", "VSplitContainer", 6 * scale); + theme->set_constant("h_separation", "GridContainer", Math::round(4 * scale)); + theme->set_constant("v_separation", "GridContainer", Math::round(4 * scale)); + theme->set_constant("separation", "SplitContainer", Math::round(12 * scale)); + theme->set_constant("separation", "HSplitContainer", Math::round(12 * scale)); + theme->set_constant("separation", "VSplitContainer", Math::round(12 * scale)); + theme->set_constant("minimum_grab_thickness", "SplitContainer", Math::round(6 * scale)); + theme->set_constant("minimum_grab_thickness", "HSplitContainer", Math::round(6 * scale)); + theme->set_constant("minimum_grab_thickness", "VSplitContainer", Math::round(6 * scale)); theme->set_constant("autohide", "SplitContainer", 1); theme->set_constant("autohide", "HSplitContainer", 1); theme->set_constant("autohide", "VSplitContainer", 1); - theme->set_constant("h_separation", "FlowContainer", 4 * scale); - theme->set_constant("v_separation", "FlowContainer", 4 * scale); - theme->set_constant("h_separation", "HFlowContainer", 4 * scale); - theme->set_constant("v_separation", "HFlowContainer", 4 * scale); - theme->set_constant("h_separation", "VFlowContainer", 4 * scale); - theme->set_constant("v_separation", "VFlowContainer", 4 * scale); + theme->set_constant("h_separation", "FlowContainer", Math::round(4 * scale)); + theme->set_constant("v_separation", "FlowContainer", Math::round(4 * scale)); + theme->set_constant("h_separation", "HFlowContainer", Math::round(4 * scale)); + theme->set_constant("v_separation", "HFlowContainer", Math::round(4 * scale)); + theme->set_constant("h_separation", "VFlowContainer", Math::round(4 * scale)); + theme->set_constant("v_separation", "VFlowContainer", Math::round(4 * scale)); theme->set_stylebox("panel", "PanelContainer", make_flat_stylebox(style_normal_color, 0, 0, 0, 0)); - theme->set_icon("minus", "GraphEdit", icons["zoom_less"]); - theme->set_icon("reset", "GraphEdit", icons["zoom_reset"]); - theme->set_icon("more", "GraphEdit", icons["zoom_more"]); - theme->set_icon("snap", "GraphEdit", icons["grid_snap"]); - theme->set_icon("minimap", "GraphEdit", icons["grid_minimap"]); + theme->set_icon("zoom_out", "GraphEdit", icons["zoom_less"]); + theme->set_icon("zoom_in", "GraphEdit", icons["zoom_more"]); + theme->set_icon("zoom_reset", "GraphEdit", icons["zoom_reset"]); + theme->set_icon("grid_toggle", "GraphEdit", icons["grid_toggle"]); + theme->set_icon("minimap_toggle", "GraphEdit", icons["grid_minimap"]); + theme->set_icon("snapping_toggle", "GraphEdit", icons["grid_snap"]); theme->set_icon("layout", "GraphEdit", icons["grid_layout"]); - theme->set_stylebox("bg", "GraphEdit", make_flat_stylebox(style_normal_color, 4, 4, 4, 5)); + + theme->set_stylebox("panel", "GraphEdit", make_flat_stylebox(style_normal_color, 4, 4, 4, 5)); + theme->set_color("grid_minor", "GraphEdit", Color(1, 1, 1, 0.05)); theme->set_color("grid_major", "GraphEdit", Color(1, 1, 1, 0.2)); theme->set_color("selection_fill", "GraphEdit", Color(1, 1, 1, 0.3)); @@ -1125,7 +1131,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_constant("port_hotzone_inner_extent", "GraphEdit", 22 * scale); theme->set_constant("port_hotzone_outer_extent", "GraphEdit", 26 * scale); - theme->set_stylebox("bg", "GraphEditMinimap", make_flat_stylebox(Color(0.24, 0.24, 0.24), 0, 0, 0, 0)); + theme->set_stylebox("panel", "GraphEditMinimap", make_flat_stylebox(Color(0.24, 0.24, 0.24), 0, 0, 0, 0)); Ref<StyleBoxFlat> style_minimap_camera = make_flat_stylebox(Color(0.65, 0.65, 0.65, 0.2), 0, 0, 0, 0, 0); style_minimap_camera->set_border_color(Color(0.65, 0.65, 0.65, 0.45)); style_minimap_camera->set_border_width_all(1); diff --git a/scene/resources/default_theme/grid_toggle.svg b/scene/resources/default_theme/grid_toggle.svg new file mode 100644 index 0000000000..b0721db518 --- /dev/null +++ b/scene/resources/default_theme/grid_toggle.svg @@ -0,0 +1 @@ +<svg viewBox="0 0 16 16" height="16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="M3 0v3H0v2h3v4H0v2h3v3h2V5h9V3h-3V0H9v3H5V0z" fill="#b2b2b2" fill-opacity=".65"/><path d="M11 6.62c-1.747 0-3.957 1.344-4.752 3.936a.683.69 0 00-.004.394C7.012 13.665 9.292 14.9 11 14.9c1.708 0 3.988-1.235 4.756-3.95a.683.69 0 000-.382C15.004 7.955 12.746 6.62 11 6.62zM11 8a2.733 2.76 0 012.733 2.76A2.733 2.76 0 0111 13.52a2.733 2.76 0 01-2.733-2.76A2.733 2.76 0 0111 8zm0 1.38a1.367 1.38 0 00-1.367 1.38A1.367 1.38 0 0011 12.14a1.367 1.38 0 001.367-1.38A1.367 1.38 0 0011 9.38z" fill="#e0e0e0" /></svg> diff --git a/scene/resources/environment.cpp b/scene/resources/environment.cpp index 757be51017..e48f744c72 100644 --- a/scene/resources/environment.cpp +++ b/scene/resources/environment.cpp @@ -31,9 +31,8 @@ #include "environment.h" #include "core/config/project_settings.h" -#include "core/core_string_names.h" +#include "scene/resources/gradient_texture.h" #include "servers/rendering_server.h" -#include "texture.h" RID Environment::get_rid() const { return environment; @@ -1004,9 +1003,7 @@ void Environment::set_adjustment_color_correction(Ref<Texture> p_color_correctio adjustment_color_correction = p_color_correction; Ref<GradientTexture1D> grad_tex = p_color_correction; if (grad_tex.is_valid()) { - if (!grad_tex->is_connected(CoreStringNames::get_singleton()->changed, callable_mp(this, &Environment::_update_adjustment))) { - grad_tex->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &Environment::_update_adjustment)); - } + grad_tex->connect_changed(callable_mp(this, &Environment::_update_adjustment)); } Ref<Texture2D> adjustment_texture_2d = adjustment_color_correction; if (adjustment_texture_2d.is_valid()) { diff --git a/scene/resources/font.cpp b/scene/resources/font.cpp index d07d9913a6..09a835db1b 100644 --- a/scene/resources/font.cpp +++ b/scene/resources/font.cpp @@ -30,12 +30,12 @@ #include "font.h" -#include "core/core_string_names.h" #include "core/io/image_loader.h" #include "core/io/resource_loader.h" #include "core/string/translation.h" #include "core/templates/hash_map.h" #include "core/templates/hashfuncs.h" +#include "scene/resources/image_texture.h" #include "scene/resources/text_line.h" #include "scene/resources/text_paragraph.h" #include "scene/resources/theme.h" @@ -159,14 +159,14 @@ void Font::set_fallbacks(const TypedArray<Font> &p_fallbacks) { for (int i = 0; i < fallbacks.size(); i++) { Ref<Font> f = fallbacks[i]; if (f.is_valid()) { - f->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(this, &Font::_invalidate_rids)); + f->disconnect_changed(callable_mp(this, &Font::_invalidate_rids)); } } fallbacks = p_fallbacks; for (int i = 0; i < fallbacks.size(); i++) { Ref<Font> f = fallbacks[i]; if (f.is_valid()) { - f->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &Font::_invalidate_rids), CONNECT_REFERENCE_COUNTED); + f->connect_changed(callable_mp(this, &Font::_invalidate_rids), CONNECT_REFERENCE_COUNTED); } } _invalidate_rids(); @@ -2674,12 +2674,12 @@ void FontVariation::_update_rids() const { void FontVariation::reset_state() { if (base_font.is_valid()) { - base_font->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(reinterpret_cast<Font *>(this), &Font::_invalidate_rids)); + base_font->disconnect_changed(callable_mp(reinterpret_cast<Font *>(this), &Font::_invalidate_rids)); base_font.unref(); } if (theme_font.is_valid()) { - theme_font->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(reinterpret_cast<Font *>(this), &Font::_invalidate_rids)); + theme_font->disconnect_changed(callable_mp(reinterpret_cast<Font *>(this), &Font::_invalidate_rids)); theme_font.unref(); } @@ -2696,11 +2696,11 @@ void FontVariation::reset_state() { void FontVariation::set_base_font(const Ref<Font> &p_font) { if (base_font != p_font) { if (base_font.is_valid()) { - base_font->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(reinterpret_cast<Font *>(this), &Font::_invalidate_rids)); + base_font->disconnect_changed(callable_mp(reinterpret_cast<Font *>(this), &Font::_invalidate_rids)); } base_font = p_font; if (base_font.is_valid()) { - base_font->connect(CoreStringNames::get_singleton()->changed, callable_mp(reinterpret_cast<Font *>(this), &Font::_invalidate_rids), CONNECT_REFERENCE_COUNTED); + base_font->connect_changed(callable_mp(reinterpret_cast<Font *>(this), &Font::_invalidate_rids), CONNECT_REFERENCE_COUNTED); } _invalidate_rids(); notify_property_list_changed(); @@ -2713,7 +2713,7 @@ Ref<Font> FontVariation::get_base_font() const { Ref<Font> FontVariation::_get_base_font_or_default() const { if (theme_font.is_valid()) { - theme_font->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(reinterpret_cast<Font *>(const_cast<FontVariation *>(this)), &Font::_invalidate_rids)); + theme_font->disconnect_changed(callable_mp(reinterpret_cast<Font *>(const_cast<FontVariation *>(this)), &Font::_invalidate_rids)); theme_font.unref(); } @@ -2734,7 +2734,7 @@ Ref<Font> FontVariation::_get_base_font_or_default() const { } if (f.is_valid()) { theme_font = f; - theme_font->connect(CoreStringNames::get_singleton()->changed, callable_mp(reinterpret_cast<Font *>(const_cast<FontVariation *>(this)), &Font::_invalidate_rids), CONNECT_REFERENCE_COUNTED); + theme_font->connect_changed(callable_mp(reinterpret_cast<Font *>(const_cast<FontVariation *>(this)), &Font::_invalidate_rids), CONNECT_REFERENCE_COUNTED); } return f; } @@ -2754,7 +2754,7 @@ Ref<Font> FontVariation::_get_base_font_or_default() const { } if (f.is_valid()) { theme_font = f; - theme_font->connect(CoreStringNames::get_singleton()->changed, callable_mp(reinterpret_cast<Font *>(const_cast<FontVariation *>(this)), &Font::_invalidate_rids), CONNECT_REFERENCE_COUNTED); + theme_font->connect_changed(callable_mp(reinterpret_cast<Font *>(const_cast<FontVariation *>(this)), &Font::_invalidate_rids), CONNECT_REFERENCE_COUNTED); } return f; } @@ -2765,7 +2765,7 @@ Ref<Font> FontVariation::_get_base_font_or_default() const { if (f != this) { if (f.is_valid()) { theme_font = f; - theme_font->connect(CoreStringNames::get_singleton()->changed, callable_mp(reinterpret_cast<Font *>(const_cast<FontVariation *>(this)), &Font::_invalidate_rids), CONNECT_REFERENCE_COUNTED); + theme_font->connect_changed(callable_mp(reinterpret_cast<Font *>(const_cast<FontVariation *>(this)), &Font::_invalidate_rids), CONNECT_REFERENCE_COUNTED); } return f; } @@ -2949,7 +2949,7 @@ void SystemFont::_update_rids() const { void SystemFont::_update_base_font() { if (base_font.is_valid()) { - base_font->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(reinterpret_cast<Font *>(this), &Font::_invalidate_rids)); + base_font->disconnect_changed(callable_mp(reinterpret_cast<Font *>(this), &Font::_invalidate_rids)); base_font.unref(); } @@ -3030,7 +3030,7 @@ void SystemFont::_update_base_font() { } if (base_font.is_valid()) { - base_font->connect(CoreStringNames::get_singleton()->changed, callable_mp(reinterpret_cast<Font *>(this), &Font::_invalidate_rids), CONNECT_REFERENCE_COUNTED); + base_font->connect_changed(callable_mp(reinterpret_cast<Font *>(this), &Font::_invalidate_rids), CONNECT_REFERENCE_COUNTED); } _invalidate_rids(); @@ -3039,12 +3039,12 @@ void SystemFont::_update_base_font() { void SystemFont::reset_state() { if (base_font.is_valid()) { - base_font->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(reinterpret_cast<Font *>(this), &Font::_invalidate_rids)); + base_font->disconnect_changed(callable_mp(reinterpret_cast<Font *>(this), &Font::_invalidate_rids)); base_font.unref(); } if (theme_font.is_valid()) { - theme_font->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(reinterpret_cast<Font *>(this), &Font::_invalidate_rids)); + theme_font->disconnect_changed(callable_mp(reinterpret_cast<Font *>(this), &Font::_invalidate_rids)); theme_font.unref(); } @@ -3070,7 +3070,7 @@ void SystemFont::reset_state() { Ref<Font> SystemFont::_get_base_font_or_default() const { if (theme_font.is_valid()) { - theme_font->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(reinterpret_cast<Font *>(const_cast<SystemFont *>(this)), &Font::_invalidate_rids)); + theme_font->disconnect_changed(callable_mp(reinterpret_cast<Font *>(const_cast<SystemFont *>(this)), &Font::_invalidate_rids)); theme_font.unref(); } @@ -3091,7 +3091,7 @@ Ref<Font> SystemFont::_get_base_font_or_default() const { } if (f.is_valid()) { theme_font = f; - theme_font->connect(CoreStringNames::get_singleton()->changed, callable_mp(reinterpret_cast<Font *>(const_cast<SystemFont *>(this)), &Font::_invalidate_rids), CONNECT_REFERENCE_COUNTED); + theme_font->connect_changed(callable_mp(reinterpret_cast<Font *>(const_cast<SystemFont *>(this)), &Font::_invalidate_rids), CONNECT_REFERENCE_COUNTED); } return f; } @@ -3111,7 +3111,7 @@ Ref<Font> SystemFont::_get_base_font_or_default() const { } if (f.is_valid()) { theme_font = f; - theme_font->connect(CoreStringNames::get_singleton()->changed, callable_mp(reinterpret_cast<Font *>(const_cast<SystemFont *>(this)), &Font::_invalidate_rids), CONNECT_REFERENCE_COUNTED); + theme_font->connect_changed(callable_mp(reinterpret_cast<Font *>(const_cast<SystemFont *>(this)), &Font::_invalidate_rids), CONNECT_REFERENCE_COUNTED); } return f; } @@ -3122,7 +3122,7 @@ Ref<Font> SystemFont::_get_base_font_or_default() const { if (f != this) { if (f.is_valid()) { theme_font = f; - theme_font->connect(CoreStringNames::get_singleton()->changed, callable_mp(reinterpret_cast<Font *>(const_cast<SystemFont *>(this)), &Font::_invalidate_rids), CONNECT_REFERENCE_COUNTED); + theme_font->connect_changed(callable_mp(reinterpret_cast<Font *>(const_cast<SystemFont *>(this)), &Font::_invalidate_rids), CONNECT_REFERENCE_COUNTED); } return f; } diff --git a/scene/resources/gradient_texture.cpp b/scene/resources/gradient_texture.cpp new file mode 100644 index 0000000000..20868faaa2 --- /dev/null +++ b/scene/resources/gradient_texture.cpp @@ -0,0 +1,438 @@ +/**************************************************************************/ +/* gradient_texture.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "gradient_texture.h" + +#include "core/core_string_names.h" +#include "core/math/geometry_2d.h" + +GradientTexture1D::GradientTexture1D() { + _queue_update(); +} + +GradientTexture1D::~GradientTexture1D() { + if (texture.is_valid()) { + ERR_FAIL_NULL(RenderingServer::get_singleton()); + RS::get_singleton()->free(texture); + } +} + +void GradientTexture1D::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_gradient", "gradient"), &GradientTexture1D::set_gradient); + ClassDB::bind_method(D_METHOD("get_gradient"), &GradientTexture1D::get_gradient); + + ClassDB::bind_method(D_METHOD("set_width", "width"), &GradientTexture1D::set_width); + // The `get_width()` method is already exposed by the parent class Texture2D. + + ClassDB::bind_method(D_METHOD("set_use_hdr", "enabled"), &GradientTexture1D::set_use_hdr); + ClassDB::bind_method(D_METHOD("is_using_hdr"), &GradientTexture1D::is_using_hdr); + + ClassDB::bind_method(D_METHOD("_update"), &GradientTexture1D::_update); + + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "gradient", PROPERTY_HINT_RESOURCE_TYPE, "Gradient", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_EDITOR_INSTANTIATE_OBJECT), "set_gradient", "get_gradient"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "width", PROPERTY_HINT_RANGE, "1,16384,suffix:px"), "set_width", "get_width"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_hdr"), "set_use_hdr", "is_using_hdr"); +} + +void GradientTexture1D::set_gradient(Ref<Gradient> p_gradient) { + if (p_gradient == gradient) { + return; + } + if (gradient.is_valid()) { + gradient->disconnect_changed(callable_mp(this, &GradientTexture1D::_update)); + } + gradient = p_gradient; + if (gradient.is_valid()) { + gradient->connect_changed(callable_mp(this, &GradientTexture1D::_update)); + } + _update(); + emit_changed(); +} + +Ref<Gradient> GradientTexture1D::get_gradient() const { + return gradient; +} + +void GradientTexture1D::_queue_update() { + if (update_pending) { + return; + } + + update_pending = true; + call_deferred(SNAME("_update")); +} + +void GradientTexture1D::_update() { + update_pending = false; + + if (gradient.is_null()) { + return; + } + + if (use_hdr) { + // High dynamic range. + Ref<Image> image = memnew(Image(width, 1, false, Image::FORMAT_RGBAF)); + Gradient &g = **gradient; + // `create()` isn't available for non-uint8_t data, so fill in the data manually. + for (int i = 0; i < width; i++) { + float ofs = float(i) / (width - 1); + image->set_pixel(i, 0, g.get_color_at_offset(ofs)); + } + + if (texture.is_valid()) { + RID new_texture = RS::get_singleton()->texture_2d_create(image); + RS::get_singleton()->texture_replace(texture, new_texture); + } else { + texture = RS::get_singleton()->texture_2d_create(image); + } + } else { + // Low dynamic range. "Overbright" colors will be clamped. + Vector<uint8_t> data; + data.resize(width * 4); + { + uint8_t *wd8 = data.ptrw(); + Gradient &g = **gradient; + + for (int i = 0; i < width; i++) { + float ofs = float(i) / (width - 1); + Color color = g.get_color_at_offset(ofs); + + wd8[i * 4 + 0] = uint8_t(CLAMP(color.r * 255.0, 0, 255)); + wd8[i * 4 + 1] = uint8_t(CLAMP(color.g * 255.0, 0, 255)); + wd8[i * 4 + 2] = uint8_t(CLAMP(color.b * 255.0, 0, 255)); + wd8[i * 4 + 3] = uint8_t(CLAMP(color.a * 255.0, 0, 255)); + } + } + + Ref<Image> image = memnew(Image(width, 1, false, Image::FORMAT_RGBA8, data)); + + if (texture.is_valid()) { + RID new_texture = RS::get_singleton()->texture_2d_create(image); + RS::get_singleton()->texture_replace(texture, new_texture); + } else { + texture = RS::get_singleton()->texture_2d_create(image); + } + } + + emit_changed(); +} + +void GradientTexture1D::set_width(int p_width) { + ERR_FAIL_COND_MSG(p_width <= 0 || p_width > 16384, "Texture dimensions have to be within 1 to 16384 range."); + width = p_width; + _queue_update(); +} + +int GradientTexture1D::get_width() const { + return width; +} + +void GradientTexture1D::set_use_hdr(bool p_enabled) { + if (p_enabled == use_hdr) { + return; + } + + use_hdr = p_enabled; + _queue_update(); +} + +bool GradientTexture1D::is_using_hdr() const { + return use_hdr; +} + +Ref<Image> GradientTexture1D::get_image() const { + if (!texture.is_valid()) { + return Ref<Image>(); + } + return RenderingServer::get_singleton()->texture_2d_get(texture); +} + +////////////////// + +GradientTexture2D::GradientTexture2D() { + _queue_update(); +} + +GradientTexture2D::~GradientTexture2D() { + if (texture.is_valid()) { + ERR_FAIL_NULL(RenderingServer::get_singleton()); + RS::get_singleton()->free(texture); + } +} + +void GradientTexture2D::set_gradient(Ref<Gradient> p_gradient) { + if (gradient == p_gradient) { + return; + } + if (gradient.is_valid()) { + gradient->disconnect_changed(callable_mp(this, &GradientTexture2D::_queue_update)); + } + gradient = p_gradient; + if (gradient.is_valid()) { + gradient->connect_changed(callable_mp(this, &GradientTexture2D::_queue_update)); + } + _update(); + emit_changed(); +} + +Ref<Gradient> GradientTexture2D::get_gradient() const { + return gradient; +} + +void GradientTexture2D::_queue_update() { + if (update_pending) { + return; + } + update_pending = true; + call_deferred(SNAME("_update")); +} + +void GradientTexture2D::_update() { + update_pending = false; + + if (gradient.is_null()) { + return; + } + Ref<Image> image; + image.instantiate(); + + if (gradient->get_point_count() <= 1) { // No need to interpolate. + image->initialize_data(width, height, false, (use_hdr) ? Image::FORMAT_RGBAF : Image::FORMAT_RGBA8); + image->fill((gradient->get_point_count() == 1) ? gradient->get_color(0) : Color(0, 0, 0, 1)); + } else { + if (use_hdr) { + image->initialize_data(width, height, false, Image::FORMAT_RGBAF); + Gradient &g = **gradient; + // `create()` isn't available for non-uint8_t data, so fill in the data manually. + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + float ofs = _get_gradient_offset_at(x, y); + image->set_pixel(x, y, g.get_color_at_offset(ofs)); + } + } + } else { + Vector<uint8_t> data; + data.resize(width * height * 4); + { + uint8_t *wd8 = data.ptrw(); + Gradient &g = **gradient; + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + float ofs = _get_gradient_offset_at(x, y); + const Color &c = g.get_color_at_offset(ofs); + + wd8[(x + (y * width)) * 4 + 0] = uint8_t(CLAMP(c.r * 255.0, 0, 255)); + wd8[(x + (y * width)) * 4 + 1] = uint8_t(CLAMP(c.g * 255.0, 0, 255)); + wd8[(x + (y * width)) * 4 + 2] = uint8_t(CLAMP(c.b * 255.0, 0, 255)); + wd8[(x + (y * width)) * 4 + 3] = uint8_t(CLAMP(c.a * 255.0, 0, 255)); + } + } + } + image->set_data(width, height, false, Image::FORMAT_RGBA8, data); + } + } + + if (texture.is_valid()) { + RID new_texture = RS::get_singleton()->texture_2d_create(image); + RS::get_singleton()->texture_replace(texture, new_texture); + } else { + texture = RS::get_singleton()->texture_2d_create(image); + } + emit_changed(); +} + +float GradientTexture2D::_get_gradient_offset_at(int x, int y) const { + if (fill_to == fill_from) { + return 0; + } + float ofs = 0; + Vector2 pos; + if (width > 1) { + pos.x = static_cast<float>(x) / (width - 1); + } + if (height > 1) { + pos.y = static_cast<float>(y) / (height - 1); + } + if (fill == Fill::FILL_LINEAR) { + Vector2 segment[2]; + segment[0] = fill_from; + segment[1] = fill_to; + Vector2 closest = Geometry2D::get_closest_point_to_segment_uncapped(pos, &segment[0]); + ofs = (closest - fill_from).length() / (fill_to - fill_from).length(); + if ((closest - fill_from).dot(fill_to - fill_from) < 0) { + ofs *= -1; + } + } else if (fill == Fill::FILL_RADIAL) { + ofs = (pos - fill_from).length() / (fill_to - fill_from).length(); + } else if (fill == Fill::FILL_SQUARE) { + ofs = MAX(Math::abs(pos.x - fill_from.x), Math::abs(pos.y - fill_from.y)) / MAX(Math::abs(fill_to.x - fill_from.x), Math::abs(fill_to.y - fill_from.y)); + } + if (repeat == Repeat::REPEAT_NONE) { + ofs = CLAMP(ofs, 0.0, 1.0); + } else if (repeat == Repeat::REPEAT) { + ofs = Math::fmod(ofs, 1.0f); + if (ofs < 0) { + ofs = 1 + ofs; + } + } else if (repeat == Repeat::REPEAT_MIRROR) { + ofs = Math::abs(ofs); + ofs = Math::fmod(ofs, 2.0f); + if (ofs > 1.0) { + ofs = 2.0 - ofs; + } + } + return ofs; +} + +void GradientTexture2D::set_width(int p_width) { + ERR_FAIL_COND_MSG(p_width <= 0 || p_width > 16384, "Texture dimensions have to be within 1 to 16384 range."); + width = p_width; + _queue_update(); +} + +int GradientTexture2D::get_width() const { + return width; +} + +void GradientTexture2D::set_height(int p_height) { + ERR_FAIL_COND_MSG(p_height <= 0 || p_height > 16384, "Texture dimensions have to be within 1 to 16384 range."); + height = p_height; + _queue_update(); +} +int GradientTexture2D::get_height() const { + return height; +} + +void GradientTexture2D::set_use_hdr(bool p_enabled) { + if (p_enabled == use_hdr) { + return; + } + + use_hdr = p_enabled; + _queue_update(); +} + +bool GradientTexture2D::is_using_hdr() const { + return use_hdr; +} + +void GradientTexture2D::set_fill_from(Vector2 p_fill_from) { + fill_from = p_fill_from; + _queue_update(); +} + +Vector2 GradientTexture2D::get_fill_from() const { + return fill_from; +} + +void GradientTexture2D::set_fill_to(Vector2 p_fill_to) { + fill_to = p_fill_to; + _queue_update(); +} + +Vector2 GradientTexture2D::get_fill_to() const { + return fill_to; +} + +void GradientTexture2D::set_fill(Fill p_fill) { + fill = p_fill; + _queue_update(); +} + +GradientTexture2D::Fill GradientTexture2D::get_fill() const { + return fill; +} + +void GradientTexture2D::set_repeat(Repeat p_repeat) { + repeat = p_repeat; + _queue_update(); +} + +GradientTexture2D::Repeat GradientTexture2D::get_repeat() const { + return repeat; +} + +RID GradientTexture2D::get_rid() const { + if (!texture.is_valid()) { + texture = RS::get_singleton()->texture_2d_placeholder_create(); + } + return texture; +} + +Ref<Image> GradientTexture2D::get_image() const { + if (!texture.is_valid()) { + return Ref<Image>(); + } + return RenderingServer::get_singleton()->texture_2d_get(texture); +} + +void GradientTexture2D::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_gradient", "gradient"), &GradientTexture2D::set_gradient); + ClassDB::bind_method(D_METHOD("get_gradient"), &GradientTexture2D::get_gradient); + + ClassDB::bind_method(D_METHOD("set_width", "width"), &GradientTexture2D::set_width); + ClassDB::bind_method(D_METHOD("set_height", "height"), &GradientTexture2D::set_height); + + ClassDB::bind_method(D_METHOD("set_use_hdr", "enabled"), &GradientTexture2D::set_use_hdr); + ClassDB::bind_method(D_METHOD("is_using_hdr"), &GradientTexture2D::is_using_hdr); + + ClassDB::bind_method(D_METHOD("set_fill", "fill"), &GradientTexture2D::set_fill); + ClassDB::bind_method(D_METHOD("get_fill"), &GradientTexture2D::get_fill); + ClassDB::bind_method(D_METHOD("set_fill_from", "fill_from"), &GradientTexture2D::set_fill_from); + ClassDB::bind_method(D_METHOD("get_fill_from"), &GradientTexture2D::get_fill_from); + ClassDB::bind_method(D_METHOD("set_fill_to", "fill_to"), &GradientTexture2D::set_fill_to); + ClassDB::bind_method(D_METHOD("get_fill_to"), &GradientTexture2D::get_fill_to); + + ClassDB::bind_method(D_METHOD("set_repeat", "repeat"), &GradientTexture2D::set_repeat); + ClassDB::bind_method(D_METHOD("get_repeat"), &GradientTexture2D::get_repeat); + + ClassDB::bind_method(D_METHOD("_update"), &GradientTexture2D::_update); + + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "gradient", PROPERTY_HINT_RESOURCE_TYPE, "Gradient", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_EDITOR_INSTANTIATE_OBJECT), "set_gradient", "get_gradient"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "width", PROPERTY_HINT_RANGE, "1,2048,or_greater,suffix:px"), "set_width", "get_width"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "height", PROPERTY_HINT_RANGE, "1,2048,or_greater,suffix:px"), "set_height", "get_height"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_hdr"), "set_use_hdr", "is_using_hdr"); + + ADD_GROUP("Fill", "fill_"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "fill", PROPERTY_HINT_ENUM, "Linear,Radial,Square"), "set_fill", "get_fill"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "fill_from"), "set_fill_from", "get_fill_from"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "fill_to"), "set_fill_to", "get_fill_to"); + + ADD_GROUP("Repeat", "repeat_"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "repeat", PROPERTY_HINT_ENUM, "No Repeat,Repeat,Mirror Repeat"), "set_repeat", "get_repeat"); + + BIND_ENUM_CONSTANT(FILL_LINEAR); + BIND_ENUM_CONSTANT(FILL_RADIAL); + BIND_ENUM_CONSTANT(FILL_SQUARE); + + BIND_ENUM_CONSTANT(REPEAT_NONE); + BIND_ENUM_CONSTANT(REPEAT); + BIND_ENUM_CONSTANT(REPEAT_MIRROR); +} diff --git a/scene/resources/gradient_texture.h b/scene/resources/gradient_texture.h new file mode 100644 index 0000000000..b8768ce0a8 --- /dev/null +++ b/scene/resources/gradient_texture.h @@ -0,0 +1,144 @@ +/**************************************************************************/ +/* gradient_texture.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef GRADIENT_TEXTURE_H +#define GRADIENT_TEXTURE_H + +#include "scene/resources/texture.h" + +class GradientTexture1D : public Texture2D { + GDCLASS(GradientTexture1D, Texture2D); + +private: + Ref<Gradient> gradient; + bool update_pending = false; + RID texture; + int width = 256; + bool use_hdr = false; + + void _queue_update(); + void _update(); + +protected: + static void _bind_methods(); + +public: + void set_gradient(Ref<Gradient> p_gradient); + Ref<Gradient> get_gradient() const; + + void set_width(int p_width); + int get_width() const override; + + void set_use_hdr(bool p_enabled); + bool is_using_hdr() const; + + virtual RID get_rid() const override { return texture; } + virtual int get_height() const override { return 1; } + virtual bool has_alpha() const override { return true; } + + virtual Ref<Image> get_image() const override; + + GradientTexture1D(); + virtual ~GradientTexture1D(); +}; + +class GradientTexture2D : public Texture2D { + GDCLASS(GradientTexture2D, Texture2D); + +public: + enum Fill { + FILL_LINEAR, + FILL_RADIAL, + FILL_SQUARE, + }; + enum Repeat { + REPEAT_NONE, + REPEAT, + REPEAT_MIRROR, + }; + +private: + Ref<Gradient> gradient; + mutable RID texture; + + int width = 64; + int height = 64; + + bool use_hdr = false; + + Vector2 fill_from; + Vector2 fill_to = Vector2(1, 0); + + Fill fill = FILL_LINEAR; + Repeat repeat = REPEAT_NONE; + + float _get_gradient_offset_at(int x, int y) const; + + bool update_pending = false; + void _queue_update(); + void _update(); + +protected: + static void _bind_methods(); + +public: + void set_gradient(Ref<Gradient> p_gradient); + Ref<Gradient> get_gradient() const; + + void set_width(int p_width); + virtual int get_width() const override; + void set_height(int p_height); + virtual int get_height() const override; + + void set_use_hdr(bool p_enabled); + bool is_using_hdr() const; + + void set_fill(Fill p_fill); + Fill get_fill() const; + void set_fill_from(Vector2 p_fill_from); + Vector2 get_fill_from() const; + void set_fill_to(Vector2 p_fill_to); + Vector2 get_fill_to() const; + + void set_repeat(Repeat p_repeat); + Repeat get_repeat() const; + + virtual RID get_rid() const override; + virtual bool has_alpha() const override { return true; } + virtual Ref<Image> get_image() const override; + + GradientTexture2D(); + virtual ~GradientTexture2D(); +}; + +VARIANT_ENUM_CAST(GradientTexture2D::Fill); +VARIANT_ENUM_CAST(GradientTexture2D::Repeat); + +#endif // GRADIENT_TEXTURE_H diff --git a/scene/resources/height_map_shape_3d.cpp b/scene/resources/height_map_shape_3d.cpp index 553daa93e6..718d701811 100644 --- a/scene/resources/height_map_shape_3d.cpp +++ b/scene/resources/height_map_shape_3d.cpp @@ -112,7 +112,7 @@ void HeightMapShape3D::set_map_width(int p_new) { } _update_shape(); - notify_change_to_owners(); + emit_changed(); } } @@ -136,7 +136,7 @@ void HeightMapShape3D::set_map_depth(int p_new) { } _update_shape(); - notify_change_to_owners(); + emit_changed(); } } @@ -172,7 +172,7 @@ void HeightMapShape3D::set_map_data(Vector<real_t> p_new) { } _update_shape(); - notify_change_to_owners(); + emit_changed(); } Vector<real_t> HeightMapShape3D::get_map_data() const { diff --git a/scene/resources/image_texture.cpp b/scene/resources/image_texture.cpp new file mode 100644 index 0000000000..ecf70d96ac --- /dev/null +++ b/scene/resources/image_texture.cpp @@ -0,0 +1,514 @@ +/**************************************************************************/ +/* image_texture.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "image_texture.h" + +#include "core/io/image_loader.h" +#include "scene/resources/bit_map.h" +#include "scene/resources/placeholder_textures.h" + +void ImageTexture::reload_from_file() { + String path = ResourceLoader::path_remap(get_path()); + if (!path.is_resource_file()) { + return; + } + + Ref<Image> img; + img.instantiate(); + + if (ImageLoader::load_image(path, img) == OK) { + set_image(img); + } else { + Resource::reload_from_file(); + notify_property_list_changed(); + emit_changed(); + } +} + +bool ImageTexture::_set(const StringName &p_name, const Variant &p_value) { + if (p_name == "image") { + set_image(p_value); + return true; + } + return false; +} + +bool ImageTexture::_get(const StringName &p_name, Variant &r_ret) const { + if (p_name == "image") { + r_ret = get_image(); + return true; + } + return false; +} + +void ImageTexture::_get_property_list(List<PropertyInfo> *p_list) const { + p_list->push_back(PropertyInfo(Variant::OBJECT, PNAME("image"), PROPERTY_HINT_RESOURCE_TYPE, "Image", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_RESOURCE_NOT_PERSISTENT)); +} + +Ref<ImageTexture> ImageTexture::create_from_image(const Ref<Image> &p_image) { + ERR_FAIL_COND_V_MSG(p_image.is_null(), Ref<ImageTexture>(), "Invalid image: null"); + ERR_FAIL_COND_V_MSG(p_image->is_empty(), Ref<ImageTexture>(), "Invalid image: image is empty"); + + Ref<ImageTexture> image_texture; + image_texture.instantiate(); + image_texture->set_image(p_image); + return image_texture; +} + +void ImageTexture::set_image(const Ref<Image> &p_image) { + ERR_FAIL_COND_MSG(p_image.is_null() || p_image->is_empty(), "Invalid image"); + w = p_image->get_width(); + h = p_image->get_height(); + format = p_image->get_format(); + mipmaps = p_image->has_mipmaps(); + + if (texture.is_null()) { + texture = RenderingServer::get_singleton()->texture_2d_create(p_image); + } else { + RID new_texture = RenderingServer::get_singleton()->texture_2d_create(p_image); + RenderingServer::get_singleton()->texture_replace(texture, new_texture); + } + notify_property_list_changed(); + emit_changed(); + + image_stored = true; +} + +Image::Format ImageTexture::get_format() const { + return format; +} + +void ImageTexture::update(const Ref<Image> &p_image) { + ERR_FAIL_COND_MSG(p_image.is_null(), "Invalid image"); + ERR_FAIL_COND_MSG(texture.is_null(), "Texture is not initialized."); + ERR_FAIL_COND_MSG(p_image->get_width() != w || p_image->get_height() != h, + "The new image dimensions must match the texture size."); + ERR_FAIL_COND_MSG(p_image->get_format() != format, + "The new image format must match the texture's image format."); + ERR_FAIL_COND_MSG(mipmaps != p_image->has_mipmaps(), + "The new image mipmaps configuration must match the texture's image mipmaps configuration"); + + RS::get_singleton()->texture_2d_update(texture, p_image); + + notify_property_list_changed(); + emit_changed(); + + alpha_cache.unref(); + image_stored = true; +} + +Ref<Image> ImageTexture::get_image() const { + if (image_stored) { + return RenderingServer::get_singleton()->texture_2d_get(texture); + } else { + return Ref<Image>(); + } +} + +int ImageTexture::get_width() const { + return w; +} + +int ImageTexture::get_height() const { + return h; +} + +RID ImageTexture::get_rid() const { + if (texture.is_null()) { + // We are in trouble, create something temporary. + texture = RenderingServer::get_singleton()->texture_2d_placeholder_create(); + } + return texture; +} + +bool ImageTexture::has_alpha() const { + return (format == Image::FORMAT_LA8 || format == Image::FORMAT_RGBA8); +} + +void ImageTexture::draw(RID p_canvas_item, const Point2 &p_pos, const Color &p_modulate, bool p_transpose) const { + if ((w | h) == 0) { + return; + } + RenderingServer::get_singleton()->canvas_item_add_texture_rect(p_canvas_item, Rect2(p_pos, Size2(w, h)), texture, false, p_modulate, p_transpose); +} + +void ImageTexture::draw_rect(RID p_canvas_item, const Rect2 &p_rect, bool p_tile, const Color &p_modulate, bool p_transpose) const { + if ((w | h) == 0) { + return; + } + RenderingServer::get_singleton()->canvas_item_add_texture_rect(p_canvas_item, p_rect, texture, p_tile, p_modulate, p_transpose); +} + +void ImageTexture::draw_rect_region(RID p_canvas_item, const Rect2 &p_rect, const Rect2 &p_src_rect, const Color &p_modulate, bool p_transpose, bool p_clip_uv) const { + if ((w | h) == 0) { + return; + } + RenderingServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas_item, p_rect, texture, p_src_rect, p_modulate, p_transpose, p_clip_uv); +} + +bool ImageTexture::is_pixel_opaque(int p_x, int p_y) const { + if (!alpha_cache.is_valid()) { + Ref<Image> img = get_image(); + if (img.is_valid()) { + if (img->is_compressed()) { //must decompress, if compressed + Ref<Image> decom = img->duplicate(); + decom->decompress(); + img = decom; + } + alpha_cache.instantiate(); + alpha_cache->create_from_image_alpha(img); + } + } + + if (alpha_cache.is_valid()) { + int aw = int(alpha_cache->get_size().width); + int ah = int(alpha_cache->get_size().height); + if (aw == 0 || ah == 0) { + return true; + } + + int x = p_x * aw / w; + int y = p_y * ah / h; + + x = CLAMP(x, 0, aw); + y = CLAMP(y, 0, ah); + + return alpha_cache->get_bit(x, y); + } + + return true; +} + +void ImageTexture::set_size_override(const Size2i &p_size) { + Size2i s = p_size; + if (s.x != 0) { + w = s.x; + } + if (s.y != 0) { + h = s.y; + } + RenderingServer::get_singleton()->texture_set_size_override(texture, w, h); +} + +void ImageTexture::set_path(const String &p_path, bool p_take_over) { + if (texture.is_valid()) { + RenderingServer::get_singleton()->texture_set_path(texture, p_path); + } + + Resource::set_path(p_path, p_take_over); +} + +void ImageTexture::_bind_methods() { + ClassDB::bind_static_method("ImageTexture", D_METHOD("create_from_image", "image"), &ImageTexture::create_from_image); + ClassDB::bind_method(D_METHOD("get_format"), &ImageTexture::get_format); + + ClassDB::bind_method(D_METHOD("set_image", "image"), &ImageTexture::set_image); + ClassDB::bind_method(D_METHOD("update", "image"), &ImageTexture::update); + ClassDB::bind_method(D_METHOD("set_size_override", "size"), &ImageTexture::set_size_override); +} + +ImageTexture::ImageTexture() {} + +ImageTexture::~ImageTexture() { + if (texture.is_valid()) { + ERR_FAIL_NULL(RenderingServer::get_singleton()); + RenderingServer::get_singleton()->free(texture); + } +} + +Image::Format ImageTextureLayered::get_format() const { + return format; +} + +int ImageTextureLayered::get_width() const { + return width; +} + +int ImageTextureLayered::get_height() const { + return height; +} + +int ImageTextureLayered::get_layers() const { + return layers; +} + +bool ImageTextureLayered::has_mipmaps() const { + return mipmaps; +} + +ImageTextureLayered::LayeredType ImageTextureLayered::get_layered_type() const { + return layered_type; +} + +Error ImageTextureLayered::_create_from_images(const TypedArray<Image> &p_images) { + Vector<Ref<Image>> images; + for (int i = 0; i < p_images.size(); i++) { + Ref<Image> img = p_images[i]; + ERR_FAIL_COND_V(img.is_null(), ERR_INVALID_PARAMETER); + images.push_back(img); + } + + return create_from_images(images); +} + +TypedArray<Image> ImageTextureLayered::_get_images() const { + TypedArray<Image> images; + for (int i = 0; i < layers; i++) { + images.push_back(get_layer_data(i)); + } + return images; +} + +void ImageTextureLayered::_set_images(const TypedArray<Image> &p_images) { + ERR_FAIL_COND(_create_from_images(p_images) != OK); +} + +Error ImageTextureLayered::create_from_images(Vector<Ref<Image>> p_images) { + int new_layers = p_images.size(); + ERR_FAIL_COND_V(new_layers == 0, ERR_INVALID_PARAMETER); + if (layered_type == LAYERED_TYPE_CUBEMAP) { + ERR_FAIL_COND_V_MSG(new_layers != 6, ERR_INVALID_PARAMETER, + "Cubemaps require exactly 6 layers"); + } else if (layered_type == LAYERED_TYPE_CUBEMAP_ARRAY) { + ERR_FAIL_COND_V_MSG((new_layers % 6) != 0, ERR_INVALID_PARAMETER, + "Cubemap array layers must be a multiple of 6"); + } + + ERR_FAIL_COND_V(p_images[0].is_null() || p_images[0]->is_empty(), ERR_INVALID_PARAMETER); + + Image::Format new_format = p_images[0]->get_format(); + int new_width = p_images[0]->get_width(); + int new_height = p_images[0]->get_height(); + bool new_mipmaps = p_images[0]->has_mipmaps(); + + for (int i = 1; i < p_images.size(); i++) { + ERR_FAIL_COND_V_MSG(p_images[i]->get_format() != new_format, ERR_INVALID_PARAMETER, + "All images must share the same format"); + ERR_FAIL_COND_V_MSG(p_images[i]->get_width() != new_width || p_images[i]->get_height() != new_height, ERR_INVALID_PARAMETER, + "All images must share the same dimensions"); + ERR_FAIL_COND_V_MSG(p_images[i]->has_mipmaps() != new_mipmaps, ERR_INVALID_PARAMETER, + "All images must share the usage of mipmaps"); + } + + if (texture.is_valid()) { + RID new_texture = RS::get_singleton()->texture_2d_layered_create(p_images, RS::TextureLayeredType(layered_type)); + ERR_FAIL_COND_V(!new_texture.is_valid(), ERR_CANT_CREATE); + RS::get_singleton()->texture_replace(texture, new_texture); + } else { + texture = RS::get_singleton()->texture_2d_layered_create(p_images, RS::TextureLayeredType(layered_type)); + ERR_FAIL_COND_V(!texture.is_valid(), ERR_CANT_CREATE); + } + + format = new_format; + width = new_width; + height = new_height; + layers = new_layers; + mipmaps = new_mipmaps; + return OK; +} + +void ImageTextureLayered::update_layer(const Ref<Image> &p_image, int p_layer) { + ERR_FAIL_COND_MSG(texture.is_null(), "Texture is not initialized."); + ERR_FAIL_COND_MSG(p_image.is_null(), "Invalid image."); + ERR_FAIL_COND_MSG(p_image->get_format() != format, "Image format must match texture's image format."); + ERR_FAIL_COND_MSG(p_image->get_width() != width || p_image->get_height() != height, "Image size must match texture's image size."); + ERR_FAIL_COND_MSG(p_image->has_mipmaps() != mipmaps, "Image mipmap configuration must match texture's image mipmap configuration."); + ERR_FAIL_INDEX_MSG(p_layer, layers, "Layer index is out of bounds."); + RS::get_singleton()->texture_2d_update(texture, p_image, p_layer); +} + +Ref<Image> ImageTextureLayered::get_layer_data(int p_layer) const { + ERR_FAIL_INDEX_V(p_layer, layers, Ref<Image>()); + return RS::get_singleton()->texture_2d_layer_get(texture, p_layer); +} + +RID ImageTextureLayered::get_rid() const { + if (texture.is_null()) { + texture = RS::get_singleton()->texture_2d_layered_placeholder_create(RS::TextureLayeredType(layered_type)); + } + return texture; +} + +void ImageTextureLayered::set_path(const String &p_path, bool p_take_over) { + if (texture.is_valid()) { + RS::get_singleton()->texture_set_path(texture, p_path); + } + + Resource::set_path(p_path, p_take_over); +} + +void ImageTextureLayered::_bind_methods() { + ClassDB::bind_method(D_METHOD("create_from_images", "images"), &ImageTextureLayered::_create_from_images); + ClassDB::bind_method(D_METHOD("update_layer", "image", "layer"), &ImageTextureLayered::update_layer); + + ClassDB::bind_method(D_METHOD("_get_images"), &ImageTextureLayered::_get_images); + ClassDB::bind_method(D_METHOD("_set_images", "images"), &ImageTextureLayered::_set_images); + + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "_images", PROPERTY_HINT_ARRAY_TYPE, "Image", PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_RESOURCE_NOT_PERSISTENT), "_set_images", "_get_images"); +} + +ImageTextureLayered::ImageTextureLayered(LayeredType p_layered_type) { + layered_type = p_layered_type; +} + +ImageTextureLayered::~ImageTextureLayered() { + if (texture.is_valid()) { + ERR_FAIL_NULL(RenderingServer::get_singleton()); + RS::get_singleton()->free(texture); + } +} + +Image::Format ImageTexture3D::get_format() const { + return format; +} +int ImageTexture3D::get_width() const { + return width; +} +int ImageTexture3D::get_height() const { + return height; +} +int ImageTexture3D::get_depth() const { + return depth; +} +bool ImageTexture3D::has_mipmaps() const { + return mipmaps; +} + +Error ImageTexture3D::_create(Image::Format p_format, int p_width, int p_height, int p_depth, bool p_mipmaps, const TypedArray<Image> &p_data) { + Vector<Ref<Image>> images; + images.resize(p_data.size()); + for (int i = 0; i < images.size(); i++) { + images.write[i] = p_data[i]; + } + return create(p_format, p_width, p_height, p_depth, p_mipmaps, images); +} + +void ImageTexture3D::_update(const TypedArray<Image> &p_data) { + Vector<Ref<Image>> images; + images.resize(p_data.size()); + for (int i = 0; i < images.size(); i++) { + images.write[i] = p_data[i]; + } + return update(images); +} + +Error ImageTexture3D::create(Image::Format p_format, int p_width, int p_height, int p_depth, bool p_mipmaps, const Vector<Ref<Image>> &p_data) { + RID tex = RenderingServer::get_singleton()->texture_3d_create(p_format, p_width, p_height, p_depth, p_mipmaps, p_data); + ERR_FAIL_COND_V(tex.is_null(), ERR_CANT_CREATE); + + if (texture.is_valid()) { + RenderingServer::get_singleton()->texture_replace(texture, tex); + } else { + texture = tex; + } + + format = p_format; + width = p_width; + height = p_height; + depth = p_depth; + mipmaps = p_mipmaps; + + return OK; +} + +void ImageTexture3D::update(const Vector<Ref<Image>> &p_data) { + ERR_FAIL_COND(!texture.is_valid()); + RenderingServer::get_singleton()->texture_3d_update(texture, p_data); +} + +Vector<Ref<Image>> ImageTexture3D::get_data() const { + ERR_FAIL_COND_V(!texture.is_valid(), Vector<Ref<Image>>()); + return RS::get_singleton()->texture_3d_get(texture); +} + +RID ImageTexture3D::get_rid() const { + if (!texture.is_valid()) { + texture = RS::get_singleton()->texture_3d_placeholder_create(); + } + return texture; +} +void ImageTexture3D::set_path(const String &p_path, bool p_take_over) { + if (texture.is_valid()) { + RenderingServer::get_singleton()->texture_set_path(texture, p_path); + } + + Resource::set_path(p_path, p_take_over); +} + +void ImageTexture3D::_bind_methods() { + ClassDB::bind_method(D_METHOD("create", "format", "width", "height", "depth", "use_mipmaps", "data"), &ImageTexture3D::_create); + ClassDB::bind_method(D_METHOD("update", "data"), &ImageTexture3D::_update); +} + +ImageTexture3D::ImageTexture3D() { +} + +ImageTexture3D::~ImageTexture3D() { + if (texture.is_valid()) { + ERR_FAIL_NULL(RenderingServer::get_singleton()); + RS::get_singleton()->free(texture); + } +} + +void Texture2DArray::_bind_methods() { + ClassDB::bind_method(D_METHOD("create_placeholder"), &Texture2DArray::create_placeholder); +} + +Ref<Resource> Texture2DArray::create_placeholder() const { + Ref<PlaceholderTexture2DArray> placeholder; + placeholder.instantiate(); + placeholder->set_size(Size2i(get_width(), get_height())); + placeholder->set_layers(get_layers()); + return placeholder; +} + +void Cubemap::_bind_methods() { + ClassDB::bind_method(D_METHOD("create_placeholder"), &Cubemap::create_placeholder); +} + +Ref<Resource> Cubemap::create_placeholder() const { + Ref<PlaceholderCubemap> placeholder; + placeholder.instantiate(); + placeholder->set_size(Size2i(get_width(), get_height())); + placeholder->set_layers(get_layers()); + return placeholder; +} + +void CubemapArray::_bind_methods() { + ClassDB::bind_method(D_METHOD("create_placeholder"), &CubemapArray::create_placeholder); +} + +Ref<Resource> CubemapArray::create_placeholder() const { + Ref<PlaceholderCubemapArray> placeholder; + placeholder.instantiate(); + placeholder->set_size(Size2i(get_width(), get_height())); + placeholder->set_layers(get_layers()); + return placeholder; +} diff --git a/scene/resources/image_texture.h b/scene/resources/image_texture.h new file mode 100644 index 0000000000..9d9c296a45 --- /dev/null +++ b/scene/resources/image_texture.h @@ -0,0 +1,203 @@ +/**************************************************************************/ +/* image_texture.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef IMAGE_TEXTURE_H +#define IMAGE_TEXTURE_H + +#include "scene/resources/texture.h" + +class BitMap; + +class ImageTexture : public Texture2D { + GDCLASS(ImageTexture, Texture2D); + RES_BASE_EXTENSION("tex"); + + mutable RID texture; + Image::Format format = Image::FORMAT_L8; + bool mipmaps = false; + int w = 0; + int h = 0; + Size2 size_override; + mutable Ref<BitMap> alpha_cache; + bool image_stored = false; + +protected: + virtual void reload_from_file() override; + + 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; + + static void _bind_methods(); + +public: + void set_image(const Ref<Image> &p_image); + static Ref<ImageTexture> create_from_image(const Ref<Image> &p_image); + + Image::Format get_format() const; + + void update(const Ref<Image> &p_image); + Ref<Image> get_image() const override; + + int get_width() const override; + int get_height() const override; + + virtual RID get_rid() const override; + + bool has_alpha() const override; + virtual void draw(RID p_canvas_item, const Point2 &p_pos, const Color &p_modulate = Color(1, 1, 1), bool p_transpose = false) const override; + virtual void draw_rect(RID p_canvas_item, const Rect2 &p_rect, bool p_tile = false, const Color &p_modulate = Color(1, 1, 1), bool p_transpose = false) const override; + virtual void draw_rect_region(RID p_canvas_item, const Rect2 &p_rect, const Rect2 &p_src_rect, const Color &p_modulate = Color(1, 1, 1), bool p_transpose = false, bool p_clip_uv = true) const override; + + bool is_pixel_opaque(int p_x, int p_y) const override; + + void set_size_override(const Size2i &p_size); + + virtual void set_path(const String &p_path, bool p_take_over = false) override; + + ImageTexture(); + ~ImageTexture(); +}; + +class ImageTextureLayered : public TextureLayered { + GDCLASS(ImageTextureLayered, TextureLayered); + + LayeredType layered_type; + + mutable RID texture; + Image::Format format = Image::FORMAT_L8; + + int width = 0; + int height = 0; + int layers = 0; + bool mipmaps = false; + + Error _create_from_images(const TypedArray<Image> &p_images); + + TypedArray<Image> _get_images() const; + void _set_images(const TypedArray<Image> &p_images); + +protected: + static void _bind_methods(); + +public: + virtual Image::Format get_format() const override; + virtual int get_width() const override; + virtual int get_height() const override; + virtual int get_layers() const override; + virtual bool has_mipmaps() const override; + virtual LayeredType get_layered_type() const override; + + Error create_from_images(Vector<Ref<Image>> p_images); + void update_layer(const Ref<Image> &p_image, int p_layer); + virtual Ref<Image> get_layer_data(int p_layer) const override; + + virtual RID get_rid() const override; + virtual void set_path(const String &p_path, bool p_take_over = false) override; + + ImageTextureLayered(LayeredType p_layered_type); + ~ImageTextureLayered(); +}; + +class ImageTexture3D : public Texture3D { + GDCLASS(ImageTexture3D, Texture3D); + + mutable RID texture; + + Image::Format format = Image::FORMAT_L8; + int width = 1; + int height = 1; + int depth = 1; + bool mipmaps = false; + +protected: + static void _bind_methods(); + + Error _create(Image::Format p_format, int p_width, int p_height, int p_depth, bool p_mipmaps, const TypedArray<Image> &p_data); + void _update(const TypedArray<Image> &p_data); + +public: + virtual Image::Format get_format() const override; + virtual int get_width() const override; + virtual int get_height() const override; + virtual int get_depth() const override; + virtual bool has_mipmaps() const override; + + Error create(Image::Format p_format, int p_width, int p_height, int p_depth, bool p_mipmaps, const Vector<Ref<Image>> &p_data); + void update(const Vector<Ref<Image>> &p_data); + virtual Vector<Ref<Image>> get_data() const override; + + virtual RID get_rid() const override; + virtual void set_path(const String &p_path, bool p_take_over = false) override; + + ImageTexture3D(); + ~ImageTexture3D(); +}; + +class Texture2DArray : public ImageTextureLayered { + GDCLASS(Texture2DArray, ImageTextureLayered) + +protected: + static void _bind_methods(); + +public: + Texture2DArray() : + ImageTextureLayered(LAYERED_TYPE_2D_ARRAY) {} + + virtual Ref<Resource> create_placeholder() const; +}; + +class Cubemap : public ImageTextureLayered { + GDCLASS(Cubemap, ImageTextureLayered); + +protected: + static void _bind_methods(); + +public: + Cubemap() : + ImageTextureLayered(LAYERED_TYPE_CUBEMAP) {} + + virtual Ref<Resource> create_placeholder() const; +}; + +class CubemapArray : public ImageTextureLayered { + GDCLASS(CubemapArray, ImageTextureLayered); + +protected: + static void _bind_methods(); + +public: + CubemapArray() : + ImageTextureLayered(LAYERED_TYPE_CUBEMAP_ARRAY) {} + + virtual Ref<Resource> create_placeholder() const; +}; + +#endif // IMAGE_TEXTURE_H diff --git a/scene/resources/label_settings.cpp b/scene/resources/label_settings.cpp index 37912df921..9a530fb680 100644 --- a/scene/resources/label_settings.cpp +++ b/scene/resources/label_settings.cpp @@ -30,8 +30,6 @@ #include "label_settings.h" -#include "core/core_string_names.h" - void LabelSettings::_font_changed() { emit_changed(); } @@ -95,11 +93,11 @@ real_t LabelSettings::get_line_spacing() const { void LabelSettings::set_font(const Ref<Font> &p_font) { if (font != p_font) { if (font.is_valid()) { - font->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(this, &LabelSettings::_font_changed)); + font->disconnect_changed(callable_mp(this, &LabelSettings::_font_changed)); } font = p_font; if (font.is_valid()) { - font->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &LabelSettings::_font_changed), CONNECT_REFERENCE_COUNTED); + font->connect_changed(callable_mp(this, &LabelSettings::_font_changed), CONNECT_REFERENCE_COUNTED); } emit_changed(); } diff --git a/scene/resources/material.cpp b/scene/resources/material.cpp index a2aab8e7b4..d3ef4a303b 100644 --- a/scene/resources/material.cpp +++ b/scene/resources/material.cpp @@ -385,7 +385,7 @@ void ShaderMaterial::set_shader(const Ref<Shader> &p_shader) { // This can be a slow operation, and `notify_property_list_changed()` (which is called by `_shader_changed()`) // does nothing in non-editor builds anyway. See GH-34741 for details. if (shader.is_valid() && Engine::get_singleton()->is_editor_hint()) { - shader->disconnect("changed", callable_mp(this, &ShaderMaterial::_shader_changed)); + shader->disconnect_changed(callable_mp(this, &ShaderMaterial::_shader_changed)); } shader = p_shader; @@ -395,7 +395,7 @@ void ShaderMaterial::set_shader(const Ref<Shader> &p_shader) { rid = shader->get_rid(); if (Engine::get_singleton()->is_editor_hint()) { - shader->connect("changed", callable_mp(this, &ShaderMaterial::_shader_changed)); + shader->connect_changed(callable_mp(this, &ShaderMaterial::_shader_changed)); } } diff --git a/scene/resources/mesh_library.cpp b/scene/resources/mesh_library.cpp index 015eb0fa45..5000541621 100644 --- a/scene/resources/mesh_library.cpp +++ b/scene/resources/mesh_library.cpp @@ -144,7 +144,6 @@ void MeshLibrary::set_item_name(int p_item, const String &p_name) { void MeshLibrary::set_item_mesh(int p_item, const Ref<Mesh> &p_mesh) { ERR_FAIL_COND_MSG(!item_map.has(p_item), "Requested for nonexistent MeshLibrary item '" + itos(p_item) + "'."); item_map[p_item].mesh = p_mesh; - notify_change_to_owners(); emit_changed(); notify_property_list_changed(); } @@ -152,7 +151,6 @@ void MeshLibrary::set_item_mesh(int p_item, const Ref<Mesh> &p_mesh) { 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(); } @@ -160,7 +158,6 @@ 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; notify_property_list_changed(); - notify_change_to_owners(); emit_changed(); notify_property_list_changed(); } @@ -169,7 +166,6 @@ void MeshLibrary::set_item_navigation_mesh(int p_item, const Ref<NavigationMesh> ERR_FAIL_COND_MSG(!item_map.has(p_item), "Requested for nonexistent MeshLibrary item '" + itos(p_item) + "'."); item_map[p_item].navigation_mesh = p_navigation_mesh; notify_property_list_changed(); - notify_change_to_owners(); emit_changed(); notify_property_list_changed(); } @@ -177,7 +173,6 @@ void MeshLibrary::set_item_navigation_mesh(int p_item, const Ref<NavigationMesh> void MeshLibrary::set_item_navigation_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].navigation_mesh_transform = p_transform; - notify_change_to_owners(); emit_changed(); notify_property_list_changed(); } @@ -186,7 +181,6 @@ void MeshLibrary::set_item_navigation_layers(int p_item, uint32_t p_navigation_l ERR_FAIL_COND_MSG(!item_map.has(p_item), "Requested for nonexistent MeshLibrary item '" + itos(p_item) + "'."); item_map[p_item].navigation_layers = p_navigation_layers; notify_property_list_changed(); - notify_change_to_owners(); emit_changed(); } @@ -244,14 +238,12 @@ bool MeshLibrary::has_item(int p_item) const { void MeshLibrary::remove_item(int p_item) { ERR_FAIL_COND_MSG(!item_map.has(p_item), "Requested for nonexistent MeshLibrary item '" + itos(p_item) + "'."); item_map.erase(p_item); - notify_change_to_owners(); notify_property_list_changed(); emit_changed(); } void MeshLibrary::clear() { item_map.clear(); - notify_change_to_owners(); notify_property_list_changed(); emit_changed(); } diff --git a/scene/resources/mesh_texture.cpp b/scene/resources/mesh_texture.cpp new file mode 100644 index 0000000000..1440b7f02b --- /dev/null +++ b/scene/resources/mesh_texture.cpp @@ -0,0 +1,156 @@ +/**************************************************************************/ +/* mesh_texture.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "mesh_texture.h" + +#include "scene/resources/mesh.h" + +int MeshTexture::get_width() const { + return size.width; +} + +int MeshTexture::get_height() const { + return size.height; +} + +RID MeshTexture::get_rid() const { + return RID(); +} + +bool MeshTexture::has_alpha() const { + return false; +} + +void MeshTexture::set_mesh(const Ref<Mesh> &p_mesh) { + mesh = p_mesh; +} + +Ref<Mesh> MeshTexture::get_mesh() const { + return mesh; +} + +void MeshTexture::set_image_size(const Size2 &p_size) { + size = p_size; +} + +Size2 MeshTexture::get_image_size() const { + return size; +} + +void MeshTexture::set_base_texture(const Ref<Texture2D> &p_texture) { + base_texture = p_texture; +} + +Ref<Texture2D> MeshTexture::get_base_texture() const { + return base_texture; +} + +void MeshTexture::draw(RID p_canvas_item, const Point2 &p_pos, const Color &p_modulate, bool p_transpose) const { + if (mesh.is_null() || base_texture.is_null()) { + return; + } + Transform2D xform; + xform.set_origin(p_pos); + if (p_transpose) { + SWAP(xform.columns[0][1], xform.columns[1][0]); + SWAP(xform.columns[0][0], xform.columns[1][1]); + } + RenderingServer::get_singleton()->canvas_item_add_mesh(p_canvas_item, mesh->get_rid(), xform, p_modulate, base_texture->get_rid()); +} + +void MeshTexture::draw_rect(RID p_canvas_item, const Rect2 &p_rect, bool p_tile, const Color &p_modulate, bool p_transpose) const { + if (mesh.is_null() || base_texture.is_null()) { + return; + } + Transform2D xform; + Vector2 origin = p_rect.position; + if (p_rect.size.x < 0) { + origin.x += size.x; + } + if (p_rect.size.y < 0) { + origin.y += size.y; + } + xform.set_origin(origin); + xform.set_scale(p_rect.size / size); + + if (p_transpose) { + SWAP(xform.columns[0][1], xform.columns[1][0]); + SWAP(xform.columns[0][0], xform.columns[1][1]); + } + RenderingServer::get_singleton()->canvas_item_add_mesh(p_canvas_item, mesh->get_rid(), xform, p_modulate, base_texture->get_rid()); +} + +void MeshTexture::draw_rect_region(RID p_canvas_item, const Rect2 &p_rect, const Rect2 &p_src_rect, const Color &p_modulate, bool p_transpose, bool p_clip_uv) const { + if (mesh.is_null() || base_texture.is_null()) { + return; + } + Transform2D xform; + Vector2 origin = p_rect.position; + if (p_rect.size.x < 0) { + origin.x += size.x; + } + if (p_rect.size.y < 0) { + origin.y += size.y; + } + xform.set_origin(origin); + xform.set_scale(p_rect.size / size); + + if (p_transpose) { + SWAP(xform.columns[0][1], xform.columns[1][0]); + SWAP(xform.columns[0][0], xform.columns[1][1]); + } + RenderingServer::get_singleton()->canvas_item_add_mesh(p_canvas_item, mesh->get_rid(), xform, p_modulate, base_texture->get_rid()); +} + +bool MeshTexture::get_rect_region(const Rect2 &p_rect, const Rect2 &p_src_rect, Rect2 &r_rect, Rect2 &r_src_rect) const { + r_rect = p_rect; + r_src_rect = p_src_rect; + return true; +} + +bool MeshTexture::is_pixel_opaque(int p_x, int p_y) const { + return true; +} + +void MeshTexture::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_mesh", "mesh"), &MeshTexture::set_mesh); + ClassDB::bind_method(D_METHOD("get_mesh"), &MeshTexture::get_mesh); + ClassDB::bind_method(D_METHOD("set_image_size", "size"), &MeshTexture::set_image_size); + ClassDB::bind_method(D_METHOD("get_image_size"), &MeshTexture::get_image_size); + ClassDB::bind_method(D_METHOD("set_base_texture", "texture"), &MeshTexture::set_base_texture); + ClassDB::bind_method(D_METHOD("get_base_texture"), &MeshTexture::get_base_texture); + + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "mesh", PROPERTY_HINT_RESOURCE_TYPE, "Mesh"), "set_mesh", "get_mesh"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "base_texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_base_texture", "get_base_texture"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "image_size", PROPERTY_HINT_RANGE, "0,16384,1,suffix:px"), "set_image_size", "get_image_size"); +} + +MeshTexture::MeshTexture() { +} diff --git a/scene/resources/mesh_texture.h b/scene/resources/mesh_texture.h new file mode 100644 index 0000000000..35ccb727c4 --- /dev/null +++ b/scene/resources/mesh_texture.h @@ -0,0 +1,75 @@ +/**************************************************************************/ +/* mesh_texture.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef MESH_TEXTURE_H +#define MESH_TEXTURE_H + +#include "scene/resources/texture.h" + +class Mesh; + +class MeshTexture : public Texture2D { + GDCLASS(MeshTexture, Texture2D); + RES_BASE_EXTENSION("meshtex"); + + Ref<Texture2D> base_texture; + Ref<Mesh> mesh; + Size2i size; + +protected: + static void _bind_methods(); + +public: + virtual int get_width() const override; + virtual int get_height() const override; + virtual RID get_rid() const override; + + virtual bool has_alpha() const override; + + void set_mesh(const Ref<Mesh> &p_mesh); + Ref<Mesh> get_mesh() const; + + void set_image_size(const Size2 &p_size); + Size2 get_image_size() const; + + void set_base_texture(const Ref<Texture2D> &p_texture); + Ref<Texture2D> get_base_texture() const; + + virtual void draw(RID p_canvas_item, const Point2 &p_pos, const Color &p_modulate = Color(1, 1, 1), bool p_transpose = false) const override; + virtual void draw_rect(RID p_canvas_item, const Rect2 &p_rect, bool p_tile = false, const Color &p_modulate = Color(1, 1, 1), bool p_transpose = false) const override; + virtual void draw_rect_region(RID p_canvas_item, const Rect2 &p_rect, const Rect2 &p_src_rect, const Color &p_modulate = Color(1, 1, 1), bool p_transpose = false, bool p_clip_uv = true) const override; + virtual bool get_rect_region(const Rect2 &p_rect, const Rect2 &p_src_rect, Rect2 &r_rect, Rect2 &r_src_rect) const override; + + bool is_pixel_opaque(int p_x, int p_y) const override; + + MeshTexture(); +}; + +#endif // MESH_TEXTURE_H diff --git a/scene/resources/navigation_mesh.cpp b/scene/resources/navigation_mesh.cpp index 1d13f07b12..82b5c6257c 100644 --- a/scene/resources/navigation_mesh.cpp +++ b/scene/resources/navigation_mesh.cpp @@ -341,6 +341,11 @@ void NavigationMesh::clear_polygons() { polygons.clear(); } +void NavigationMesh::clear() { + polygons.clear(); + vertices.clear(); +} + #ifdef DEBUG_ENABLED Ref<ArrayMesh> NavigationMesh::get_debug_mesh() { if (debug_mesh.is_valid()) { @@ -518,6 +523,8 @@ void NavigationMesh::_bind_methods() { ClassDB::bind_method(D_METHOD("_set_polygons", "polygons"), &NavigationMesh::_set_polygons); ClassDB::bind_method(D_METHOD("_get_polygons"), &NavigationMesh::_get_polygons); + ClassDB::bind_method(D_METHOD("clear"), &NavigationMesh::clear); + ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR3_ARRAY, "vertices", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "set_vertices", "get_vertices"); ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "polygons", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_polygons", "_get_polygons"); diff --git a/scene/resources/navigation_mesh.h b/scene/resources/navigation_mesh.h index c3b8c13c05..8b9b810038 100644 --- a/scene/resources/navigation_mesh.h +++ b/scene/resources/navigation_mesh.h @@ -43,19 +43,6 @@ class NavigationMesh : public Resource { Vector<Polygon> polygons; Ref<ArrayMesh> debug_mesh; - struct _EdgeKey { - Vector3 from; - Vector3 to; - - static uint32_t hash(const _EdgeKey &p_key) { - return HashMapHasherDefault::hash(p_key.from) ^ HashMapHasherDefault::hash(p_key.to); - } - - bool operator==(const _EdgeKey &p_with) const { - return HashMapComparatorDefault<Vector3>::compare(from, p_with.from) && HashMapComparatorDefault<Vector3>::compare(to, p_with.to); - } - }; - protected: static void _bind_methods(); void _validate_property(PropertyInfo &p_property) const; @@ -99,7 +86,7 @@ protected: float agent_max_slope = 45.0f; float region_min_size = 2.0f; float region_merge_size = 20.0f; - float edge_max_length = 12.0f; + float edge_max_length = 0.0f; float edge_max_error = 1.3f; float vertices_per_polygon = 6.0f; float detail_sample_distance = 6.0f; @@ -202,6 +189,8 @@ public: Vector<int> get_polygon(int p_idx); void clear_polygons(); + void clear(); + #ifdef DEBUG_ENABLED Ref<ArrayMesh> get_debug_mesh(); #endif // DEBUG_ENABLED diff --git a/scene/resources/navigation_polygon.cpp b/scene/resources/navigation_polygon.cpp index 0b60d46d40..e521bfb2e0 100644 --- a/scene/resources/navigation_polygon.cpp +++ b/scene/resources/navigation_polygon.cpp @@ -162,6 +162,15 @@ void NavigationPolygon::clear_polygons() { } } +void NavigationPolygon::clear() { + polygons.clear(); + vertices.clear(); + { + MutexLock lock(navigation_mesh_generation); + navigation_mesh.unref(); + } +} + Ref<NavigationMesh> NavigationPolygon::get_navigation_mesh() { MutexLock lock(navigation_mesh_generation); @@ -360,6 +369,8 @@ void NavigationPolygon::_bind_methods() { ClassDB::bind_method(D_METHOD("set_cell_size", "cell_size"), &NavigationPolygon::set_cell_size); ClassDB::bind_method(D_METHOD("get_cell_size"), &NavigationPolygon::get_cell_size); + ClassDB::bind_method(D_METHOD("clear"), &NavigationPolygon::clear); + ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR2_ARRAY, "vertices", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "set_vertices", "get_vertices"); ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "polygons", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_polygons", "_get_polygons"); ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "outlines", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_outlines", "_get_outlines"); diff --git a/scene/resources/navigation_polygon.h b/scene/resources/navigation_polygon.h index 3ab666eb16..7926709a9e 100644 --- a/scene/resources/navigation_polygon.h +++ b/scene/resources/navigation_polygon.h @@ -92,6 +92,8 @@ public: void set_cell_size(real_t p_cell_size); real_t get_cell_size() const; + void clear(); + NavigationPolygon() {} ~NavigationPolygon() {} }; diff --git a/scene/resources/particle_process_material.cpp b/scene/resources/particle_process_material.cpp index d6639d4e3f..745c71626c 100644 --- a/scene/resources/particle_process_material.cpp +++ b/scene/resources/particle_process_material.cpp @@ -31,6 +31,7 @@ #include "particle_process_material.h" #include "core/version.h" +#include "scene/resources/curve_texture.h" Mutex ParticleProcessMaterial::material_mutex; SelfList<ParticleProcessMaterial>::List *ParticleProcessMaterial::dirty_materials = nullptr; diff --git a/scene/resources/placeholder_textures.cpp b/scene/resources/placeholder_textures.cpp new file mode 100644 index 0000000000..224ea1f177 --- /dev/null +++ b/scene/resources/placeholder_textures.cpp @@ -0,0 +1,177 @@ +/**************************************************************************/ +/* placeholder_textures.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "placeholder_textures.h" + +void PlaceholderTexture2D::set_size(Size2 p_size) { + size = p_size; +} + +int PlaceholderTexture2D::get_width() const { + return size.width; +} + +int PlaceholderTexture2D::get_height() const { + return size.height; +} + +bool PlaceholderTexture2D::has_alpha() const { + return false; +} + +Ref<Image> PlaceholderTexture2D::get_image() const { + return Ref<Image>(); +} + +RID PlaceholderTexture2D::get_rid() const { + return rid; +} + +void PlaceholderTexture2D::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_size", "size"), &PlaceholderTexture2D::set_size); + + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "size", PROPERTY_HINT_NONE, "suffix:px"), "set_size", "get_size"); +} + +PlaceholderTexture2D::PlaceholderTexture2D() { + rid = RS::get_singleton()->texture_2d_placeholder_create(); +} + +PlaceholderTexture2D::~PlaceholderTexture2D() { + ERR_FAIL_NULL(RenderingServer::get_singleton()); + RS::get_singleton()->free(rid); +} + +/////////////////////////////////////////////// + +void PlaceholderTexture3D::set_size(const Vector3i &p_size) { + size = p_size; +} + +Vector3i PlaceholderTexture3D::get_size() const { + return size; +} + +Image::Format PlaceholderTexture3D::get_format() const { + return Image::FORMAT_RGB8; +} + +int PlaceholderTexture3D::get_width() const { + return size.x; +} + +int PlaceholderTexture3D::get_height() const { + return size.y; +} + +int PlaceholderTexture3D::get_depth() const { + return size.z; +} + +bool PlaceholderTexture3D::has_mipmaps() const { + return false; +} + +Vector<Ref<Image>> PlaceholderTexture3D::get_data() const { + return Vector<Ref<Image>>(); +} + +void PlaceholderTexture3D::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_size", "size"), &PlaceholderTexture3D::set_size); + ClassDB::bind_method(D_METHOD("get_size"), &PlaceholderTexture3D::get_size); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3I, "size", PROPERTY_HINT_NONE, "suffix:px"), "set_size", "get_size"); +} + +PlaceholderTexture3D::PlaceholderTexture3D() { + rid = RS::get_singleton()->texture_3d_placeholder_create(); +} +PlaceholderTexture3D::~PlaceholderTexture3D() { + ERR_FAIL_NULL(RenderingServer::get_singleton()); + RS::get_singleton()->free(rid); +} + +///////////////////////////////////////////////// + +void PlaceholderTextureLayered::set_size(const Size2i &p_size) { + size = p_size; +} + +Size2i PlaceholderTextureLayered::get_size() const { + return size; +} + +void PlaceholderTextureLayered::set_layers(int p_layers) { + layers = p_layers; +} + +Image::Format PlaceholderTextureLayered::get_format() const { + return Image::FORMAT_RGB8; +} + +TextureLayered::LayeredType PlaceholderTextureLayered::get_layered_type() const { + return layered_type; +} + +int PlaceholderTextureLayered::get_width() const { + return size.x; +} + +int PlaceholderTextureLayered::get_height() const { + return size.y; +} + +int PlaceholderTextureLayered::get_layers() const { + return layers; +} + +bool PlaceholderTextureLayered::has_mipmaps() const { + return false; +} + +Ref<Image> PlaceholderTextureLayered::get_layer_data(int p_layer) const { + return Ref<Image>(); +} + +void PlaceholderTextureLayered::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_size", "size"), &PlaceholderTextureLayered::set_size); + ClassDB::bind_method(D_METHOD("get_size"), &PlaceholderTextureLayered::get_size); + ClassDB::bind_method(D_METHOD("set_layers", "layers"), &PlaceholderTextureLayered::set_layers); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "size", PROPERTY_HINT_NONE, "suffix:px"), "set_size", "get_size"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "layers", PROPERTY_HINT_RANGE, "1,4096"), "set_layers", "get_layers"); +} + +PlaceholderTextureLayered::PlaceholderTextureLayered(LayeredType p_type) { + layered_type = p_type; + rid = RS::get_singleton()->texture_2d_layered_placeholder_create(RS::TextureLayeredType(layered_type)); +} +PlaceholderTextureLayered::~PlaceholderTextureLayered() { + ERR_FAIL_NULL(RenderingServer::get_singleton()); + RS::get_singleton()->free(rid); +} diff --git a/scene/resources/placeholder_textures.h b/scene/resources/placeholder_textures.h new file mode 100644 index 0000000000..116ed0f0f0 --- /dev/null +++ b/scene/resources/placeholder_textures.h @@ -0,0 +1,130 @@ +/**************************************************************************/ +/* placeholder_textures.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef PLACEHOLDER_TEXTURES_H +#define PLACEHOLDER_TEXTURES_H + +#include "scene/resources/texture.h" + +class PlaceholderTexture2D : public Texture2D { + GDCLASS(PlaceholderTexture2D, Texture2D) + + RID rid; + Size2 size = Size2(1, 1); + +protected: + static void _bind_methods(); + +public: + void set_size(Size2 p_size); + + virtual int get_width() const override; + virtual int get_height() const override; + virtual RID get_rid() const override; + virtual bool has_alpha() const override; + + virtual Ref<Image> get_image() const override; + + PlaceholderTexture2D(); + ~PlaceholderTexture2D(); +}; + +class PlaceholderTexture3D : public Texture3D { + GDCLASS(PlaceholderTexture3D, Texture3D) + + RID rid; + Vector3i size = Vector3i(1, 1, 1); + +protected: + static void _bind_methods(); + +public: + void set_size(const Vector3i &p_size); + Vector3i get_size() const; + virtual Image::Format get_format() const override; + virtual int get_width() const override; + virtual int get_height() const override; + virtual int get_depth() const override; + virtual bool has_mipmaps() const override; + virtual Vector<Ref<Image>> get_data() const override; + + PlaceholderTexture3D(); + ~PlaceholderTexture3D(); +}; + +class PlaceholderTextureLayered : public TextureLayered { + GDCLASS(PlaceholderTextureLayered, TextureLayered) + + RID rid; + Size2i size = Size2i(1, 1); + int layers = 1; + LayeredType layered_type = LAYERED_TYPE_2D_ARRAY; + +protected: + static void _bind_methods(); + +public: + void set_size(const Size2i &p_size); + Size2i get_size() const; + void set_layers(int p_layers); + virtual Image::Format get_format() const override; + virtual LayeredType get_layered_type() const override; + virtual int get_width() const override; + virtual int get_height() const override; + virtual int get_layers() const override; + virtual bool has_mipmaps() const override; + virtual Ref<Image> get_layer_data(int p_layer) const override; + + PlaceholderTextureLayered(LayeredType p_type); + ~PlaceholderTextureLayered(); +}; + +class PlaceholderTexture2DArray : public PlaceholderTextureLayered { + GDCLASS(PlaceholderTexture2DArray, PlaceholderTextureLayered) +public: + PlaceholderTexture2DArray() : + PlaceholderTextureLayered(LAYERED_TYPE_2D_ARRAY) {} +}; + +class PlaceholderCubemap : public PlaceholderTextureLayered { + GDCLASS(PlaceholderCubemap, PlaceholderTextureLayered) +public: + PlaceholderCubemap() : + PlaceholderTextureLayered(LAYERED_TYPE_CUBEMAP) {} +}; + +class PlaceholderCubemapArray : public PlaceholderTextureLayered { + GDCLASS(PlaceholderCubemapArray, PlaceholderTextureLayered) +public: + PlaceholderCubemapArray() : + PlaceholderTextureLayered(LAYERED_TYPE_CUBEMAP_ARRAY) {} +}; + +#endif // PLACEHOLDER_TEXTURES_H diff --git a/scene/resources/portable_compressed_texture.cpp b/scene/resources/portable_compressed_texture.cpp new file mode 100644 index 0000000000..a61799c7d6 --- /dev/null +++ b/scene/resources/portable_compressed_texture.cpp @@ -0,0 +1,339 @@ +/**************************************************************************/ +/* portable_compressed_texture.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "portable_compressed_texture.h" + +#include "core/io/marshalls.h" +#include "scene/resources/bit_map.h" + +void PortableCompressedTexture2D::_set_data(const Vector<uint8_t> &p_data) { + if (p_data.size() == 0) { + return; //nothing to do + } + + const uint8_t *data = p_data.ptr(); + uint32_t data_size = p_data.size(); + ERR_FAIL_COND(data_size < 20); + compression_mode = CompressionMode(decode_uint32(data + 0)); + format = Image::Format(decode_uint32(data + 4)); + uint32_t mipmap_count = decode_uint32(data + 8); + size.width = decode_uint32(data + 12); + size.height = decode_uint32(data + 16); + mipmaps = mipmap_count > 1; + + data += 20; + data_size -= 20; + + Ref<Image> image; + + switch (compression_mode) { + case COMPRESSION_MODE_LOSSLESS: + case COMPRESSION_MODE_LOSSY: { + Vector<uint8_t> image_data; + + ERR_FAIL_COND(data_size < 4); + for (uint32_t i = 0; i < mipmap_count; i++) { + uint32_t mipsize = decode_uint32(data); + data += 4; + data_size -= 4; + ERR_FAIL_COND(mipsize < data_size); + Ref<Image> img = memnew(Image(data, data_size)); + ERR_FAIL_COND(img->is_empty()); + if (img->get_format() != format) { // May happen due to webp/png in the tiny mipmaps. + img->convert(format); + } + image_data.append_array(img->get_data()); + + data += mipsize; + data_size -= mipsize; + } + + image = Ref<Image>(memnew(Image(size.width, size.height, mipmap_count > 1, format, image_data))); + + } break; + case COMPRESSION_MODE_BASIS_UNIVERSAL: { + ERR_FAIL_NULL(Image::basis_universal_unpacker_ptr); + image = Image::basis_universal_unpacker_ptr(data, data_size); + + } break; + case COMPRESSION_MODE_S3TC: + case COMPRESSION_MODE_ETC2: + case COMPRESSION_MODE_BPTC: { + image = Ref<Image>(memnew(Image(size.width, size.height, mipmap_count > 1, format, p_data.slice(20)))); + } break; + } + ERR_FAIL_COND(image.is_null()); + + if (texture.is_null()) { + texture = RenderingServer::get_singleton()->texture_2d_create(image); + } else { + RID new_texture = RenderingServer::get_singleton()->texture_2d_create(image); + RenderingServer::get_singleton()->texture_replace(texture, new_texture); + } + + image_stored = true; + RenderingServer::get_singleton()->texture_set_size_override(texture, size_override.width, size_override.height); + alpha_cache.unref(); + + if (keep_all_compressed_buffers || keep_compressed_buffer) { + compressed_buffer = p_data; + } else { + compressed_buffer.clear(); + } +} + +PortableCompressedTexture2D::CompressionMode PortableCompressedTexture2D::get_compression_mode() const { + return compression_mode; +} +Vector<uint8_t> PortableCompressedTexture2D::_get_data() const { + return compressed_buffer; +} + +void PortableCompressedTexture2D::create_from_image(const Ref<Image> &p_image, CompressionMode p_compression_mode, bool p_normal_map, float p_lossy_quality) { + ERR_FAIL_COND(p_image.is_null() || p_image->is_empty()); + + Vector<uint8_t> buffer; + + buffer.resize(20); + encode_uint32(p_compression_mode, buffer.ptrw()); + encode_uint32(p_image->get_format(), buffer.ptrw() + 4); + encode_uint32(p_image->get_mipmap_count() + 1, buffer.ptrw() + 8); + encode_uint32(p_image->get_width(), buffer.ptrw() + 12); + encode_uint32(p_image->get_height(), buffer.ptrw() + 16); + + switch (p_compression_mode) { + case COMPRESSION_MODE_LOSSLESS: + case COMPRESSION_MODE_LOSSY: { + for (int i = 0; i < p_image->get_mipmap_count() + 1; i++) { + Vector<uint8_t> data; + if (p_compression_mode == COMPRESSION_MODE_LOSSY) { + data = Image::webp_lossy_packer(p_image->get_image_from_mipmap(i), p_lossy_quality); + } else { + data = Image::webp_lossless_packer(p_image->get_image_from_mipmap(i)); + } + int data_len = data.size(); + buffer.resize(buffer.size() + 4); + encode_uint32(data_len, buffer.ptrw() + buffer.size() - 4); + buffer.append_array(data); + } + } break; + case COMPRESSION_MODE_BASIS_UNIVERSAL: { + Image::UsedChannels uc = p_image->detect_used_channels(p_normal_map ? Image::COMPRESS_SOURCE_NORMAL : Image::COMPRESS_SOURCE_GENERIC); + Vector<uint8_t> budata = Image::basis_universal_packer(p_image, uc); + buffer.append_array(budata); + + } break; + case COMPRESSION_MODE_S3TC: + case COMPRESSION_MODE_ETC2: + case COMPRESSION_MODE_BPTC: { + Ref<Image> copy = p_image->duplicate(); + switch (p_compression_mode) { + case COMPRESSION_MODE_S3TC: + copy->compress(Image::COMPRESS_S3TC); + break; + case COMPRESSION_MODE_ETC2: + copy->compress(Image::COMPRESS_ETC2); + break; + case COMPRESSION_MODE_BPTC: + copy->compress(Image::COMPRESS_BPTC); + break; + default: { + }; + } + + buffer.append_array(copy->get_data()); + + } break; + } + + _set_data(buffer); +} + +Image::Format PortableCompressedTexture2D::get_format() const { + return format; +} + +Ref<Image> PortableCompressedTexture2D::get_image() const { + if (image_stored) { + return RenderingServer::get_singleton()->texture_2d_get(texture); + } else { + return Ref<Image>(); + } +} + +int PortableCompressedTexture2D::get_width() const { + return size.width; +} + +int PortableCompressedTexture2D::get_height() const { + return size.height; +} + +RID PortableCompressedTexture2D::get_rid() const { + if (texture.is_null()) { + // We are in trouble, create something temporary. + texture = RenderingServer::get_singleton()->texture_2d_placeholder_create(); + } + return texture; +} + +bool PortableCompressedTexture2D::has_alpha() const { + return (format == Image::FORMAT_LA8 || format == Image::FORMAT_RGBA8); +} + +void PortableCompressedTexture2D::draw(RID p_canvas_item, const Point2 &p_pos, const Color &p_modulate, bool p_transpose) const { + if (size.width == 0 || size.height == 0) { + return; + } + RenderingServer::get_singleton()->canvas_item_add_texture_rect(p_canvas_item, Rect2(p_pos, size), texture, false, p_modulate, p_transpose); +} + +void PortableCompressedTexture2D::draw_rect(RID p_canvas_item, const Rect2 &p_rect, bool p_tile, const Color &p_modulate, bool p_transpose) const { + if (size.width == 0 || size.height == 0) { + return; + } + RenderingServer::get_singleton()->canvas_item_add_texture_rect(p_canvas_item, p_rect, texture, p_tile, p_modulate, p_transpose); +} + +void PortableCompressedTexture2D::draw_rect_region(RID p_canvas_item, const Rect2 &p_rect, const Rect2 &p_src_rect, const Color &p_modulate, bool p_transpose, bool p_clip_uv) const { + if (size.width == 0 || size.height == 0) { + return; + } + RenderingServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas_item, p_rect, texture, p_src_rect, p_modulate, p_transpose, p_clip_uv); +} + +bool PortableCompressedTexture2D::is_pixel_opaque(int p_x, int p_y) const { + if (!alpha_cache.is_valid()) { + Ref<Image> img = get_image(); + if (img.is_valid()) { + if (img->is_compressed()) { //must decompress, if compressed + Ref<Image> decom = img->duplicate(); + decom->decompress(); + img = decom; + } + alpha_cache.instantiate(); + alpha_cache->create_from_image_alpha(img); + } + } + + if (alpha_cache.is_valid()) { + int aw = int(alpha_cache->get_size().width); + int ah = int(alpha_cache->get_size().height); + if (aw == 0 || ah == 0) { + return true; + } + + int x = p_x * aw / size.width; + int y = p_y * ah / size.height; + + x = CLAMP(x, 0, aw); + y = CLAMP(y, 0, ah); + + return alpha_cache->get_bit(x, y); + } + + return true; +} + +void PortableCompressedTexture2D::set_size_override(const Size2 &p_size) { + size_override = p_size; + RenderingServer::get_singleton()->texture_set_size_override(texture, size_override.width, size_override.height); +} + +Size2 PortableCompressedTexture2D::get_size_override() const { + return size_override; +} + +void PortableCompressedTexture2D::set_path(const String &p_path, bool p_take_over) { + if (texture.is_valid()) { + RenderingServer::get_singleton()->texture_set_path(texture, p_path); + } + + Resource::set_path(p_path, p_take_over); +} + +bool PortableCompressedTexture2D::keep_all_compressed_buffers = false; + +void PortableCompressedTexture2D::set_keep_all_compressed_buffers(bool p_keep) { + keep_all_compressed_buffers = p_keep; +} + +bool PortableCompressedTexture2D::is_keeping_all_compressed_buffers() { + return keep_all_compressed_buffers; +} + +void PortableCompressedTexture2D::set_keep_compressed_buffer(bool p_keep) { + keep_compressed_buffer = p_keep; + if (!p_keep) { + compressed_buffer.clear(); + } +} + +bool PortableCompressedTexture2D::is_keeping_compressed_buffer() const { + return keep_compressed_buffer; +} + +void PortableCompressedTexture2D::_bind_methods() { + ClassDB::bind_method(D_METHOD("create_from_image", "image", "compression_mode", "normal_map", "lossy_quality"), &PortableCompressedTexture2D::create_from_image, DEFVAL(false), DEFVAL(0.8)); + ClassDB::bind_method(D_METHOD("get_format"), &PortableCompressedTexture2D::get_format); + ClassDB::bind_method(D_METHOD("get_compression_mode"), &PortableCompressedTexture2D::get_compression_mode); + + ClassDB::bind_method(D_METHOD("set_size_override", "size"), &PortableCompressedTexture2D::set_size_override); + ClassDB::bind_method(D_METHOD("get_size_override"), &PortableCompressedTexture2D::get_size_override); + + ClassDB::bind_method(D_METHOD("set_keep_compressed_buffer", "keep"), &PortableCompressedTexture2D::set_keep_compressed_buffer); + ClassDB::bind_method(D_METHOD("is_keeping_compressed_buffer"), &PortableCompressedTexture2D::is_keeping_compressed_buffer); + + ClassDB::bind_method(D_METHOD("_set_data", "data"), &PortableCompressedTexture2D::_set_data); + ClassDB::bind_method(D_METHOD("_get_data"), &PortableCompressedTexture2D::_get_data); + + ClassDB::bind_static_method("PortableCompressedTexture2D", D_METHOD("set_keep_all_compressed_buffers", "keep"), &PortableCompressedTexture2D::set_keep_all_compressed_buffers); + ClassDB::bind_static_method("PortableCompressedTexture2D", D_METHOD("is_keeping_all_compressed_buffers"), &PortableCompressedTexture2D::is_keeping_all_compressed_buffers); + + ADD_PROPERTY(PropertyInfo(Variant::PACKED_BYTE_ARRAY, "_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "_set_data", "_get_data"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "size_override", PROPERTY_HINT_NONE, "suffix:px"), "set_size_override", "get_size_override"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "keep_compressed_buffer"), "set_keep_compressed_buffer", "is_keeping_compressed_buffer"); + + BIND_ENUM_CONSTANT(COMPRESSION_MODE_LOSSLESS); + BIND_ENUM_CONSTANT(COMPRESSION_MODE_LOSSY); + BIND_ENUM_CONSTANT(COMPRESSION_MODE_BASIS_UNIVERSAL); + BIND_ENUM_CONSTANT(COMPRESSION_MODE_S3TC); + BIND_ENUM_CONSTANT(COMPRESSION_MODE_ETC2); + BIND_ENUM_CONSTANT(COMPRESSION_MODE_BPTC); +} + +PortableCompressedTexture2D::PortableCompressedTexture2D() {} + +PortableCompressedTexture2D::~PortableCompressedTexture2D() { + if (texture.is_valid()) { + ERR_FAIL_NULL(RenderingServer::get_singleton()); + RenderingServer::get_singleton()->free(texture); + } +} diff --git a/scene/resources/portable_compressed_texture.h b/scene/resources/portable_compressed_texture.h new file mode 100644 index 0000000000..86d80e39f7 --- /dev/null +++ b/scene/resources/portable_compressed_texture.h @@ -0,0 +1,110 @@ +/**************************************************************************/ +/* portable_compressed_texture.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef PORTABLE_COMPRESSED_TEXTURE_H +#define PORTABLE_COMPRESSED_TEXTURE_H + +#include "scene/resources/texture.h" + +class BitMap; + +class PortableCompressedTexture2D : public Texture2D { + GDCLASS(PortableCompressedTexture2D, Texture2D); + +public: + enum CompressionMode { + COMPRESSION_MODE_LOSSLESS, + COMPRESSION_MODE_LOSSY, + COMPRESSION_MODE_BASIS_UNIVERSAL, + COMPRESSION_MODE_S3TC, + COMPRESSION_MODE_ETC2, + COMPRESSION_MODE_BPTC, + }; + +private: + CompressionMode compression_mode = COMPRESSION_MODE_LOSSLESS; + static bool keep_all_compressed_buffers; + bool keep_compressed_buffer = false; + Vector<uint8_t> compressed_buffer; + Size2 size; + Size2 size_override; + bool mipmaps = false; + Image::Format format = Image::FORMAT_L8; + + mutable RID texture; + mutable Ref<BitMap> alpha_cache; + + bool image_stored = false; + +protected: + Vector<uint8_t> _get_data() const; + void _set_data(const Vector<uint8_t> &p_data); + + static void _bind_methods(); + +public: + CompressionMode get_compression_mode() const; + void create_from_image(const Ref<Image> &p_image, CompressionMode p_compression_mode, bool p_normal_map = false, float p_lossy_quality = 0.8); + + Image::Format get_format() const; + + void update(const Ref<Image> &p_image); + Ref<Image> get_image() const override; + + int get_width() const override; + int get_height() const override; + + virtual RID get_rid() const override; + + bool has_alpha() const override; + virtual void draw(RID p_canvas_item, const Point2 &p_pos, const Color &p_modulate = Color(1, 1, 1), bool p_transpose = false) const override; + virtual void draw_rect(RID p_canvas_item, const Rect2 &p_rect, bool p_tile = false, const Color &p_modulate = Color(1, 1, 1), bool p_transpose = false) const override; + virtual void draw_rect_region(RID p_canvas_item, const Rect2 &p_rect, const Rect2 &p_src_rect, const Color &p_modulate = Color(1, 1, 1), bool p_transpose = false, bool p_clip_uv = true) const override; + + bool is_pixel_opaque(int p_x, int p_y) const override; + + virtual void set_path(const String &p_path, bool p_take_over = false) override; + + void set_size_override(const Size2 &p_size); + Size2 get_size_override() const; + + void set_keep_compressed_buffer(bool p_keep); + bool is_keeping_compressed_buffer() const; + + static void set_keep_all_compressed_buffers(bool p_keep); + static bool is_keeping_all_compressed_buffers(); + + PortableCompressedTexture2D(); + ~PortableCompressedTexture2D(); +}; + +VARIANT_ENUM_CAST(PortableCompressedTexture2D::CompressionMode) + +#endif // PORTABLE_COMPRESSED_TEXTURE_H diff --git a/scene/resources/primitive_meshes.cpp b/scene/resources/primitive_meshes.cpp index 204987cac9..6430a1302d 100644 --- a/scene/resources/primitive_meshes.cpp +++ b/scene/resources/primitive_meshes.cpp @@ -31,7 +31,6 @@ #include "primitive_meshes.h" #include "core/config/project_settings.h" -#include "core/core_string_names.h" #include "scene/resources/theme.h" #include "scene/theme/theme_db.h" #include "servers/rendering_server.h" @@ -2194,11 +2193,11 @@ void TubeTrailMesh::set_curve(const Ref<Curve> &p_curve) { return; } if (curve.is_valid()) { - curve->disconnect("changed", callable_mp(this, &TubeTrailMesh::_curve_changed)); + curve->disconnect_changed(callable_mp(this, &TubeTrailMesh::_curve_changed)); } curve = p_curve; if (curve.is_valid()) { - curve->connect("changed", callable_mp(this, &TubeTrailMesh::_curve_changed)); + curve->connect_changed(callable_mp(this, &TubeTrailMesh::_curve_changed)); } _request_update(); } @@ -2533,11 +2532,11 @@ void RibbonTrailMesh::set_curve(const Ref<Curve> &p_curve) { return; } if (curve.is_valid()) { - curve->disconnect("changed", callable_mp(this, &RibbonTrailMesh::_curve_changed)); + curve->disconnect_changed(callable_mp(this, &RibbonTrailMesh::_curve_changed)); } curve = p_curve; if (curve.is_valid()) { - curve->connect("changed", callable_mp(this, &RibbonTrailMesh::_curve_changed)); + curve->connect_changed(callable_mp(this, &RibbonTrailMesh::_curve_changed)); } _request_update(); } @@ -3446,13 +3445,13 @@ void TextMesh::_font_changed() { void TextMesh::set_font(const Ref<Font> &p_font) { if (font_override != p_font) { if (font_override.is_valid()) { - font_override->disconnect(CoreStringNames::get_singleton()->changed, Callable(this, "_font_changed")); + font_override->disconnect_changed(Callable(this, "_font_changed")); } font_override = p_font; dirty_font = true; dirty_cache = true; if (font_override.is_valid()) { - font_override->connect(CoreStringNames::get_singleton()->changed, Callable(this, "_font_changed")); + font_override->connect_changed(Callable(this, "_font_changed")); } _request_update(); } diff --git a/scene/resources/resource_format_text.cpp b/scene/resources/resource_format_text.cpp index dc0240859e..a2c04698e1 100644 --- a/scene/resources/resource_format_text.cpp +++ b/scene/resources/resource_format_text.cpp @@ -1877,6 +1877,8 @@ void ResourceFormatSaverTextInstance::_find_resources(const Variant &p_variant, return; } + resource_set.insert(res); + List<PropertyInfo> property_list; res->get_property_list(&property_list); @@ -1891,14 +1893,17 @@ void ResourceFormatSaverTextInstance::_find_resources(const Variant &p_variant, Variant v = res->get(I->get().name); if (pi.usage & PROPERTY_USAGE_RESOURCE_NOT_PERSISTENT) { + NonPersistentKey npk; + npk.base = res; + npk.property = pi.name; + non_persistent_map[npk] = v; + Ref<Resource> sres = v; if (sres.is_valid()) { - NonPersistentKey npk; - npk.base = res; - npk.property = pi.name; - non_persistent_map[npk] = sres; resource_set.insert(sres); saved_resources.push_back(sres); + } else { + _find_resources(v); } } else { _find_resources(v); @@ -1908,8 +1913,7 @@ void ResourceFormatSaverTextInstance::_find_resources(const Variant &p_variant, I = I->next(); } - resource_set.insert(res); //saved after, so the children it needs are available when loaded - saved_resources.push_back(res); + saved_resources.push_back(res); // Saved after, so the children it needs are available when loaded } break; case Variant::ARRAY: { diff --git a/scene/resources/resource_format_text.h b/scene/resources/resource_format_text.h index c35a594f72..1734ccc98b 100644 --- a/scene/resources/resource_format_text.h +++ b/scene/resources/resource_format_text.h @@ -171,7 +171,7 @@ class ResourceFormatSaverTextInstance { bool operator<(const NonPersistentKey &p_key) const { return base == p_key.base ? property < p_key.property : base < p_key.base; } }; - RBMap<NonPersistentKey, Ref<Resource>> non_persistent_map; + RBMap<NonPersistentKey, Variant> non_persistent_map; HashSet<Ref<Resource>> resource_set; List<Ref<Resource>> saved_resources; diff --git a/scene/resources/separation_ray_shape_3d.cpp b/scene/resources/separation_ray_shape_3d.cpp index 3d8e2af201..07e93b8b79 100644 --- a/scene/resources/separation_ray_shape_3d.cpp +++ b/scene/resources/separation_ray_shape_3d.cpp @@ -56,7 +56,7 @@ void SeparationRayShape3D::_update_shape() { void SeparationRayShape3D::set_length(float p_length) { length = p_length; _update_shape(); - notify_change_to_owners(); + emit_changed(); } float SeparationRayShape3D::get_length() const { @@ -66,7 +66,7 @@ float SeparationRayShape3D::get_length() const { void SeparationRayShape3D::set_slide_on_slope(bool p_active) { slide_on_slope = p_active; _update_shape(); - notify_change_to_owners(); + emit_changed(); } bool SeparationRayShape3D::get_slide_on_slope() const { @@ -88,5 +88,5 @@ SeparationRayShape3D::SeparationRayShape3D() : Shape3D(PhysicsServer3D::get_singleton()->shape_create(PhysicsServer3D::SHAPE_SEPARATION_RAY)) { /* Code copied from setters to prevent the use of uninitialized variables */ _update_shape(); - notify_change_to_owners(); + emit_changed(); } diff --git a/scene/resources/shader.cpp b/scene/resources/shader.cpp index 62c2483a93..f919386980 100644 --- a/scene/resources/shader.cpp +++ b/scene/resources/shader.cpp @@ -63,21 +63,7 @@ void Shader::set_include_path(const String &p_path) { void Shader::set_code(const String &p_code) { for (const Ref<ShaderInclude> &E : include_dependencies) { - E->disconnect(SNAME("changed"), callable_mp(this, &Shader::_dependency_changed)); - } - - String type = ShaderLanguage::get_shader_type(p_code); - - if (type == "canvas_item") { - mode = MODE_CANVAS_ITEM; - } else if (type == "particles") { - mode = MODE_PARTICLES; - } else if (type == "sky") { - mode = MODE_SKY; - } else if (type == "fog") { - mode = MODE_FOG; - } else { - mode = MODE_SPATIAL; + E->disconnect_changed(callable_mp(this, &Shader::_dependency_changed)); } code = p_code; @@ -100,8 +86,23 @@ void Shader::set_code(const String &p_code) { } } + // Try to get the shader type from the final, fully preprocessed shader code. + String type = ShaderLanguage::get_shader_type(pp_code); + + if (type == "canvas_item") { + mode = MODE_CANVAS_ITEM; + } else if (type == "particles") { + mode = MODE_PARTICLES; + } else if (type == "sky") { + mode = MODE_SKY; + } else if (type == "fog") { + mode = MODE_FOG; + } else { + mode = MODE_SPATIAL; + } + for (const Ref<ShaderInclude> &E : include_dependencies) { - E->connect(SNAME("changed"), callable_mp(this, &Shader::_dependency_changed)); + E->connect_changed(callable_mp(this, &Shader::_dependency_changed)); } RenderingServer::get_singleton()->shader_set_code(shader, pp_code); diff --git a/scene/resources/shader_include.cpp b/scene/resources/shader_include.cpp index 6e9c64d34b..aead484cc1 100644 --- a/scene/resources/shader_include.cpp +++ b/scene/resources/shader_include.cpp @@ -40,7 +40,7 @@ void ShaderInclude::set_code(const String &p_code) { code = p_code; for (const Ref<ShaderInclude> &E : dependencies) { - E->disconnect(SNAME("changed"), callable_mp(this, &ShaderInclude::_dependency_changed)); + E->disconnect_changed(callable_mp(this, &ShaderInclude::_dependency_changed)); } { @@ -60,7 +60,7 @@ void ShaderInclude::set_code(const String &p_code) { } for (const Ref<ShaderInclude> &E : dependencies) { - E->connect(SNAME("changed"), callable_mp(this, &ShaderInclude::_dependency_changed)); + E->connect_changed(callable_mp(this, &ShaderInclude::_dependency_changed)); } emit_changed(); diff --git a/scene/resources/sphere_shape_3d.cpp b/scene/resources/sphere_shape_3d.cpp index fa22aabe5b..56b78471ec 100644 --- a/scene/resources/sphere_shape_3d.cpp +++ b/scene/resources/sphere_shape_3d.cpp @@ -67,7 +67,7 @@ void SphereShape3D::set_radius(float p_radius) { ERR_FAIL_COND_MSG(p_radius < 0, "SphereShape3D radius cannot be negative."); radius = p_radius; _update_shape(); - notify_change_to_owners(); + emit_changed(); } float SphereShape3D::get_radius() const { diff --git a/scene/resources/style_box.cpp b/scene/resources/style_box.cpp index a9ebb21345..f87bf1ee05 100644 --- a/scene/resources/style_box.cpp +++ b/scene/resources/style_box.cpp @@ -32,8 +32,6 @@ #include "scene/main/canvas_item.h" -#include <limits.h> - Size2 StyleBox::get_minimum_size() const { Size2 min_size = Size2(get_margin(SIDE_LEFT) + get_margin(SIDE_RIGHT), get_margin(SIDE_TOP) + get_margin(SIDE_BOTTOM)); Size2 custom_size; @@ -145,925 +143,3 @@ StyleBox::StyleBox() { content_margin[i] = -1; } } - -void StyleBoxTexture::set_texture(Ref<Texture2D> p_texture) { - if (texture == p_texture) { - return; - } - texture = p_texture; - emit_changed(); -} - -Ref<Texture2D> StyleBoxTexture::get_texture() const { - return texture; -} - -void StyleBoxTexture::set_texture_margin(Side p_side, float p_size) { - ERR_FAIL_INDEX((int)p_side, 4); - - texture_margin[p_side] = p_size; - emit_changed(); -} - -void StyleBoxTexture::set_texture_margin_all(float p_size) { - for (int i = 0; i < 4; i++) { - texture_margin[i] = p_size; - } - emit_changed(); -} - -void StyleBoxTexture::set_texture_margin_individual(float p_left, float p_top, float p_right, float p_bottom) { - texture_margin[SIDE_LEFT] = p_left; - texture_margin[SIDE_TOP] = p_top; - texture_margin[SIDE_RIGHT] = p_right; - texture_margin[SIDE_BOTTOM] = p_bottom; - emit_changed(); -} - -float StyleBoxTexture::get_texture_margin(Side p_side) const { - ERR_FAIL_INDEX_V((int)p_side, 4, 0.0); - - return texture_margin[p_side]; -} - -float StyleBoxTexture::get_style_margin(Side p_side) const { - ERR_FAIL_INDEX_V((int)p_side, 4, 0.0); - - return texture_margin[p_side]; -} - -Rect2 StyleBoxTexture::get_draw_rect(const Rect2 &p_rect) const { - return p_rect.grow_individual(expand_margin[SIDE_LEFT], expand_margin[SIDE_TOP], expand_margin[SIDE_RIGHT], expand_margin[SIDE_BOTTOM]); -} - -void StyleBoxTexture::draw(RID p_canvas_item, const Rect2 &p_rect) const { - if (texture.is_null()) { - return; - } - - Rect2 rect = p_rect; - Rect2 src_rect = region_rect; - - texture->get_rect_region(rect, src_rect, rect, src_rect); - - rect.position.x -= expand_margin[SIDE_LEFT]; - rect.position.y -= expand_margin[SIDE_TOP]; - rect.size.x += expand_margin[SIDE_LEFT] + expand_margin[SIDE_RIGHT]; - rect.size.y += expand_margin[SIDE_TOP] + expand_margin[SIDE_BOTTOM]; - - Vector2 start_offset = Vector2(texture_margin[SIDE_LEFT], texture_margin[SIDE_TOP]); - Vector2 end_offset = Vector2(texture_margin[SIDE_RIGHT], texture_margin[SIDE_BOTTOM]); - - RenderingServer::get_singleton()->canvas_item_add_nine_patch(p_canvas_item, rect, src_rect, texture->get_rid(), start_offset, end_offset, RS::NinePatchAxisMode(axis_h), RS::NinePatchAxisMode(axis_v), draw_center, modulate); -} - -void StyleBoxTexture::set_draw_center(bool p_enabled) { - draw_center = p_enabled; - emit_changed(); -} - -bool StyleBoxTexture::is_draw_center_enabled() const { - return draw_center; -} - -void StyleBoxTexture::set_expand_margin(Side p_side, float p_size) { - ERR_FAIL_INDEX((int)p_side, 4); - expand_margin[p_side] = p_size; - emit_changed(); -} - -void StyleBoxTexture::set_expand_margin_individual(float p_left, float p_top, float p_right, float p_bottom) { - expand_margin[SIDE_LEFT] = p_left; - expand_margin[SIDE_TOP] = p_top; - expand_margin[SIDE_RIGHT] = p_right; - expand_margin[SIDE_BOTTOM] = p_bottom; - emit_changed(); -} - -void StyleBoxTexture::set_expand_margin_all(float p_expand_margin_size) { - for (int i = 0; i < 4; i++) { - expand_margin[i] = p_expand_margin_size; - } - emit_changed(); -} - -float StyleBoxTexture::get_expand_margin(Side p_side) const { - ERR_FAIL_INDEX_V((int)p_side, 4, 0); - return expand_margin[p_side]; -} - -void StyleBoxTexture::set_region_rect(const Rect2 &p_region_rect) { - if (region_rect == p_region_rect) { - return; - } - - region_rect = p_region_rect; - emit_changed(); -} - -Rect2 StyleBoxTexture::get_region_rect() const { - return region_rect; -} - -void StyleBoxTexture::set_h_axis_stretch_mode(AxisStretchMode p_mode) { - ERR_FAIL_INDEX((int)p_mode, 3); - axis_h = p_mode; - emit_changed(); -} - -StyleBoxTexture::AxisStretchMode StyleBoxTexture::get_h_axis_stretch_mode() const { - return axis_h; -} - -void StyleBoxTexture::set_v_axis_stretch_mode(AxisStretchMode p_mode) { - ERR_FAIL_INDEX((int)p_mode, 3); - axis_v = p_mode; - emit_changed(); -} - -StyleBoxTexture::AxisStretchMode StyleBoxTexture::get_v_axis_stretch_mode() const { - return axis_v; -} - -void StyleBoxTexture::set_modulate(const Color &p_modulate) { - if (modulate == p_modulate) { - return; - } - modulate = p_modulate; - emit_changed(); -} - -Color StyleBoxTexture::get_modulate() const { - return modulate; -} - -void StyleBoxTexture::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_texture", "texture"), &StyleBoxTexture::set_texture); - ClassDB::bind_method(D_METHOD("get_texture"), &StyleBoxTexture::get_texture); - - ClassDB::bind_method(D_METHOD("set_texture_margin", "margin", "size"), &StyleBoxTexture::set_texture_margin); - ClassDB::bind_method(D_METHOD("set_texture_margin_all", "size"), &StyleBoxTexture::set_texture_margin_all); - ClassDB::bind_method(D_METHOD("get_texture_margin", "margin"), &StyleBoxTexture::get_texture_margin); - - ClassDB::bind_method(D_METHOD("set_expand_margin", "margin", "size"), &StyleBoxTexture::set_expand_margin); - ClassDB::bind_method(D_METHOD("set_expand_margin_all", "size"), &StyleBoxTexture::set_expand_margin_all); - ClassDB::bind_method(D_METHOD("get_expand_margin", "margin"), &StyleBoxTexture::get_expand_margin); - - ClassDB::bind_method(D_METHOD("set_region_rect", "region"), &StyleBoxTexture::set_region_rect); - ClassDB::bind_method(D_METHOD("get_region_rect"), &StyleBoxTexture::get_region_rect); - - ClassDB::bind_method(D_METHOD("set_draw_center", "enable"), &StyleBoxTexture::set_draw_center); - ClassDB::bind_method(D_METHOD("is_draw_center_enabled"), &StyleBoxTexture::is_draw_center_enabled); - - ClassDB::bind_method(D_METHOD("set_modulate", "color"), &StyleBoxTexture::set_modulate); - ClassDB::bind_method(D_METHOD("get_modulate"), &StyleBoxTexture::get_modulate); - - ClassDB::bind_method(D_METHOD("set_h_axis_stretch_mode", "mode"), &StyleBoxTexture::set_h_axis_stretch_mode); - ClassDB::bind_method(D_METHOD("get_h_axis_stretch_mode"), &StyleBoxTexture::get_h_axis_stretch_mode); - - ClassDB::bind_method(D_METHOD("set_v_axis_stretch_mode", "mode"), &StyleBoxTexture::set_v_axis_stretch_mode); - ClassDB::bind_method(D_METHOD("get_v_axis_stretch_mode"), &StyleBoxTexture::get_v_axis_stretch_mode); - - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_texture", "get_texture"); - - ADD_GROUP("Texture Margins", "texture_margin_"); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "texture_margin_left", PROPERTY_HINT_RANGE, "0,2048,1,suffix:px"), "set_texture_margin", "get_texture_margin", SIDE_LEFT); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "texture_margin_top", PROPERTY_HINT_RANGE, "0,2048,1,suffix:px"), "set_texture_margin", "get_texture_margin", SIDE_TOP); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "texture_margin_right", PROPERTY_HINT_RANGE, "0,2048,1,suffix:px"), "set_texture_margin", "get_texture_margin", SIDE_RIGHT); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "texture_margin_bottom", PROPERTY_HINT_RANGE, "0,2048,1,suffix:px"), "set_texture_margin", "get_texture_margin", SIDE_BOTTOM); - - ADD_GROUP("Expand Margins", "expand_margin_"); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "expand_margin_left", PROPERTY_HINT_RANGE, "0,2048,1,suffix:px"), "set_expand_margin", "get_expand_margin", SIDE_LEFT); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "expand_margin_top", PROPERTY_HINT_RANGE, "0,2048,1,suffix:px"), "set_expand_margin", "get_expand_margin", SIDE_TOP); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "expand_margin_right", PROPERTY_HINT_RANGE, "0,2048,1,suffix:px"), "set_expand_margin", "get_expand_margin", SIDE_RIGHT); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "expand_margin_bottom", PROPERTY_HINT_RANGE, "0,2048,1,suffix:px"), "set_expand_margin", "get_expand_margin", SIDE_BOTTOM); - - ADD_GROUP("Axis Stretch", "axis_stretch_"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "axis_stretch_horizontal", PROPERTY_HINT_ENUM, "Stretch,Tile,Tile Fit"), "set_h_axis_stretch_mode", "get_h_axis_stretch_mode"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "axis_stretch_vertical", PROPERTY_HINT_ENUM, "Stretch,Tile,Tile Fit"), "set_v_axis_stretch_mode", "get_v_axis_stretch_mode"); - - ADD_GROUP("Sub-Region", "region_"); - ADD_PROPERTY(PropertyInfo(Variant::RECT2, "region_rect", PROPERTY_HINT_NONE, "suffix:px"), "set_region_rect", "get_region_rect"); - - ADD_GROUP("Modulate", "modulate_"); - ADD_PROPERTY(PropertyInfo(Variant::COLOR, "modulate_color"), "set_modulate", "get_modulate"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draw_center"), "set_draw_center", "is_draw_center_enabled"); - - BIND_ENUM_CONSTANT(AXIS_STRETCH_MODE_STRETCH); - BIND_ENUM_CONSTANT(AXIS_STRETCH_MODE_TILE); - BIND_ENUM_CONSTANT(AXIS_STRETCH_MODE_TILE_FIT); -} - -StyleBoxTexture::StyleBoxTexture() {} - -StyleBoxTexture::~StyleBoxTexture() {} - -//////////////// - -void StyleBoxFlat::set_bg_color(const Color &p_color) { - bg_color = p_color; - emit_changed(); -} - -Color StyleBoxFlat::get_bg_color() const { - return bg_color; -} - -void StyleBoxFlat::set_border_color(const Color &p_color) { - border_color = p_color; - emit_changed(); -} - -Color StyleBoxFlat::get_border_color() const { - return border_color; -} - -void StyleBoxFlat::set_border_width_all(int p_size) { - border_width[0] = p_size; - border_width[1] = p_size; - border_width[2] = p_size; - border_width[3] = p_size; - emit_changed(); -} - -int StyleBoxFlat::get_border_width_min() const { - return MIN(MIN(border_width[0], border_width[1]), MIN(border_width[2], border_width[3])); -} - -void StyleBoxFlat::set_border_width(Side p_side, int p_width) { - ERR_FAIL_INDEX((int)p_side, 4); - border_width[p_side] = p_width; - emit_changed(); -} - -int StyleBoxFlat::get_border_width(Side p_side) const { - ERR_FAIL_INDEX_V((int)p_side, 4, 0); - return border_width[p_side]; -} - -void StyleBoxFlat::set_border_blend(bool p_blend) { - blend_border = p_blend; - emit_changed(); -} - -bool StyleBoxFlat::get_border_blend() const { - return blend_border; -} - -void StyleBoxFlat::set_corner_radius_all(int radius) { - for (int i = 0; i < 4; i++) { - corner_radius[i] = radius; - } - - emit_changed(); -} - -void StyleBoxFlat::set_corner_radius_individual(const int radius_top_left, const int radius_top_right, const int radius_bottom_right, const int radius_bottom_left) { - corner_radius[0] = radius_top_left; - corner_radius[1] = radius_top_right; - corner_radius[2] = radius_bottom_right; - corner_radius[3] = radius_bottom_left; - - emit_changed(); -} - -void StyleBoxFlat::set_corner_radius(const Corner p_corner, const int radius) { - ERR_FAIL_INDEX((int)p_corner, 4); - corner_radius[p_corner] = radius; - emit_changed(); -} - -int StyleBoxFlat::get_corner_radius(const Corner p_corner) const { - ERR_FAIL_INDEX_V((int)p_corner, 4, 0); - return corner_radius[p_corner]; -} - -void StyleBoxFlat::set_expand_margin(Side p_side, float p_size) { - ERR_FAIL_INDEX((int)p_side, 4); - expand_margin[p_side] = p_size; - emit_changed(); -} - -void StyleBoxFlat::set_expand_margin_individual(float p_left, float p_top, float p_right, float p_bottom) { - expand_margin[SIDE_LEFT] = p_left; - expand_margin[SIDE_TOP] = p_top; - expand_margin[SIDE_RIGHT] = p_right; - expand_margin[SIDE_BOTTOM] = p_bottom; - emit_changed(); -} - -void StyleBoxFlat::set_expand_margin_all(float p_expand_margin_size) { - for (int i = 0; i < 4; i++) { - expand_margin[i] = p_expand_margin_size; - } - emit_changed(); -} - -float StyleBoxFlat::get_expand_margin(Side p_side) const { - ERR_FAIL_INDEX_V((int)p_side, 4, 0.0); - return expand_margin[p_side]; -} - -void StyleBoxFlat::set_draw_center(bool p_enabled) { - draw_center = p_enabled; - emit_changed(); -} - -bool StyleBoxFlat::is_draw_center_enabled() const { - return draw_center; -} - -void StyleBoxFlat::set_skew(Vector2 p_skew) { - skew = p_skew; - emit_changed(); -} - -Vector2 StyleBoxFlat::get_skew() const { - return skew; -} - -void StyleBoxFlat::set_shadow_color(const Color &p_color) { - shadow_color = p_color; - emit_changed(); -} - -Color StyleBoxFlat::get_shadow_color() const { - return shadow_color; -} - -void StyleBoxFlat::set_shadow_size(const int &p_size) { - shadow_size = p_size; - emit_changed(); -} - -int StyleBoxFlat::get_shadow_size() const { - return shadow_size; -} - -void StyleBoxFlat::set_shadow_offset(const Point2 &p_offset) { - shadow_offset = p_offset; - emit_changed(); -} - -Point2 StyleBoxFlat::get_shadow_offset() const { - return shadow_offset; -} - -void StyleBoxFlat::set_anti_aliased(const bool &p_anti_aliased) { - anti_aliased = p_anti_aliased; - emit_changed(); - notify_property_list_changed(); -} - -bool StyleBoxFlat::is_anti_aliased() const { - return anti_aliased; -} - -void StyleBoxFlat::set_aa_size(const real_t p_aa_size) { - aa_size = CLAMP(p_aa_size, 0.01, 10); - emit_changed(); -} - -real_t StyleBoxFlat::get_aa_size() const { - return aa_size; -} - -void StyleBoxFlat::set_corner_detail(const int &p_corner_detail) { - corner_detail = CLAMP(p_corner_detail, 1, 20); - emit_changed(); -} - -int StyleBoxFlat::get_corner_detail() const { - return corner_detail; -} - -inline void set_inner_corner_radius(const Rect2 style_rect, const Rect2 inner_rect, const real_t corner_radius[4], real_t *inner_corner_radius) { - real_t border_left = inner_rect.position.x - style_rect.position.x; - real_t border_top = inner_rect.position.y - style_rect.position.y; - real_t border_right = style_rect.size.width - inner_rect.size.width - border_left; - real_t border_bottom = style_rect.size.height - inner_rect.size.height - border_top; - - real_t rad; - - // Top left. - rad = MIN(border_top, border_left); - inner_corner_radius[0] = MAX(corner_radius[0] - rad, 0); - - // Top right; - rad = MIN(border_top, border_right); - inner_corner_radius[1] = MAX(corner_radius[1] - rad, 0); - - // Bottom right. - rad = MIN(border_bottom, border_right); - inner_corner_radius[2] = MAX(corner_radius[2] - rad, 0); - - // Bottom left. - rad = MIN(border_bottom, border_left); - inner_corner_radius[3] = MAX(corner_radius[3] - rad, 0); -} - -inline void draw_rounded_rectangle(Vector<Vector2> &verts, Vector<int> &indices, Vector<Color> &colors, const Rect2 &style_rect, const real_t corner_radius[4], - const Rect2 &ring_rect, const Rect2 &inner_rect, const Color &inner_color, const Color &outer_color, const int corner_detail, const Vector2 &skew, bool is_filled = false) { - int vert_offset = verts.size(); - if (!vert_offset) { - vert_offset = 0; - } - - int adapted_corner_detail = (corner_radius[0] == 0 && corner_radius[1] == 0 && corner_radius[2] == 0 && corner_radius[3] == 0) ? 1 : corner_detail; - - bool draw_border = !is_filled; - - real_t ring_corner_radius[4]; - set_inner_corner_radius(style_rect, ring_rect, corner_radius, ring_corner_radius); - - // Corner radius center points. - Vector<Point2> outer_points = { - ring_rect.position + Vector2(ring_corner_radius[0], ring_corner_radius[0]), //tl - Point2(ring_rect.position.x + ring_rect.size.x - ring_corner_radius[1], ring_rect.position.y + ring_corner_radius[1]), //tr - ring_rect.position + ring_rect.size - Vector2(ring_corner_radius[2], ring_corner_radius[2]), //br - Point2(ring_rect.position.x + ring_corner_radius[3], ring_rect.position.y + ring_rect.size.y - ring_corner_radius[3]) //bl - }; - - real_t inner_corner_radius[4]; - set_inner_corner_radius(style_rect, inner_rect, corner_radius, inner_corner_radius); - - Vector<Point2> inner_points = { - inner_rect.position + Vector2(inner_corner_radius[0], inner_corner_radius[0]), //tl - Point2(inner_rect.position.x + inner_rect.size.x - inner_corner_radius[1], inner_rect.position.y + inner_corner_radius[1]), //tr - inner_rect.position + inner_rect.size - Vector2(inner_corner_radius[2], inner_corner_radius[2]), //br - Point2(inner_rect.position.x + inner_corner_radius[3], inner_rect.position.y + inner_rect.size.y - inner_corner_radius[3]) //bl - }; - // Calculate the vertices. - - // If the center is filled, we do not draw the border and directly use the inner ring as reference. Because all calls to this - // method either draw a ring or a filled rounded rectangle, but not both. - int max_inner_outer = draw_border ? 2 : 1; - - for (int corner_index = 0; corner_index < 4; corner_index++) { - for (int detail = 0; detail <= adapted_corner_detail; detail++) { - for (int inner_outer = 0; inner_outer < max_inner_outer; inner_outer++) { - real_t radius; - Color color; - Point2 corner_point; - if (inner_outer == 0) { - radius = inner_corner_radius[corner_index]; - color = inner_color; - corner_point = inner_points[corner_index]; - } else { - radius = ring_corner_radius[corner_index]; - color = outer_color; - corner_point = outer_points[corner_index]; - } - - const real_t x = radius * (real_t)cos((corner_index + detail / (double)adapted_corner_detail) * (Math_TAU / 4.0) + Math_PI) + corner_point.x; - const real_t y = radius * (real_t)sin((corner_index + detail / (double)adapted_corner_detail) * (Math_TAU / 4.0) + Math_PI) + corner_point.y; - const float x_skew = -skew.x * (y - ring_rect.get_center().y); - const float y_skew = -skew.y * (x - ring_rect.get_center().x); - verts.push_back(Vector2(x + x_skew, y + y_skew)); - colors.push_back(color); - } - } - } - - int ring_vert_count = verts.size() - vert_offset; - - // Fill the indices and the colors for the border. - - if (draw_border) { - for (int i = 0; i < ring_vert_count; i++) { - indices.push_back(vert_offset + ((i + 0) % ring_vert_count)); - indices.push_back(vert_offset + ((i + 2) % ring_vert_count)); - indices.push_back(vert_offset + ((i + 1) % ring_vert_count)); - } - } - - if (is_filled) { - // Compute the triangles pattern to draw the rounded rectangle. - // Consists of vertical stripes of two triangles each. - - int stripes_count = ring_vert_count / 2 - 1; - int last_vert_id = ring_vert_count - 1; - - for (int i = 0; i < stripes_count; i++) { - // Polygon 1. - indices.push_back(vert_offset + i); - indices.push_back(vert_offset + last_vert_id - i - 1); - indices.push_back(vert_offset + i + 1); - // Polygon 2. - indices.push_back(vert_offset + i); - indices.push_back(vert_offset + last_vert_id - 0 - i); - indices.push_back(vert_offset + last_vert_id - 1 - i); - } - } -} - -inline void adapt_values(int p_index_a, int p_index_b, real_t *adapted_values, const real_t *p_values, const real_t p_width, const real_t p_max_a, const real_t p_max_b) { - if (p_values[p_index_a] + p_values[p_index_b] > p_width) { - real_t factor; - real_t new_value; - - factor = (real_t)p_width / (real_t)(p_values[p_index_a] + p_values[p_index_b]); - - new_value = (p_values[p_index_a] * factor); - if (new_value < adapted_values[p_index_a]) { - adapted_values[p_index_a] = new_value; - } - new_value = (p_values[p_index_b] * factor); - if (new_value < adapted_values[p_index_b]) { - adapted_values[p_index_b] = new_value; - } - } else { - adapted_values[p_index_a] = MIN(p_values[p_index_a], adapted_values[p_index_a]); - adapted_values[p_index_b] = MIN(p_values[p_index_b], adapted_values[p_index_b]); - } - adapted_values[p_index_a] = MIN(p_max_a, adapted_values[p_index_a]); - adapted_values[p_index_b] = MIN(p_max_b, adapted_values[p_index_b]); -} - -Rect2 StyleBoxFlat::get_draw_rect(const Rect2 &p_rect) const { - Rect2 draw_rect = p_rect.grow_individual(expand_margin[SIDE_LEFT], expand_margin[SIDE_TOP], expand_margin[SIDE_RIGHT], expand_margin[SIDE_BOTTOM]); - - if (shadow_size > 0) { - Rect2 shadow_rect = draw_rect.grow(shadow_size); - shadow_rect.position += shadow_offset; - draw_rect = draw_rect.merge(shadow_rect); - } - - return draw_rect; -} - -void StyleBoxFlat::draw(RID p_canvas_item, const Rect2 &p_rect) const { - bool draw_border = (border_width[0] > 0) || (border_width[1] > 0) || (border_width[2] > 0) || (border_width[3] > 0); - bool draw_shadow = (shadow_size > 0); - if (!draw_border && !draw_center && !draw_shadow) { - return; - } - - Rect2 style_rect = p_rect.grow_individual(expand_margin[SIDE_LEFT], expand_margin[SIDE_TOP], expand_margin[SIDE_RIGHT], expand_margin[SIDE_BOTTOM]); - if (Math::is_zero_approx(style_rect.size.width) || Math::is_zero_approx(style_rect.size.height)) { - return; - } - - const bool rounded_corners = (corner_radius[0] > 0) || (corner_radius[1] > 0) || (corner_radius[2] > 0) || (corner_radius[3] > 0); - // Only enable antialiasing if it is actually needed. This improve performances - // and maximizes sharpness for non-skewed StyleBoxes with sharp corners. - const bool aa_on = (rounded_corners || !skew.is_zero_approx()) && anti_aliased; - - const bool blend_on = blend_border && draw_border; - - Color border_color_alpha = Color(border_color.r, border_color.g, border_color.b, 0); - Color border_color_blend = (draw_center ? bg_color : border_color_alpha); - Color border_color_inner = blend_on ? border_color_blend : border_color; - - // Adapt borders (prevent weird overlapping/glitchy drawings). - real_t width = MAX(style_rect.size.width, 0); - real_t height = MAX(style_rect.size.height, 0); - real_t adapted_border[4] = { 1000000.0, 1000000.0, 1000000.0, 1000000.0 }; - adapt_values(SIDE_TOP, SIDE_BOTTOM, adapted_border, border_width, height, height, height); - adapt_values(SIDE_LEFT, SIDE_RIGHT, adapted_border, border_width, width, width, width); - - // Adapt corners (prevent weird overlapping/glitchy drawings). - real_t adapted_corner[4] = { 1000000.0, 1000000.0, 1000000.0, 1000000.0 }; - adapt_values(CORNER_TOP_RIGHT, CORNER_BOTTOM_RIGHT, adapted_corner, corner_radius, height, height - adapted_border[SIDE_BOTTOM], height - adapted_border[SIDE_TOP]); - adapt_values(CORNER_TOP_LEFT, CORNER_BOTTOM_LEFT, adapted_corner, corner_radius, height, height - adapted_border[SIDE_BOTTOM], height - adapted_border[SIDE_TOP]); - adapt_values(CORNER_TOP_LEFT, CORNER_TOP_RIGHT, adapted_corner, corner_radius, width, width - adapted_border[SIDE_RIGHT], width - adapted_border[SIDE_LEFT]); - adapt_values(CORNER_BOTTOM_LEFT, CORNER_BOTTOM_RIGHT, adapted_corner, corner_radius, width, width - adapted_border[SIDE_RIGHT], width - adapted_border[SIDE_LEFT]); - - Rect2 infill_rect = style_rect.grow_individual(-adapted_border[SIDE_LEFT], -adapted_border[SIDE_TOP], -adapted_border[SIDE_RIGHT], -adapted_border[SIDE_BOTTOM]); - - Rect2 border_style_rect = style_rect; - if (aa_on) { - for (int i = 0; i < 4; i++) { - if (border_width[i] > 0) { - border_style_rect = border_style_rect.grow_side((Side)i, -aa_size); - } - } - } - - Vector<Point2> verts; - Vector<int> indices; - Vector<Color> colors; - Vector<Point2> uvs; - - // Create shadow - if (draw_shadow) { - Rect2 shadow_inner_rect = style_rect; - shadow_inner_rect.position += shadow_offset; - - Rect2 shadow_rect = style_rect.grow(shadow_size); - shadow_rect.position += shadow_offset; - - Color shadow_color_transparent = Color(shadow_color.r, shadow_color.g, shadow_color.b, 0); - - draw_rounded_rectangle(verts, indices, colors, shadow_inner_rect, adapted_corner, - shadow_rect, shadow_inner_rect, shadow_color, shadow_color_transparent, corner_detail, skew); - - if (draw_center) { - draw_rounded_rectangle(verts, indices, colors, shadow_inner_rect, adapted_corner, - shadow_inner_rect, shadow_inner_rect, shadow_color, shadow_color, corner_detail, skew, true); - } - } - - // Create border (no AA). - if (draw_border && !aa_on) { - draw_rounded_rectangle(verts, indices, colors, border_style_rect, adapted_corner, - border_style_rect, infill_rect, border_color_inner, border_color, corner_detail, skew); - } - - // Create infill (no AA). - if (draw_center && (!aa_on || blend_on)) { - draw_rounded_rectangle(verts, indices, colors, border_style_rect, adapted_corner, - infill_rect, infill_rect, bg_color, bg_color, corner_detail, skew, true); - } - - if (aa_on) { - real_t aa_border_width[4]; - real_t aa_border_width_half[4]; - real_t aa_fill_width[4]; - real_t aa_fill_width_half[4]; - if (draw_border) { - for (int i = 0; i < 4; i++) { - if (border_width[i] > 0) { - aa_border_width[i] = aa_size; - aa_border_width_half[i] = aa_size / 2; - aa_fill_width[i] = 0; - aa_fill_width_half[i] = 0; - } else { - aa_border_width[i] = 0; - aa_border_width_half[i] = 0; - aa_fill_width[i] = aa_size; - aa_fill_width_half[i] = aa_size / 2; - } - } - } else { - for (int i = 0; i < 4; i++) { - aa_border_width[i] = 0; - aa_border_width_half[i] = 0; - aa_fill_width[i] = aa_size; - aa_fill_width_half[i] = aa_size / 2; - } - } - - if (draw_center) { - // Infill rect, transparent side of antialiasing gradient (base infill rect enlarged by AA size) - Rect2 infill_rect_aa_transparent = infill_rect.grow_individual(aa_fill_width_half[SIDE_LEFT], aa_fill_width_half[SIDE_TOP], - aa_fill_width_half[SIDE_RIGHT], aa_fill_width_half[SIDE_BOTTOM]); - // Infill rect, colored side of antialiasing gradient (base infill rect shrunk by AA size) - Rect2 infill_rect_aa_colored = infill_rect_aa_transparent.grow_individual(-aa_fill_width[SIDE_LEFT], -aa_fill_width[SIDE_TOP], - -aa_fill_width[SIDE_RIGHT], -aa_fill_width[SIDE_BOTTOM]); - if (!blend_on) { - // Create center fill, not antialiased yet - draw_rounded_rectangle(verts, indices, colors, border_style_rect, adapted_corner, - infill_rect_aa_colored, infill_rect_aa_colored, bg_color, bg_color, corner_detail, skew, true); - } - if (!blend_on || !draw_border) { - Color alpha_bg = Color(bg_color.r, bg_color.g, bg_color.b, 0); - // Add antialiasing on the center fill - draw_rounded_rectangle(verts, indices, colors, border_style_rect, adapted_corner, - infill_rect_aa_transparent, infill_rect_aa_colored, bg_color, alpha_bg, corner_detail, skew); - } - } - - if (draw_border) { - // Inner border recct, fully colored side of antialiasing gradient (base inner rect enlarged by AA size) - Rect2 inner_rect_aa_colored = infill_rect.grow_individual(aa_border_width_half[SIDE_LEFT], aa_border_width_half[SIDE_TOP], - aa_border_width_half[SIDE_RIGHT], aa_border_width_half[SIDE_BOTTOM]); - // Inner border rect, transparent side of antialiasing gradient (base inner rect shrunk by AA size) - Rect2 inner_rect_aa_transparent = inner_rect_aa_colored.grow_individual(-aa_border_width[SIDE_LEFT], -aa_border_width[SIDE_TOP], - -aa_border_width[SIDE_RIGHT], -aa_border_width[SIDE_BOTTOM]); - // Outer border rect, transparent side of antialiasing gradient (base outer rect enlarged by AA size) - Rect2 outer_rect_aa_transparent = style_rect.grow_individual(aa_border_width_half[SIDE_LEFT], aa_border_width_half[SIDE_TOP], - aa_border_width_half[SIDE_RIGHT], aa_border_width_half[SIDE_BOTTOM]); - // Outer border rect, colored side of antialiasing gradient (base outer rect shrunk by AA size) - Rect2 outer_rect_aa_colored = border_style_rect.grow_individual(aa_border_width_half[SIDE_LEFT], aa_border_width_half[SIDE_TOP], - aa_border_width_half[SIDE_RIGHT], aa_border_width_half[SIDE_BOTTOM]); - - // Create border ring, not antialiased yet - draw_rounded_rectangle(verts, indices, colors, border_style_rect, adapted_corner, - outer_rect_aa_colored, ((blend_on) ? infill_rect : inner_rect_aa_colored), border_color_inner, border_color, corner_detail, skew); - if (!blend_on) { - // Add antialiasing on the ring inner border - draw_rounded_rectangle(verts, indices, colors, border_style_rect, adapted_corner, - inner_rect_aa_colored, inner_rect_aa_transparent, border_color_blend, border_color, corner_detail, skew); - } - // Add antialiasing on the ring outer border - draw_rounded_rectangle(verts, indices, colors, border_style_rect, adapted_corner, - outer_rect_aa_transparent, outer_rect_aa_colored, border_color, border_color_alpha, corner_detail, skew); - } - } - - // Compute UV coordinates. - Rect2 uv_rect = style_rect.grow(aa_on ? aa_size : 0); - uvs.resize(verts.size()); - for (int i = 0; i < verts.size(); i++) { - uvs.write[i].x = (verts[i].x - uv_rect.position.x) / uv_rect.size.width; - uvs.write[i].y = (verts[i].y - uv_rect.position.y) / uv_rect.size.height; - } - - // Draw stylebox. - RenderingServer *vs = RenderingServer::get_singleton(); - vs->canvas_item_add_triangle_array(p_canvas_item, indices, verts, colors, uvs); -} - -float StyleBoxFlat::get_style_margin(Side p_side) const { - ERR_FAIL_INDEX_V((int)p_side, 4, 0.0); - return border_width[p_side]; -} - -void StyleBoxFlat::_validate_property(PropertyInfo &p_property) const { - if (!anti_aliased && p_property.name == "anti_aliasing_size") { - p_property.usage = PROPERTY_USAGE_NO_EDITOR; - } -} - -void StyleBoxFlat::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_bg_color", "color"), &StyleBoxFlat::set_bg_color); - ClassDB::bind_method(D_METHOD("get_bg_color"), &StyleBoxFlat::get_bg_color); - - ClassDB::bind_method(D_METHOD("set_border_color", "color"), &StyleBoxFlat::set_border_color); - ClassDB::bind_method(D_METHOD("get_border_color"), &StyleBoxFlat::get_border_color); - - ClassDB::bind_method(D_METHOD("set_border_width_all", "width"), &StyleBoxFlat::set_border_width_all); - ClassDB::bind_method(D_METHOD("get_border_width_min"), &StyleBoxFlat::get_border_width_min); - - ClassDB::bind_method(D_METHOD("set_border_width", "margin", "width"), &StyleBoxFlat::set_border_width); - ClassDB::bind_method(D_METHOD("get_border_width", "margin"), &StyleBoxFlat::get_border_width); - - ClassDB::bind_method(D_METHOD("set_border_blend", "blend"), &StyleBoxFlat::set_border_blend); - ClassDB::bind_method(D_METHOD("get_border_blend"), &StyleBoxFlat::get_border_blend); - - ClassDB::bind_method(D_METHOD("set_corner_radius_all", "radius"), &StyleBoxFlat::set_corner_radius_all); - - ClassDB::bind_method(D_METHOD("set_corner_radius", "corner", "radius"), &StyleBoxFlat::set_corner_radius); - ClassDB::bind_method(D_METHOD("get_corner_radius", "corner"), &StyleBoxFlat::get_corner_radius); - - ClassDB::bind_method(D_METHOD("set_expand_margin", "margin", "size"), &StyleBoxFlat::set_expand_margin); - ClassDB::bind_method(D_METHOD("set_expand_margin_all", "size"), &StyleBoxFlat::set_expand_margin_all); - ClassDB::bind_method(D_METHOD("get_expand_margin", "margin"), &StyleBoxFlat::get_expand_margin); - - ClassDB::bind_method(D_METHOD("set_draw_center", "draw_center"), &StyleBoxFlat::set_draw_center); - ClassDB::bind_method(D_METHOD("is_draw_center_enabled"), &StyleBoxFlat::is_draw_center_enabled); - - ClassDB::bind_method(D_METHOD("set_skew", "skew"), &StyleBoxFlat::set_skew); - ClassDB::bind_method(D_METHOD("get_skew"), &StyleBoxFlat::get_skew); - - ClassDB::bind_method(D_METHOD("set_shadow_color", "color"), &StyleBoxFlat::set_shadow_color); - ClassDB::bind_method(D_METHOD("get_shadow_color"), &StyleBoxFlat::get_shadow_color); - - ClassDB::bind_method(D_METHOD("set_shadow_size", "size"), &StyleBoxFlat::set_shadow_size); - ClassDB::bind_method(D_METHOD("get_shadow_size"), &StyleBoxFlat::get_shadow_size); - - ClassDB::bind_method(D_METHOD("set_shadow_offset", "offset"), &StyleBoxFlat::set_shadow_offset); - ClassDB::bind_method(D_METHOD("get_shadow_offset"), &StyleBoxFlat::get_shadow_offset); - - ClassDB::bind_method(D_METHOD("set_anti_aliased", "anti_aliased"), &StyleBoxFlat::set_anti_aliased); - ClassDB::bind_method(D_METHOD("is_anti_aliased"), &StyleBoxFlat::is_anti_aliased); - - ClassDB::bind_method(D_METHOD("set_aa_size", "size"), &StyleBoxFlat::set_aa_size); - ClassDB::bind_method(D_METHOD("get_aa_size"), &StyleBoxFlat::get_aa_size); - - ClassDB::bind_method(D_METHOD("set_corner_detail", "detail"), &StyleBoxFlat::set_corner_detail); - ClassDB::bind_method(D_METHOD("get_corner_detail"), &StyleBoxFlat::get_corner_detail); - - ADD_PROPERTY(PropertyInfo(Variant::COLOR, "bg_color"), "set_bg_color", "get_bg_color"); - - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draw_center"), "set_draw_center", "is_draw_center_enabled"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "skew"), "set_skew", "get_skew"); - - ADD_GROUP("Border Width", "border_width_"); - ADD_PROPERTYI(PropertyInfo(Variant::INT, "border_width_left", PROPERTY_HINT_RANGE, "0,1024,1,suffix:px"), "set_border_width", "get_border_width", SIDE_LEFT); - ADD_PROPERTYI(PropertyInfo(Variant::INT, "border_width_top", PROPERTY_HINT_RANGE, "0,1024,1,suffix:px"), "set_border_width", "get_border_width", SIDE_TOP); - ADD_PROPERTYI(PropertyInfo(Variant::INT, "border_width_right", PROPERTY_HINT_RANGE, "0,1024,1,suffix:px"), "set_border_width", "get_border_width", SIDE_RIGHT); - ADD_PROPERTYI(PropertyInfo(Variant::INT, "border_width_bottom", PROPERTY_HINT_RANGE, "0,1024,1,suffix:px"), "set_border_width", "get_border_width", SIDE_BOTTOM); - - ADD_GROUP("Border", "border_"); - ADD_PROPERTY(PropertyInfo(Variant::COLOR, "border_color"), "set_border_color", "get_border_color"); - - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "border_blend"), "set_border_blend", "get_border_blend"); - - ADD_GROUP("Corner Radius", "corner_radius_"); - ADD_PROPERTYI(PropertyInfo(Variant::INT, "corner_radius_top_left", PROPERTY_HINT_RANGE, "0,1024,1,suffix:px"), "set_corner_radius", "get_corner_radius", CORNER_TOP_LEFT); - ADD_PROPERTYI(PropertyInfo(Variant::INT, "corner_radius_top_right", PROPERTY_HINT_RANGE, "0,1024,1,suffix:px"), "set_corner_radius", "get_corner_radius", CORNER_TOP_RIGHT); - ADD_PROPERTYI(PropertyInfo(Variant::INT, "corner_radius_bottom_right", PROPERTY_HINT_RANGE, "0,1024,1,suffix:px"), "set_corner_radius", "get_corner_radius", CORNER_BOTTOM_RIGHT); - ADD_PROPERTYI(PropertyInfo(Variant::INT, "corner_radius_bottom_left", PROPERTY_HINT_RANGE, "0,1024,1,suffix:px"), "set_corner_radius", "get_corner_radius", CORNER_BOTTOM_LEFT); - - ADD_PROPERTY(PropertyInfo(Variant::INT, "corner_detail", PROPERTY_HINT_RANGE, "1,20,1"), "set_corner_detail", "get_corner_detail"); - - ADD_GROUP("Expand Margins", "expand_margin_"); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "expand_margin_left", PROPERTY_HINT_RANGE, "0,2048,1,suffix:px"), "set_expand_margin", "get_expand_margin", SIDE_LEFT); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "expand_margin_top", PROPERTY_HINT_RANGE, "0,2048,1,suffix:px"), "set_expand_margin", "get_expand_margin", SIDE_TOP); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "expand_margin_right", PROPERTY_HINT_RANGE, "0,2048,1,suffix:px"), "set_expand_margin", "get_expand_margin", SIDE_RIGHT); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "expand_margin_bottom", PROPERTY_HINT_RANGE, "0,2048,1,suffix:px"), "set_expand_margin", "get_expand_margin", SIDE_BOTTOM); - - ADD_GROUP("Shadow", "shadow_"); - ADD_PROPERTY(PropertyInfo(Variant::COLOR, "shadow_color"), "set_shadow_color", "get_shadow_color"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "shadow_size", PROPERTY_HINT_RANGE, "0,100,1,or_greater,suffix:px"), "set_shadow_size", "get_shadow_size"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "shadow_offset", PROPERTY_HINT_NONE, "suffix:px"), "set_shadow_offset", "get_shadow_offset"); - - ADD_GROUP("Anti Aliasing", "anti_aliasing_"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "anti_aliasing"), "set_anti_aliased", "is_anti_aliased"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "anti_aliasing_size", PROPERTY_HINT_RANGE, "0.01,10,0.001,suffix:px"), "set_aa_size", "get_aa_size"); -} - -StyleBoxFlat::StyleBoxFlat() {} - -StyleBoxFlat::~StyleBoxFlat() {} - -void StyleBoxLine::set_color(const Color &p_color) { - color = p_color; - emit_changed(); -} - -Color StyleBoxLine::get_color() const { - return color; -} - -void StyleBoxLine::set_thickness(int p_thickness) { - thickness = p_thickness; - emit_changed(); -} - -int StyleBoxLine::get_thickness() const { - return thickness; -} - -void StyleBoxLine::set_vertical(bool p_vertical) { - vertical = p_vertical; - emit_changed(); -} - -bool StyleBoxLine::is_vertical() const { - return vertical; -} - -void StyleBoxLine::set_grow_end(float p_grow_end) { - grow_end = p_grow_end; - emit_changed(); -} - -float StyleBoxLine::get_grow_end() const { - return grow_end; -} - -void StyleBoxLine::set_grow_begin(float p_grow_begin) { - grow_begin = p_grow_begin; - emit_changed(); -} - -float StyleBoxLine::get_grow_begin() const { - return grow_begin; -} - -void StyleBoxLine::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_color", "color"), &StyleBoxLine::set_color); - ClassDB::bind_method(D_METHOD("get_color"), &StyleBoxLine::get_color); - ClassDB::bind_method(D_METHOD("set_thickness", "thickness"), &StyleBoxLine::set_thickness); - ClassDB::bind_method(D_METHOD("get_thickness"), &StyleBoxLine::get_thickness); - ClassDB::bind_method(D_METHOD("set_grow_begin", "offset"), &StyleBoxLine::set_grow_begin); - ClassDB::bind_method(D_METHOD("get_grow_begin"), &StyleBoxLine::get_grow_begin); - ClassDB::bind_method(D_METHOD("set_grow_end", "offset"), &StyleBoxLine::set_grow_end); - ClassDB::bind_method(D_METHOD("get_grow_end"), &StyleBoxLine::get_grow_end); - ClassDB::bind_method(D_METHOD("set_vertical", "vertical"), &StyleBoxLine::set_vertical); - ClassDB::bind_method(D_METHOD("is_vertical"), &StyleBoxLine::is_vertical); - - ADD_PROPERTY(PropertyInfo(Variant::COLOR, "color"), "set_color", "get_color"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "grow_begin", PROPERTY_HINT_RANGE, "-300,300,1,suffix:px"), "set_grow_begin", "get_grow_begin"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "grow_end", PROPERTY_HINT_RANGE, "-300,300,1,suffix:px"), "set_grow_end", "get_grow_end"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "thickness", PROPERTY_HINT_RANGE, "0,100,suffix:px"), "set_thickness", "get_thickness"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "vertical"), "set_vertical", "is_vertical"); -} - -float StyleBoxLine::get_style_margin(Side p_side) const { - ERR_FAIL_INDEX_V((int)p_side, 4, 0); - - if (vertical) { - if (p_side == SIDE_LEFT || p_side == SIDE_RIGHT) { - return thickness / 2.0; - } - } else if (p_side == SIDE_TOP || p_side == SIDE_BOTTOM) { - return thickness / 2.0; - } - - return 0; -} - -void StyleBoxLine::draw(RID p_canvas_item, const Rect2 &p_rect) const { - RenderingServer *vs = RenderingServer::get_singleton(); - Rect2i r = p_rect; - - if (vertical) { - r.position.y -= grow_begin; - r.size.y += (grow_begin + grow_end); - r.size.x = thickness; - } else { - r.position.x -= grow_begin; - r.size.x += (grow_begin + grow_end); - r.size.y = thickness; - } - - vs->canvas_item_add_rect(p_canvas_item, r, color); -} - -StyleBoxLine::StyleBoxLine() {} - -StyleBoxLine::~StyleBoxLine() {} diff --git a/scene/resources/style_box.h b/scene/resources/style_box.h index 9d96e9a3b7..3f9a96be2f 100644 --- a/scene/resources/style_box.h +++ b/scene/resources/style_box.h @@ -32,8 +32,9 @@ #define STYLE_BOX_H #include "core/io/resource.h" -#include "scene/resources/texture.h" -#include "servers/rendering_server.h" +#include "core/object/class_db.h" +#include "core/object/gdvirtual.gen.inc" +#include "core/object/script_language.h" class CanvasItem; @@ -41,11 +42,12 @@ class StyleBox : public Resource { GDCLASS(StyleBox, Resource); RES_BASE_EXTENSION("stylebox"); OBJ_SAVE_TYPE(StyleBox); + float content_margin[4]; protected: - virtual float get_style_margin(Side p_side) const { return 0; } static void _bind_methods(); + virtual float get_style_margin(Side p_side) const { return 0; } GDVIRTUAL2C(_draw, RID, Rect2) GDVIRTUAL1RC(Rect2, _get_draw_rect, Rect2) @@ -82,184 +84,4 @@ public: StyleBoxEmpty() {} }; -class StyleBoxTexture : public StyleBox { - GDCLASS(StyleBoxTexture, StyleBox); - -public: - enum AxisStretchMode { - AXIS_STRETCH_MODE_STRETCH, - AXIS_STRETCH_MODE_TILE, - AXIS_STRETCH_MODE_TILE_FIT, - }; - -private: - float expand_margin[4] = {}; - float texture_margin[4] = {}; - Rect2 region_rect; - Ref<Texture2D> texture; - bool draw_center = true; - Color modulate = Color(1, 1, 1, 1); - AxisStretchMode axis_h = AXIS_STRETCH_MODE_STRETCH; - AxisStretchMode axis_v = AXIS_STRETCH_MODE_STRETCH; - -protected: - virtual float get_style_margin(Side p_side) const override; - static void _bind_methods(); - -public: - void set_expand_margin(Side p_expand_side, float p_size); - void set_expand_margin_all(float p_expand_margin_size); - void set_expand_margin_individual(float p_left, float p_top, float p_right, float p_bottom); - float get_expand_margin(Side p_expand_side) const; - - void set_texture_margin(Side p_side, float p_size); - void set_texture_margin_all(float p_size); - void set_texture_margin_individual(float p_left, float p_top, float p_right, float p_bottom); - float get_texture_margin(Side p_side) const; - - void set_region_rect(const Rect2 &p_region_rect); - Rect2 get_region_rect() const; - - void set_texture(Ref<Texture2D> p_texture); - Ref<Texture2D> get_texture() const; - - void set_draw_center(bool p_enabled); - bool is_draw_center_enabled() const; - - void set_h_axis_stretch_mode(AxisStretchMode p_mode); - AxisStretchMode get_h_axis_stretch_mode() const; - - void set_v_axis_stretch_mode(AxisStretchMode p_mode); - AxisStretchMode get_v_axis_stretch_mode() const; - - void set_modulate(const Color &p_modulate); - Color get_modulate() const; - - virtual Rect2 get_draw_rect(const Rect2 &p_rect) const override; - virtual void draw(RID p_canvas_item, const Rect2 &p_rect) const override; - - StyleBoxTexture(); - ~StyleBoxTexture(); -}; - -VARIANT_ENUM_CAST(StyleBoxTexture::AxisStretchMode) - -class StyleBoxFlat : public StyleBox { - GDCLASS(StyleBoxFlat, StyleBox); - - Color bg_color = Color(0.6, 0.6, 0.6); - Color shadow_color = Color(0, 0, 0, 0.6); - Color border_color = Color(0.8, 0.8, 0.8); - - real_t border_width[4] = {}; - real_t expand_margin[4] = {}; - real_t corner_radius[4] = {}; - - bool draw_center = true; - bool blend_border = false; - Vector2 skew; - bool anti_aliased = true; - - int corner_detail = 8; - int shadow_size = 0; - Point2 shadow_offset; - real_t aa_size = 1; - -protected: - virtual float get_style_margin(Side p_side) const override; - static void _bind_methods(); - void _validate_property(PropertyInfo &p_property) const; - -public: - void set_bg_color(const Color &p_color); - Color get_bg_color() const; - - void set_border_color(const Color &p_color); - Color get_border_color() const; - - void set_border_width_all(int p_size); - int get_border_width_min() const; - - void set_border_width(Side p_side, int p_width); - int get_border_width(Side p_side) const; - - void set_border_blend(bool p_blend); - bool get_border_blend() const; - - void set_corner_radius_all(int radius); - void set_corner_radius_individual(const int radius_top_left, const int radius_top_right, const int radius_bottom_right, const int radius_bottom_left); - - void set_corner_radius(Corner p_corner, const int radius); - int get_corner_radius(Corner p_corner) const; - - void set_corner_detail(const int &p_corner_detail); - int get_corner_detail() const; - - void set_expand_margin(Side p_expand_side, float p_size); - void set_expand_margin_all(float p_expand_margin_size); - void set_expand_margin_individual(float p_left, float p_top, float p_right, float p_bottom); - float get_expand_margin(Side p_expand_side) const; - - void set_draw_center(bool p_enabled); - bool is_draw_center_enabled() const; - - void set_skew(Vector2 p_skew); - Vector2 get_skew() const; - - void set_shadow_color(const Color &p_color); - Color get_shadow_color() const; - - void set_shadow_size(const int &p_size); - int get_shadow_size() const; - - void set_shadow_offset(const Point2 &p_offset); - Point2 get_shadow_offset() const; - - void set_anti_aliased(const bool &p_anti_aliased); - bool is_anti_aliased() const; - void set_aa_size(const real_t p_aa_size); - real_t get_aa_size() const; - - virtual Rect2 get_draw_rect(const Rect2 &p_rect) const override; - virtual void draw(RID p_canvas_item, const Rect2 &p_rect) const override; - - StyleBoxFlat(); - ~StyleBoxFlat(); -}; - -// Just used to draw lines. -class StyleBoxLine : public StyleBox { - GDCLASS(StyleBoxLine, StyleBox); - Color color; - int thickness = 1; - bool vertical = false; - float grow_begin = 1.0; - float grow_end = 1.0; - -protected: - virtual float get_style_margin(Side p_side) const override; - static void _bind_methods(); - -public: - void set_color(const Color &p_color); - Color get_color() const; - - void set_thickness(int p_thickness); - int get_thickness() const; - - void set_vertical(bool p_vertical); - bool is_vertical() const; - - void set_grow_begin(float p_grow); - float get_grow_begin() const; - - void set_grow_end(float p_grow); - float get_grow_end() const; - - virtual void draw(RID p_canvas_item, const Rect2 &p_rect) const override; - - StyleBoxLine(); - ~StyleBoxLine(); -}; - #endif // STYLE_BOX_H diff --git a/scene/resources/style_box_flat.cpp b/scene/resources/style_box_flat.cpp new file mode 100644 index 0000000000..52d02e92cb --- /dev/null +++ b/scene/resources/style_box_flat.cpp @@ -0,0 +1,642 @@ +/**************************************************************************/ +/* style_box_flat.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "style_box_flat.h" + +#include "servers/rendering_server.h" + +float StyleBoxFlat::get_style_margin(Side p_side) const { + ERR_FAIL_INDEX_V((int)p_side, 4, 0.0); + return border_width[p_side]; +} + +void StyleBoxFlat::_validate_property(PropertyInfo &p_property) const { + if (!anti_aliased && p_property.name == "anti_aliasing_size") { + p_property.usage = PROPERTY_USAGE_NO_EDITOR; + } +} + +void StyleBoxFlat::set_bg_color(const Color &p_color) { + bg_color = p_color; + emit_changed(); +} + +Color StyleBoxFlat::get_bg_color() const { + return bg_color; +} + +void StyleBoxFlat::set_border_color(const Color &p_color) { + border_color = p_color; + emit_changed(); +} + +Color StyleBoxFlat::get_border_color() const { + return border_color; +} + +void StyleBoxFlat::set_border_width_all(int p_size) { + border_width[0] = p_size; + border_width[1] = p_size; + border_width[2] = p_size; + border_width[3] = p_size; + emit_changed(); +} + +int StyleBoxFlat::get_border_width_min() const { + return MIN(MIN(border_width[0], border_width[1]), MIN(border_width[2], border_width[3])); +} + +void StyleBoxFlat::set_border_width(Side p_side, int p_width) { + ERR_FAIL_INDEX((int)p_side, 4); + border_width[p_side] = p_width; + emit_changed(); +} + +int StyleBoxFlat::get_border_width(Side p_side) const { + ERR_FAIL_INDEX_V((int)p_side, 4, 0); + return border_width[p_side]; +} + +void StyleBoxFlat::set_border_blend(bool p_blend) { + blend_border = p_blend; + emit_changed(); +} + +bool StyleBoxFlat::get_border_blend() const { + return blend_border; +} + +void StyleBoxFlat::set_corner_radius(const Corner p_corner, const int radius) { + ERR_FAIL_INDEX((int)p_corner, 4); + corner_radius[p_corner] = radius; + emit_changed(); +} + +void StyleBoxFlat::set_corner_radius_all(int radius) { + for (int i = 0; i < 4; i++) { + corner_radius[i] = radius; + } + + emit_changed(); +} + +void StyleBoxFlat::set_corner_radius_individual(const int radius_top_left, const int radius_top_right, const int radius_bottom_right, const int radius_bottom_left) { + corner_radius[0] = radius_top_left; + corner_radius[1] = radius_top_right; + corner_radius[2] = radius_bottom_right; + corner_radius[3] = radius_bottom_left; + + emit_changed(); +} + +int StyleBoxFlat::get_corner_radius(const Corner p_corner) const { + ERR_FAIL_INDEX_V((int)p_corner, 4, 0); + return corner_radius[p_corner]; +} + +void StyleBoxFlat::set_corner_detail(const int &p_corner_detail) { + corner_detail = CLAMP(p_corner_detail, 1, 20); + emit_changed(); +} + +int StyleBoxFlat::get_corner_detail() const { + return corner_detail; +} + +void StyleBoxFlat::set_expand_margin(Side p_side, float p_size) { + ERR_FAIL_INDEX((int)p_side, 4); + expand_margin[p_side] = p_size; + emit_changed(); +} + +void StyleBoxFlat::set_expand_margin_all(float p_expand_margin_size) { + for (int i = 0; i < 4; i++) { + expand_margin[i] = p_expand_margin_size; + } + emit_changed(); +} + +void StyleBoxFlat::set_expand_margin_individual(float p_left, float p_top, float p_right, float p_bottom) { + expand_margin[SIDE_LEFT] = p_left; + expand_margin[SIDE_TOP] = p_top; + expand_margin[SIDE_RIGHT] = p_right; + expand_margin[SIDE_BOTTOM] = p_bottom; + emit_changed(); +} + +float StyleBoxFlat::get_expand_margin(Side p_side) const { + ERR_FAIL_INDEX_V((int)p_side, 4, 0.0); + return expand_margin[p_side]; +} + +void StyleBoxFlat::set_draw_center(bool p_enabled) { + draw_center = p_enabled; + emit_changed(); +} + +bool StyleBoxFlat::is_draw_center_enabled() const { + return draw_center; +} + +void StyleBoxFlat::set_skew(Vector2 p_skew) { + skew = p_skew; + emit_changed(); +} + +Vector2 StyleBoxFlat::get_skew() const { + return skew; +} + +void StyleBoxFlat::set_shadow_color(const Color &p_color) { + shadow_color = p_color; + emit_changed(); +} + +Color StyleBoxFlat::get_shadow_color() const { + return shadow_color; +} + +void StyleBoxFlat::set_shadow_size(const int &p_size) { + shadow_size = p_size; + emit_changed(); +} + +int StyleBoxFlat::get_shadow_size() const { + return shadow_size; +} + +void StyleBoxFlat::set_shadow_offset(const Point2 &p_offset) { + shadow_offset = p_offset; + emit_changed(); +} + +Point2 StyleBoxFlat::get_shadow_offset() const { + return shadow_offset; +} + +void StyleBoxFlat::set_anti_aliased(const bool &p_anti_aliased) { + anti_aliased = p_anti_aliased; + emit_changed(); + notify_property_list_changed(); +} + +bool StyleBoxFlat::is_anti_aliased() const { + return anti_aliased; +} + +void StyleBoxFlat::set_aa_size(const real_t p_aa_size) { + aa_size = CLAMP(p_aa_size, 0.01, 10); + emit_changed(); +} + +real_t StyleBoxFlat::get_aa_size() const { + return aa_size; +} + +inline void set_inner_corner_radius(const Rect2 style_rect, const Rect2 inner_rect, const real_t corner_radius[4], real_t *inner_corner_radius) { + real_t border_left = inner_rect.position.x - style_rect.position.x; + real_t border_top = inner_rect.position.y - style_rect.position.y; + real_t border_right = style_rect.size.width - inner_rect.size.width - border_left; + real_t border_bottom = style_rect.size.height - inner_rect.size.height - border_top; + + real_t rad; + + // Top left. + rad = MIN(border_top, border_left); + inner_corner_radius[0] = MAX(corner_radius[0] - rad, 0); + + // Top right; + rad = MIN(border_top, border_right); + inner_corner_radius[1] = MAX(corner_radius[1] - rad, 0); + + // Bottom right. + rad = MIN(border_bottom, border_right); + inner_corner_radius[2] = MAX(corner_radius[2] - rad, 0); + + // Bottom left. + rad = MIN(border_bottom, border_left); + inner_corner_radius[3] = MAX(corner_radius[3] - rad, 0); +} + +inline void draw_rounded_rectangle(Vector<Vector2> &verts, Vector<int> &indices, Vector<Color> &colors, const Rect2 &style_rect, const real_t corner_radius[4], + const Rect2 &ring_rect, const Rect2 &inner_rect, const Color &inner_color, const Color &outer_color, const int corner_detail, const Vector2 &skew, bool is_filled = false) { + int vert_offset = verts.size(); + if (!vert_offset) { + vert_offset = 0; + } + + int adapted_corner_detail = (corner_radius[0] == 0 && corner_radius[1] == 0 && corner_radius[2] == 0 && corner_radius[3] == 0) ? 1 : corner_detail; + + bool draw_border = !is_filled; + + real_t ring_corner_radius[4]; + set_inner_corner_radius(style_rect, ring_rect, corner_radius, ring_corner_radius); + + // Corner radius center points. + Vector<Point2> outer_points = { + ring_rect.position + Vector2(ring_corner_radius[0], ring_corner_radius[0]), //tl + Point2(ring_rect.position.x + ring_rect.size.x - ring_corner_radius[1], ring_rect.position.y + ring_corner_radius[1]), //tr + ring_rect.position + ring_rect.size - Vector2(ring_corner_radius[2], ring_corner_radius[2]), //br + Point2(ring_rect.position.x + ring_corner_radius[3], ring_rect.position.y + ring_rect.size.y - ring_corner_radius[3]) //bl + }; + + real_t inner_corner_radius[4]; + set_inner_corner_radius(style_rect, inner_rect, corner_radius, inner_corner_radius); + + Vector<Point2> inner_points = { + inner_rect.position + Vector2(inner_corner_radius[0], inner_corner_radius[0]), //tl + Point2(inner_rect.position.x + inner_rect.size.x - inner_corner_radius[1], inner_rect.position.y + inner_corner_radius[1]), //tr + inner_rect.position + inner_rect.size - Vector2(inner_corner_radius[2], inner_corner_radius[2]), //br + Point2(inner_rect.position.x + inner_corner_radius[3], inner_rect.position.y + inner_rect.size.y - inner_corner_radius[3]) //bl + }; + // Calculate the vertices. + + // If the center is filled, we do not draw the border and directly use the inner ring as reference. Because all calls to this + // method either draw a ring or a filled rounded rectangle, but not both. + int max_inner_outer = draw_border ? 2 : 1; + + for (int corner_index = 0; corner_index < 4; corner_index++) { + for (int detail = 0; detail <= adapted_corner_detail; detail++) { + for (int inner_outer = 0; inner_outer < max_inner_outer; inner_outer++) { + real_t radius; + Color color; + Point2 corner_point; + if (inner_outer == 0) { + radius = inner_corner_radius[corner_index]; + color = inner_color; + corner_point = inner_points[corner_index]; + } else { + radius = ring_corner_radius[corner_index]; + color = outer_color; + corner_point = outer_points[corner_index]; + } + + const real_t x = radius * (real_t)cos((corner_index + detail / (double)adapted_corner_detail) * (Math_TAU / 4.0) + Math_PI) + corner_point.x; + const real_t y = radius * (real_t)sin((corner_index + detail / (double)adapted_corner_detail) * (Math_TAU / 4.0) + Math_PI) + corner_point.y; + const float x_skew = -skew.x * (y - ring_rect.get_center().y); + const float y_skew = -skew.y * (x - ring_rect.get_center().x); + verts.push_back(Vector2(x + x_skew, y + y_skew)); + colors.push_back(color); + } + } + } + + int ring_vert_count = verts.size() - vert_offset; + + // Fill the indices and the colors for the border. + + if (draw_border) { + for (int i = 0; i < ring_vert_count; i++) { + indices.push_back(vert_offset + ((i + 0) % ring_vert_count)); + indices.push_back(vert_offset + ((i + 2) % ring_vert_count)); + indices.push_back(vert_offset + ((i + 1) % ring_vert_count)); + } + } + + if (is_filled) { + // Compute the triangles pattern to draw the rounded rectangle. + // Consists of vertical stripes of two triangles each. + + int stripes_count = ring_vert_count / 2 - 1; + int last_vert_id = ring_vert_count - 1; + + for (int i = 0; i < stripes_count; i++) { + // Polygon 1. + indices.push_back(vert_offset + i); + indices.push_back(vert_offset + last_vert_id - i - 1); + indices.push_back(vert_offset + i + 1); + // Polygon 2. + indices.push_back(vert_offset + i); + indices.push_back(vert_offset + last_vert_id - 0 - i); + indices.push_back(vert_offset + last_vert_id - 1 - i); + } + } +} + +inline void adapt_values(int p_index_a, int p_index_b, real_t *adapted_values, const real_t *p_values, const real_t p_width, const real_t p_max_a, const real_t p_max_b) { + if (p_values[p_index_a] + p_values[p_index_b] > p_width) { + real_t factor; + real_t new_value; + + factor = (real_t)p_width / (real_t)(p_values[p_index_a] + p_values[p_index_b]); + + new_value = (p_values[p_index_a] * factor); + if (new_value < adapted_values[p_index_a]) { + adapted_values[p_index_a] = new_value; + } + new_value = (p_values[p_index_b] * factor); + if (new_value < adapted_values[p_index_b]) { + adapted_values[p_index_b] = new_value; + } + } else { + adapted_values[p_index_a] = MIN(p_values[p_index_a], adapted_values[p_index_a]); + adapted_values[p_index_b] = MIN(p_values[p_index_b], adapted_values[p_index_b]); + } + adapted_values[p_index_a] = MIN(p_max_a, adapted_values[p_index_a]); + adapted_values[p_index_b] = MIN(p_max_b, adapted_values[p_index_b]); +} + +Rect2 StyleBoxFlat::get_draw_rect(const Rect2 &p_rect) const { + Rect2 draw_rect = p_rect.grow_individual(expand_margin[SIDE_LEFT], expand_margin[SIDE_TOP], expand_margin[SIDE_RIGHT], expand_margin[SIDE_BOTTOM]); + + if (shadow_size > 0) { + Rect2 shadow_rect = draw_rect.grow(shadow_size); + shadow_rect.position += shadow_offset; + draw_rect = draw_rect.merge(shadow_rect); + } + + return draw_rect; +} + +void StyleBoxFlat::draw(RID p_canvas_item, const Rect2 &p_rect) const { + bool draw_border = (border_width[0] > 0) || (border_width[1] > 0) || (border_width[2] > 0) || (border_width[3] > 0); + bool draw_shadow = (shadow_size > 0); + if (!draw_border && !draw_center && !draw_shadow) { + return; + } + + Rect2 style_rect = p_rect.grow_individual(expand_margin[SIDE_LEFT], expand_margin[SIDE_TOP], expand_margin[SIDE_RIGHT], expand_margin[SIDE_BOTTOM]); + if (Math::is_zero_approx(style_rect.size.width) || Math::is_zero_approx(style_rect.size.height)) { + return; + } + + const bool rounded_corners = (corner_radius[0] > 0) || (corner_radius[1] > 0) || (corner_radius[2] > 0) || (corner_radius[3] > 0); + // Only enable antialiasing if it is actually needed. This improve performances + // and maximizes sharpness for non-skewed StyleBoxes with sharp corners. + const bool aa_on = (rounded_corners || !skew.is_zero_approx()) && anti_aliased; + + const bool blend_on = blend_border && draw_border; + + Color border_color_alpha = Color(border_color.r, border_color.g, border_color.b, 0); + Color border_color_blend = (draw_center ? bg_color : border_color_alpha); + Color border_color_inner = blend_on ? border_color_blend : border_color; + + // Adapt borders (prevent weird overlapping/glitchy drawings). + real_t width = MAX(style_rect.size.width, 0); + real_t height = MAX(style_rect.size.height, 0); + real_t adapted_border[4] = { 1000000.0, 1000000.0, 1000000.0, 1000000.0 }; + adapt_values(SIDE_TOP, SIDE_BOTTOM, adapted_border, border_width, height, height, height); + adapt_values(SIDE_LEFT, SIDE_RIGHT, adapted_border, border_width, width, width, width); + + // Adapt corners (prevent weird overlapping/glitchy drawings). + real_t adapted_corner[4] = { 1000000.0, 1000000.0, 1000000.0, 1000000.0 }; + adapt_values(CORNER_TOP_RIGHT, CORNER_BOTTOM_RIGHT, adapted_corner, corner_radius, height, height - adapted_border[SIDE_BOTTOM], height - adapted_border[SIDE_TOP]); + adapt_values(CORNER_TOP_LEFT, CORNER_BOTTOM_LEFT, adapted_corner, corner_radius, height, height - adapted_border[SIDE_BOTTOM], height - adapted_border[SIDE_TOP]); + adapt_values(CORNER_TOP_LEFT, CORNER_TOP_RIGHT, adapted_corner, corner_radius, width, width - adapted_border[SIDE_RIGHT], width - adapted_border[SIDE_LEFT]); + adapt_values(CORNER_BOTTOM_LEFT, CORNER_BOTTOM_RIGHT, adapted_corner, corner_radius, width, width - adapted_border[SIDE_RIGHT], width - adapted_border[SIDE_LEFT]); + + Rect2 infill_rect = style_rect.grow_individual(-adapted_border[SIDE_LEFT], -adapted_border[SIDE_TOP], -adapted_border[SIDE_RIGHT], -adapted_border[SIDE_BOTTOM]); + + Rect2 border_style_rect = style_rect; + if (aa_on) { + for (int i = 0; i < 4; i++) { + if (border_width[i] > 0) { + border_style_rect = border_style_rect.grow_side((Side)i, -aa_size); + } + } + } + + Vector<Point2> verts; + Vector<int> indices; + Vector<Color> colors; + Vector<Point2> uvs; + + // Create shadow + if (draw_shadow) { + Rect2 shadow_inner_rect = style_rect; + shadow_inner_rect.position += shadow_offset; + + Rect2 shadow_rect = style_rect.grow(shadow_size); + shadow_rect.position += shadow_offset; + + Color shadow_color_transparent = Color(shadow_color.r, shadow_color.g, shadow_color.b, 0); + + draw_rounded_rectangle(verts, indices, colors, shadow_inner_rect, adapted_corner, + shadow_rect, shadow_inner_rect, shadow_color, shadow_color_transparent, corner_detail, skew); + + if (draw_center) { + draw_rounded_rectangle(verts, indices, colors, shadow_inner_rect, adapted_corner, + shadow_inner_rect, shadow_inner_rect, shadow_color, shadow_color, corner_detail, skew, true); + } + } + + // Create border (no AA). + if (draw_border && !aa_on) { + draw_rounded_rectangle(verts, indices, colors, border_style_rect, adapted_corner, + border_style_rect, infill_rect, border_color_inner, border_color, corner_detail, skew); + } + + // Create infill (no AA). + if (draw_center && (!aa_on || blend_on)) { + draw_rounded_rectangle(verts, indices, colors, border_style_rect, adapted_corner, + infill_rect, infill_rect, bg_color, bg_color, corner_detail, skew, true); + } + + if (aa_on) { + real_t aa_border_width[4]; + real_t aa_border_width_half[4]; + real_t aa_fill_width[4]; + real_t aa_fill_width_half[4]; + if (draw_border) { + for (int i = 0; i < 4; i++) { + if (border_width[i] > 0) { + aa_border_width[i] = aa_size; + aa_border_width_half[i] = aa_size / 2; + aa_fill_width[i] = 0; + aa_fill_width_half[i] = 0; + } else { + aa_border_width[i] = 0; + aa_border_width_half[i] = 0; + aa_fill_width[i] = aa_size; + aa_fill_width_half[i] = aa_size / 2; + } + } + } else { + for (int i = 0; i < 4; i++) { + aa_border_width[i] = 0; + aa_border_width_half[i] = 0; + aa_fill_width[i] = aa_size; + aa_fill_width_half[i] = aa_size / 2; + } + } + + if (draw_center) { + // Infill rect, transparent side of antialiasing gradient (base infill rect enlarged by AA size) + Rect2 infill_rect_aa_transparent = infill_rect.grow_individual(aa_fill_width_half[SIDE_LEFT], aa_fill_width_half[SIDE_TOP], + aa_fill_width_half[SIDE_RIGHT], aa_fill_width_half[SIDE_BOTTOM]); + // Infill rect, colored side of antialiasing gradient (base infill rect shrunk by AA size) + Rect2 infill_rect_aa_colored = infill_rect_aa_transparent.grow_individual(-aa_fill_width[SIDE_LEFT], -aa_fill_width[SIDE_TOP], + -aa_fill_width[SIDE_RIGHT], -aa_fill_width[SIDE_BOTTOM]); + if (!blend_on) { + // Create center fill, not antialiased yet + draw_rounded_rectangle(verts, indices, colors, border_style_rect, adapted_corner, + infill_rect_aa_colored, infill_rect_aa_colored, bg_color, bg_color, corner_detail, skew, true); + } + if (!blend_on || !draw_border) { + Color alpha_bg = Color(bg_color.r, bg_color.g, bg_color.b, 0); + // Add antialiasing on the center fill + draw_rounded_rectangle(verts, indices, colors, border_style_rect, adapted_corner, + infill_rect_aa_transparent, infill_rect_aa_colored, bg_color, alpha_bg, corner_detail, skew); + } + } + + if (draw_border) { + // Inner border recct, fully colored side of antialiasing gradient (base inner rect enlarged by AA size) + Rect2 inner_rect_aa_colored = infill_rect.grow_individual(aa_border_width_half[SIDE_LEFT], aa_border_width_half[SIDE_TOP], + aa_border_width_half[SIDE_RIGHT], aa_border_width_half[SIDE_BOTTOM]); + // Inner border rect, transparent side of antialiasing gradient (base inner rect shrunk by AA size) + Rect2 inner_rect_aa_transparent = inner_rect_aa_colored.grow_individual(-aa_border_width[SIDE_LEFT], -aa_border_width[SIDE_TOP], + -aa_border_width[SIDE_RIGHT], -aa_border_width[SIDE_BOTTOM]); + // Outer border rect, transparent side of antialiasing gradient (base outer rect enlarged by AA size) + Rect2 outer_rect_aa_transparent = style_rect.grow_individual(aa_border_width_half[SIDE_LEFT], aa_border_width_half[SIDE_TOP], + aa_border_width_half[SIDE_RIGHT], aa_border_width_half[SIDE_BOTTOM]); + // Outer border rect, colored side of antialiasing gradient (base outer rect shrunk by AA size) + Rect2 outer_rect_aa_colored = border_style_rect.grow_individual(aa_border_width_half[SIDE_LEFT], aa_border_width_half[SIDE_TOP], + aa_border_width_half[SIDE_RIGHT], aa_border_width_half[SIDE_BOTTOM]); + + // Create border ring, not antialiased yet + draw_rounded_rectangle(verts, indices, colors, border_style_rect, adapted_corner, + outer_rect_aa_colored, ((blend_on) ? infill_rect : inner_rect_aa_colored), border_color_inner, border_color, corner_detail, skew); + if (!blend_on) { + // Add antialiasing on the ring inner border + draw_rounded_rectangle(verts, indices, colors, border_style_rect, adapted_corner, + inner_rect_aa_colored, inner_rect_aa_transparent, border_color_blend, border_color, corner_detail, skew); + } + // Add antialiasing on the ring outer border + draw_rounded_rectangle(verts, indices, colors, border_style_rect, adapted_corner, + outer_rect_aa_transparent, outer_rect_aa_colored, border_color, border_color_alpha, corner_detail, skew); + } + } + + // Compute UV coordinates. + Rect2 uv_rect = style_rect.grow(aa_on ? aa_size : 0); + uvs.resize(verts.size()); + for (int i = 0; i < verts.size(); i++) { + uvs.write[i].x = (verts[i].x - uv_rect.position.x) / uv_rect.size.width; + uvs.write[i].y = (verts[i].y - uv_rect.position.y) / uv_rect.size.height; + } + + // Draw stylebox. + RenderingServer *vs = RenderingServer::get_singleton(); + vs->canvas_item_add_triangle_array(p_canvas_item, indices, verts, colors, uvs); +} + +void StyleBoxFlat::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_bg_color", "color"), &StyleBoxFlat::set_bg_color); + ClassDB::bind_method(D_METHOD("get_bg_color"), &StyleBoxFlat::get_bg_color); + + ClassDB::bind_method(D_METHOD("set_border_color", "color"), &StyleBoxFlat::set_border_color); + ClassDB::bind_method(D_METHOD("get_border_color"), &StyleBoxFlat::get_border_color); + + ClassDB::bind_method(D_METHOD("set_border_width_all", "width"), &StyleBoxFlat::set_border_width_all); + ClassDB::bind_method(D_METHOD("get_border_width_min"), &StyleBoxFlat::get_border_width_min); + + ClassDB::bind_method(D_METHOD("set_border_width", "margin", "width"), &StyleBoxFlat::set_border_width); + ClassDB::bind_method(D_METHOD("get_border_width", "margin"), &StyleBoxFlat::get_border_width); + + ClassDB::bind_method(D_METHOD("set_border_blend", "blend"), &StyleBoxFlat::set_border_blend); + ClassDB::bind_method(D_METHOD("get_border_blend"), &StyleBoxFlat::get_border_blend); + + ClassDB::bind_method(D_METHOD("set_corner_radius_all", "radius"), &StyleBoxFlat::set_corner_radius_all); + + ClassDB::bind_method(D_METHOD("set_corner_radius", "corner", "radius"), &StyleBoxFlat::set_corner_radius); + ClassDB::bind_method(D_METHOD("get_corner_radius", "corner"), &StyleBoxFlat::get_corner_radius); + + ClassDB::bind_method(D_METHOD("set_expand_margin", "margin", "size"), &StyleBoxFlat::set_expand_margin); + ClassDB::bind_method(D_METHOD("set_expand_margin_all", "size"), &StyleBoxFlat::set_expand_margin_all); + ClassDB::bind_method(D_METHOD("get_expand_margin", "margin"), &StyleBoxFlat::get_expand_margin); + + ClassDB::bind_method(D_METHOD("set_draw_center", "draw_center"), &StyleBoxFlat::set_draw_center); + ClassDB::bind_method(D_METHOD("is_draw_center_enabled"), &StyleBoxFlat::is_draw_center_enabled); + + ClassDB::bind_method(D_METHOD("set_skew", "skew"), &StyleBoxFlat::set_skew); + ClassDB::bind_method(D_METHOD("get_skew"), &StyleBoxFlat::get_skew); + + ClassDB::bind_method(D_METHOD("set_shadow_color", "color"), &StyleBoxFlat::set_shadow_color); + ClassDB::bind_method(D_METHOD("get_shadow_color"), &StyleBoxFlat::get_shadow_color); + + ClassDB::bind_method(D_METHOD("set_shadow_size", "size"), &StyleBoxFlat::set_shadow_size); + ClassDB::bind_method(D_METHOD("get_shadow_size"), &StyleBoxFlat::get_shadow_size); + + ClassDB::bind_method(D_METHOD("set_shadow_offset", "offset"), &StyleBoxFlat::set_shadow_offset); + ClassDB::bind_method(D_METHOD("get_shadow_offset"), &StyleBoxFlat::get_shadow_offset); + + ClassDB::bind_method(D_METHOD("set_anti_aliased", "anti_aliased"), &StyleBoxFlat::set_anti_aliased); + ClassDB::bind_method(D_METHOD("is_anti_aliased"), &StyleBoxFlat::is_anti_aliased); + + ClassDB::bind_method(D_METHOD("set_aa_size", "size"), &StyleBoxFlat::set_aa_size); + ClassDB::bind_method(D_METHOD("get_aa_size"), &StyleBoxFlat::get_aa_size); + + ClassDB::bind_method(D_METHOD("set_corner_detail", "detail"), &StyleBoxFlat::set_corner_detail); + ClassDB::bind_method(D_METHOD("get_corner_detail"), &StyleBoxFlat::get_corner_detail); + + ADD_PROPERTY(PropertyInfo(Variant::COLOR, "bg_color"), "set_bg_color", "get_bg_color"); + + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draw_center"), "set_draw_center", "is_draw_center_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "skew"), "set_skew", "get_skew"); + + ADD_GROUP("Border Width", "border_width_"); + ADD_PROPERTYI(PropertyInfo(Variant::INT, "border_width_left", PROPERTY_HINT_RANGE, "0,1024,1,suffix:px"), "set_border_width", "get_border_width", SIDE_LEFT); + ADD_PROPERTYI(PropertyInfo(Variant::INT, "border_width_top", PROPERTY_HINT_RANGE, "0,1024,1,suffix:px"), "set_border_width", "get_border_width", SIDE_TOP); + ADD_PROPERTYI(PropertyInfo(Variant::INT, "border_width_right", PROPERTY_HINT_RANGE, "0,1024,1,suffix:px"), "set_border_width", "get_border_width", SIDE_RIGHT); + ADD_PROPERTYI(PropertyInfo(Variant::INT, "border_width_bottom", PROPERTY_HINT_RANGE, "0,1024,1,suffix:px"), "set_border_width", "get_border_width", SIDE_BOTTOM); + + ADD_GROUP("Border", "border_"); + ADD_PROPERTY(PropertyInfo(Variant::COLOR, "border_color"), "set_border_color", "get_border_color"); + + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "border_blend"), "set_border_blend", "get_border_blend"); + + ADD_GROUP("Corner Radius", "corner_radius_"); + ADD_PROPERTYI(PropertyInfo(Variant::INT, "corner_radius_top_left", PROPERTY_HINT_RANGE, "0,1024,1,suffix:px"), "set_corner_radius", "get_corner_radius", CORNER_TOP_LEFT); + ADD_PROPERTYI(PropertyInfo(Variant::INT, "corner_radius_top_right", PROPERTY_HINT_RANGE, "0,1024,1,suffix:px"), "set_corner_radius", "get_corner_radius", CORNER_TOP_RIGHT); + ADD_PROPERTYI(PropertyInfo(Variant::INT, "corner_radius_bottom_right", PROPERTY_HINT_RANGE, "0,1024,1,suffix:px"), "set_corner_radius", "get_corner_radius", CORNER_BOTTOM_RIGHT); + ADD_PROPERTYI(PropertyInfo(Variant::INT, "corner_radius_bottom_left", PROPERTY_HINT_RANGE, "0,1024,1,suffix:px"), "set_corner_radius", "get_corner_radius", CORNER_BOTTOM_LEFT); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "corner_detail", PROPERTY_HINT_RANGE, "1,20,1"), "set_corner_detail", "get_corner_detail"); + + ADD_GROUP("Expand Margins", "expand_margin_"); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "expand_margin_left", PROPERTY_HINT_RANGE, "0,2048,1,suffix:px"), "set_expand_margin", "get_expand_margin", SIDE_LEFT); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "expand_margin_top", PROPERTY_HINT_RANGE, "0,2048,1,suffix:px"), "set_expand_margin", "get_expand_margin", SIDE_TOP); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "expand_margin_right", PROPERTY_HINT_RANGE, "0,2048,1,suffix:px"), "set_expand_margin", "get_expand_margin", SIDE_RIGHT); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "expand_margin_bottom", PROPERTY_HINT_RANGE, "0,2048,1,suffix:px"), "set_expand_margin", "get_expand_margin", SIDE_BOTTOM); + + ADD_GROUP("Shadow", "shadow_"); + ADD_PROPERTY(PropertyInfo(Variant::COLOR, "shadow_color"), "set_shadow_color", "get_shadow_color"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "shadow_size", PROPERTY_HINT_RANGE, "0,100,1,or_greater,suffix:px"), "set_shadow_size", "get_shadow_size"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "shadow_offset", PROPERTY_HINT_NONE, "suffix:px"), "set_shadow_offset", "get_shadow_offset"); + + ADD_GROUP("Anti Aliasing", "anti_aliasing_"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "anti_aliasing"), "set_anti_aliased", "is_anti_aliased"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "anti_aliasing_size", PROPERTY_HINT_RANGE, "0.01,10,0.001,suffix:px"), "set_aa_size", "get_aa_size"); +} + +StyleBoxFlat::StyleBoxFlat() {} + +StyleBoxFlat::~StyleBoxFlat() {} diff --git a/scene/resources/style_box_flat.h b/scene/resources/style_box_flat.h new file mode 100644 index 0000000000..b6bb145f05 --- /dev/null +++ b/scene/resources/style_box_flat.h @@ -0,0 +1,118 @@ +/**************************************************************************/ +/* style_box_flat.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef STYLE_BOX_FLAT_H +#define STYLE_BOX_FLAT_H + +#include "scene/resources/style_box.h" + +class StyleBoxFlat : public StyleBox { + GDCLASS(StyleBoxFlat, StyleBox); + + Color bg_color = Color(0.6, 0.6, 0.6); + Color shadow_color = Color(0, 0, 0, 0.6); + Color border_color = Color(0.8, 0.8, 0.8); + + real_t border_width[4] = {}; + real_t expand_margin[4] = {}; + real_t corner_radius[4] = {}; + + bool draw_center = true; + bool blend_border = false; + Vector2 skew; + bool anti_aliased = true; + + int corner_detail = 8; + int shadow_size = 0; + Point2 shadow_offset; + real_t aa_size = 1; + +protected: + virtual float get_style_margin(Side p_side) const override; + static void _bind_methods(); + void _validate_property(PropertyInfo &p_property) const; + +public: + void set_bg_color(const Color &p_color); + Color get_bg_color() const; + + void set_border_color(const Color &p_color); + Color get_border_color() const; + + void set_border_width_all(int p_size); + int get_border_width_min() const; + + void set_border_width(Side p_side, int p_width); + int get_border_width(Side p_side) const; + + void set_border_blend(bool p_blend); + bool get_border_blend() const; + + void set_corner_radius_all(int radius); + void set_corner_radius_individual(const int radius_top_left, const int radius_top_right, const int radius_bottom_right, const int radius_bottom_left); + void set_corner_radius(Corner p_corner, const int radius); + int get_corner_radius(Corner p_corner) const; + + void set_corner_detail(const int &p_corner_detail); + int get_corner_detail() const; + + void set_expand_margin(Side p_expand_side, float p_size); + void set_expand_margin_all(float p_expand_margin_size); + void set_expand_margin_individual(float p_left, float p_top, float p_right, float p_bottom); + float get_expand_margin(Side p_expand_side) const; + + void set_draw_center(bool p_enabled); + bool is_draw_center_enabled() const; + + void set_skew(Vector2 p_skew); + Vector2 get_skew() const; + + void set_shadow_color(const Color &p_color); + Color get_shadow_color() const; + + void set_shadow_size(const int &p_size); + int get_shadow_size() const; + + void set_shadow_offset(const Point2 &p_offset); + Point2 get_shadow_offset() const; + + void set_anti_aliased(const bool &p_anti_aliased); + bool is_anti_aliased() const; + void set_aa_size(const real_t p_aa_size); + real_t get_aa_size() const; + + virtual Rect2 get_draw_rect(const Rect2 &p_rect) const override; + virtual void draw(RID p_canvas_item, const Rect2 &p_rect) const override; + + StyleBoxFlat(); + ~StyleBoxFlat(); +}; + +#endif // STYLE_BOX_FLAT_H diff --git a/scene/resources/style_box_line.cpp b/scene/resources/style_box_line.cpp new file mode 100644 index 0000000000..9aeba88531 --- /dev/null +++ b/scene/resources/style_box_line.cpp @@ -0,0 +1,132 @@ +/**************************************************************************/ +/* style_box_line.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "style_box_line.h" + +#include "servers/rendering_server.h" + +float StyleBoxLine::get_style_margin(Side p_side) const { + ERR_FAIL_INDEX_V((int)p_side, 4, 0); + + if (vertical) { + if (p_side == SIDE_LEFT || p_side == SIDE_RIGHT) { + return thickness / 2.0; + } + } else if (p_side == SIDE_TOP || p_side == SIDE_BOTTOM) { + return thickness / 2.0; + } + + return 0; +} + +void StyleBoxLine::set_color(const Color &p_color) { + color = p_color; + emit_changed(); +} + +Color StyleBoxLine::get_color() const { + return color; +} + +void StyleBoxLine::set_thickness(int p_thickness) { + thickness = p_thickness; + emit_changed(); +} + +int StyleBoxLine::get_thickness() const { + return thickness; +} + +void StyleBoxLine::set_vertical(bool p_vertical) { + vertical = p_vertical; + emit_changed(); +} + +bool StyleBoxLine::is_vertical() const { + return vertical; +} + +void StyleBoxLine::set_grow_end(float p_grow_end) { + grow_end = p_grow_end; + emit_changed(); +} + +float StyleBoxLine::get_grow_end() const { + return grow_end; +} + +void StyleBoxLine::set_grow_begin(float p_grow_begin) { + grow_begin = p_grow_begin; + emit_changed(); +} + +float StyleBoxLine::get_grow_begin() const { + return grow_begin; +} + +void StyleBoxLine::draw(RID p_canvas_item, const Rect2 &p_rect) const { + RenderingServer *vs = RenderingServer::get_singleton(); + Rect2i r = p_rect; + + if (vertical) { + r.position.y -= grow_begin; + r.size.y += (grow_begin + grow_end); + r.size.x = thickness; + } else { + r.position.x -= grow_begin; + r.size.x += (grow_begin + grow_end); + r.size.y = thickness; + } + + vs->canvas_item_add_rect(p_canvas_item, r, color); +} + +void StyleBoxLine::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_color", "color"), &StyleBoxLine::set_color); + ClassDB::bind_method(D_METHOD("get_color"), &StyleBoxLine::get_color); + ClassDB::bind_method(D_METHOD("set_thickness", "thickness"), &StyleBoxLine::set_thickness); + ClassDB::bind_method(D_METHOD("get_thickness"), &StyleBoxLine::get_thickness); + ClassDB::bind_method(D_METHOD("set_grow_begin", "offset"), &StyleBoxLine::set_grow_begin); + ClassDB::bind_method(D_METHOD("get_grow_begin"), &StyleBoxLine::get_grow_begin); + ClassDB::bind_method(D_METHOD("set_grow_end", "offset"), &StyleBoxLine::set_grow_end); + ClassDB::bind_method(D_METHOD("get_grow_end"), &StyleBoxLine::get_grow_end); + ClassDB::bind_method(D_METHOD("set_vertical", "vertical"), &StyleBoxLine::set_vertical); + ClassDB::bind_method(D_METHOD("is_vertical"), &StyleBoxLine::is_vertical); + + ADD_PROPERTY(PropertyInfo(Variant::COLOR, "color"), "set_color", "get_color"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "grow_begin", PROPERTY_HINT_RANGE, "-300,300,1,suffix:px"), "set_grow_begin", "get_grow_begin"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "grow_end", PROPERTY_HINT_RANGE, "-300,300,1,suffix:px"), "set_grow_end", "get_grow_end"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "thickness", PROPERTY_HINT_RANGE, "0,100,suffix:px"), "set_thickness", "get_thickness"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "vertical"), "set_vertical", "is_vertical"); +} + +StyleBoxLine::StyleBoxLine() {} + +StyleBoxLine::~StyleBoxLine() {} diff --git a/scene/resources/style_box_line.h b/scene/resources/style_box_line.h new file mode 100644 index 0000000000..18f765a1e4 --- /dev/null +++ b/scene/resources/style_box_line.h @@ -0,0 +1,70 @@ +/**************************************************************************/ +/* style_box_line.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef STYLE_BOX_LINE_H +#define STYLE_BOX_LINE_H + +#include "scene/resources/style_box.h" + +class StyleBoxLine : public StyleBox { + GDCLASS(StyleBoxLine, StyleBox); + Color color; + int thickness = 1; + bool vertical = false; + float grow_begin = 1.0; + float grow_end = 1.0; + +protected: + virtual float get_style_margin(Side p_side) const override; + static void _bind_methods(); + +public: + void set_color(const Color &p_color); + Color get_color() const; + + void set_thickness(int p_thickness); + int get_thickness() const; + + void set_vertical(bool p_vertical); + bool is_vertical() const; + + void set_grow_begin(float p_grow); + float get_grow_begin() const; + + void set_grow_end(float p_grow); + float get_grow_end() const; + + virtual void draw(RID p_canvas_item, const Rect2 &p_rect) const override; + + StyleBoxLine(); + ~StyleBoxLine(); +}; + +#endif // STYLE_BOX_LINE_H diff --git a/scene/resources/style_box_texture.cpp b/scene/resources/style_box_texture.cpp new file mode 100644 index 0000000000..156525d21c --- /dev/null +++ b/scene/resources/style_box_texture.cpp @@ -0,0 +1,243 @@ +/**************************************************************************/ +/* style_box_texture.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "style_box_texture.h" + +float StyleBoxTexture::get_style_margin(Side p_side) const { + ERR_FAIL_INDEX_V((int)p_side, 4, 0.0); + + return texture_margin[p_side]; +} + +void StyleBoxTexture::set_texture(Ref<Texture2D> p_texture) { + if (texture == p_texture) { + return; + } + texture = p_texture; + emit_changed(); +} + +Ref<Texture2D> StyleBoxTexture::get_texture() const { + return texture; +} + +void StyleBoxTexture::set_texture_margin(Side p_side, float p_size) { + ERR_FAIL_INDEX((int)p_side, 4); + + texture_margin[p_side] = p_size; + emit_changed(); +} + +void StyleBoxTexture::set_texture_margin_all(float p_size) { + for (int i = 0; i < 4; i++) { + texture_margin[i] = p_size; + } + emit_changed(); +} + +void StyleBoxTexture::set_texture_margin_individual(float p_left, float p_top, float p_right, float p_bottom) { + texture_margin[SIDE_LEFT] = p_left; + texture_margin[SIDE_TOP] = p_top; + texture_margin[SIDE_RIGHT] = p_right; + texture_margin[SIDE_BOTTOM] = p_bottom; + emit_changed(); +} + +float StyleBoxTexture::get_texture_margin(Side p_side) const { + ERR_FAIL_INDEX_V((int)p_side, 4, 0.0); + + return texture_margin[p_side]; +} + +void StyleBoxTexture::set_expand_margin(Side p_side, float p_size) { + ERR_FAIL_INDEX((int)p_side, 4); + expand_margin[p_side] = p_size; + emit_changed(); +} + +void StyleBoxTexture::set_expand_margin_all(float p_expand_margin_size) { + for (int i = 0; i < 4; i++) { + expand_margin[i] = p_expand_margin_size; + } + emit_changed(); +} + +void StyleBoxTexture::set_expand_margin_individual(float p_left, float p_top, float p_right, float p_bottom) { + expand_margin[SIDE_LEFT] = p_left; + expand_margin[SIDE_TOP] = p_top; + expand_margin[SIDE_RIGHT] = p_right; + expand_margin[SIDE_BOTTOM] = p_bottom; + emit_changed(); +} + +float StyleBoxTexture::get_expand_margin(Side p_side) const { + ERR_FAIL_INDEX_V((int)p_side, 4, 0); + return expand_margin[p_side]; +} + +void StyleBoxTexture::set_region_rect(const Rect2 &p_region_rect) { + if (region_rect == p_region_rect) { + return; + } + + region_rect = p_region_rect; + emit_changed(); +} + +Rect2 StyleBoxTexture::get_region_rect() const { + return region_rect; +} + +void StyleBoxTexture::set_draw_center(bool p_enabled) { + draw_center = p_enabled; + emit_changed(); +} + +bool StyleBoxTexture::is_draw_center_enabled() const { + return draw_center; +} + +void StyleBoxTexture::set_h_axis_stretch_mode(AxisStretchMode p_mode) { + ERR_FAIL_INDEX((int)p_mode, 3); + axis_h = p_mode; + emit_changed(); +} + +StyleBoxTexture::AxisStretchMode StyleBoxTexture::get_h_axis_stretch_mode() const { + return axis_h; +} + +void StyleBoxTexture::set_v_axis_stretch_mode(AxisStretchMode p_mode) { + ERR_FAIL_INDEX((int)p_mode, 3); + axis_v = p_mode; + emit_changed(); +} + +StyleBoxTexture::AxisStretchMode StyleBoxTexture::get_v_axis_stretch_mode() const { + return axis_v; +} + +void StyleBoxTexture::set_modulate(const Color &p_modulate) { + if (modulate == p_modulate) { + return; + } + modulate = p_modulate; + emit_changed(); +} + +Color StyleBoxTexture::get_modulate() const { + return modulate; +} + +Rect2 StyleBoxTexture::get_draw_rect(const Rect2 &p_rect) const { + return p_rect.grow_individual(expand_margin[SIDE_LEFT], expand_margin[SIDE_TOP], expand_margin[SIDE_RIGHT], expand_margin[SIDE_BOTTOM]); +} + +void StyleBoxTexture::draw(RID p_canvas_item, const Rect2 &p_rect) const { + if (texture.is_null()) { + return; + } + + Rect2 rect = p_rect; + Rect2 src_rect = region_rect; + + texture->get_rect_region(rect, src_rect, rect, src_rect); + + rect.position.x -= expand_margin[SIDE_LEFT]; + rect.position.y -= expand_margin[SIDE_TOP]; + rect.size.x += expand_margin[SIDE_LEFT] + expand_margin[SIDE_RIGHT]; + rect.size.y += expand_margin[SIDE_TOP] + expand_margin[SIDE_BOTTOM]; + + Vector2 start_offset = Vector2(texture_margin[SIDE_LEFT], texture_margin[SIDE_TOP]); + Vector2 end_offset = Vector2(texture_margin[SIDE_RIGHT], texture_margin[SIDE_BOTTOM]); + + RenderingServer::get_singleton()->canvas_item_add_nine_patch(p_canvas_item, rect, src_rect, texture->get_rid(), start_offset, end_offset, RS::NinePatchAxisMode(axis_h), RS::NinePatchAxisMode(axis_v), draw_center, modulate); +} + +void StyleBoxTexture::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_texture", "texture"), &StyleBoxTexture::set_texture); + ClassDB::bind_method(D_METHOD("get_texture"), &StyleBoxTexture::get_texture); + + ClassDB::bind_method(D_METHOD("set_texture_margin", "margin", "size"), &StyleBoxTexture::set_texture_margin); + ClassDB::bind_method(D_METHOD("set_texture_margin_all", "size"), &StyleBoxTexture::set_texture_margin_all); + ClassDB::bind_method(D_METHOD("get_texture_margin", "margin"), &StyleBoxTexture::get_texture_margin); + + ClassDB::bind_method(D_METHOD("set_expand_margin", "margin", "size"), &StyleBoxTexture::set_expand_margin); + ClassDB::bind_method(D_METHOD("set_expand_margin_all", "size"), &StyleBoxTexture::set_expand_margin_all); + ClassDB::bind_method(D_METHOD("get_expand_margin", "margin"), &StyleBoxTexture::get_expand_margin); + + ClassDB::bind_method(D_METHOD("set_region_rect", "region"), &StyleBoxTexture::set_region_rect); + ClassDB::bind_method(D_METHOD("get_region_rect"), &StyleBoxTexture::get_region_rect); + + ClassDB::bind_method(D_METHOD("set_draw_center", "enable"), &StyleBoxTexture::set_draw_center); + ClassDB::bind_method(D_METHOD("is_draw_center_enabled"), &StyleBoxTexture::is_draw_center_enabled); + + ClassDB::bind_method(D_METHOD("set_modulate", "color"), &StyleBoxTexture::set_modulate); + ClassDB::bind_method(D_METHOD("get_modulate"), &StyleBoxTexture::get_modulate); + + ClassDB::bind_method(D_METHOD("set_h_axis_stretch_mode", "mode"), &StyleBoxTexture::set_h_axis_stretch_mode); + ClassDB::bind_method(D_METHOD("get_h_axis_stretch_mode"), &StyleBoxTexture::get_h_axis_stretch_mode); + + ClassDB::bind_method(D_METHOD("set_v_axis_stretch_mode", "mode"), &StyleBoxTexture::set_v_axis_stretch_mode); + ClassDB::bind_method(D_METHOD("get_v_axis_stretch_mode"), &StyleBoxTexture::get_v_axis_stretch_mode); + + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_texture", "get_texture"); + + ADD_GROUP("Texture Margins", "texture_margin_"); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "texture_margin_left", PROPERTY_HINT_RANGE, "0,2048,1,suffix:px"), "set_texture_margin", "get_texture_margin", SIDE_LEFT); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "texture_margin_top", PROPERTY_HINT_RANGE, "0,2048,1,suffix:px"), "set_texture_margin", "get_texture_margin", SIDE_TOP); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "texture_margin_right", PROPERTY_HINT_RANGE, "0,2048,1,suffix:px"), "set_texture_margin", "get_texture_margin", SIDE_RIGHT); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "texture_margin_bottom", PROPERTY_HINT_RANGE, "0,2048,1,suffix:px"), "set_texture_margin", "get_texture_margin", SIDE_BOTTOM); + + ADD_GROUP("Expand Margins", "expand_margin_"); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "expand_margin_left", PROPERTY_HINT_RANGE, "0,2048,1,suffix:px"), "set_expand_margin", "get_expand_margin", SIDE_LEFT); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "expand_margin_top", PROPERTY_HINT_RANGE, "0,2048,1,suffix:px"), "set_expand_margin", "get_expand_margin", SIDE_TOP); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "expand_margin_right", PROPERTY_HINT_RANGE, "0,2048,1,suffix:px"), "set_expand_margin", "get_expand_margin", SIDE_RIGHT); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "expand_margin_bottom", PROPERTY_HINT_RANGE, "0,2048,1,suffix:px"), "set_expand_margin", "get_expand_margin", SIDE_BOTTOM); + + ADD_GROUP("Axis Stretch", "axis_stretch_"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "axis_stretch_horizontal", PROPERTY_HINT_ENUM, "Stretch,Tile,Tile Fit"), "set_h_axis_stretch_mode", "get_h_axis_stretch_mode"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "axis_stretch_vertical", PROPERTY_HINT_ENUM, "Stretch,Tile,Tile Fit"), "set_v_axis_stretch_mode", "get_v_axis_stretch_mode"); + + ADD_GROUP("Sub-Region", "region_"); + ADD_PROPERTY(PropertyInfo(Variant::RECT2, "region_rect", PROPERTY_HINT_NONE, "suffix:px"), "set_region_rect", "get_region_rect"); + + ADD_GROUP("Modulate", "modulate_"); + ADD_PROPERTY(PropertyInfo(Variant::COLOR, "modulate_color"), "set_modulate", "get_modulate"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draw_center"), "set_draw_center", "is_draw_center_enabled"); + + BIND_ENUM_CONSTANT(AXIS_STRETCH_MODE_STRETCH); + BIND_ENUM_CONSTANT(AXIS_STRETCH_MODE_TILE); + BIND_ENUM_CONSTANT(AXIS_STRETCH_MODE_TILE_FIT); +} + +StyleBoxTexture::StyleBoxTexture() {} + +StyleBoxTexture::~StyleBoxTexture() {} diff --git a/scene/resources/style_box_texture.h b/scene/resources/style_box_texture.h new file mode 100644 index 0000000000..b1b833f470 --- /dev/null +++ b/scene/resources/style_box_texture.h @@ -0,0 +1,99 @@ +/**************************************************************************/ +/* style_box_texture.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef STYLE_BOX_TEXTURE_H +#define STYLE_BOX_TEXTURE_H + +#include "scene/resources/style_box.h" +#include "scene/resources/texture.h" + +class StyleBoxTexture : public StyleBox { + GDCLASS(StyleBoxTexture, StyleBox); + +public: + enum AxisStretchMode { + AXIS_STRETCH_MODE_STRETCH, + AXIS_STRETCH_MODE_TILE, + AXIS_STRETCH_MODE_TILE_FIT, + }; + +private: + float expand_margin[4] = {}; + float texture_margin[4] = {}; + Rect2 region_rect; + Ref<Texture2D> texture; + bool draw_center = true; + Color modulate = Color(1, 1, 1, 1); + AxisStretchMode axis_h = AXIS_STRETCH_MODE_STRETCH; + AxisStretchMode axis_v = AXIS_STRETCH_MODE_STRETCH; + +protected: + virtual float get_style_margin(Side p_side) const override; + static void _bind_methods(); + +public: + void set_texture(Ref<Texture2D> p_texture); + Ref<Texture2D> get_texture() const; + + void set_texture_margin(Side p_side, float p_size); + void set_texture_margin_all(float p_size); + void set_texture_margin_individual(float p_left, float p_top, float p_right, float p_bottom); + float get_texture_margin(Side p_side) const; + + void set_expand_margin(Side p_expand_side, float p_size); + void set_expand_margin_all(float p_expand_margin_size); + void set_expand_margin_individual(float p_left, float p_top, float p_right, float p_bottom); + float get_expand_margin(Side p_expand_side) const; + + void set_region_rect(const Rect2 &p_region_rect); + Rect2 get_region_rect() const; + + void set_draw_center(bool p_enabled); + bool is_draw_center_enabled() const; + + void set_h_axis_stretch_mode(AxisStretchMode p_mode); + AxisStretchMode get_h_axis_stretch_mode() const; + + void set_v_axis_stretch_mode(AxisStretchMode p_mode); + AxisStretchMode get_v_axis_stretch_mode() const; + + void set_modulate(const Color &p_modulate); + Color get_modulate() const; + + virtual Rect2 get_draw_rect(const Rect2 &p_rect) const override; + virtual void draw(RID p_canvas_item, const Rect2 &p_rect) const override; + + StyleBoxTexture(); + ~StyleBoxTexture(); +}; + +VARIANT_ENUM_CAST(StyleBoxTexture::AxisStretchMode) + +#endif // STYLE_BOX_TEXTURE_H diff --git a/scene/resources/texture.cpp b/scene/resources/texture.cpp index 837aa39ce1..0efaad61fe 100644 --- a/scene/resources/texture.cpp +++ b/scene/resources/texture.cpp @@ -30,14 +30,7 @@ #include "texture.h" -#include "core/core_string_names.h" -#include "core/io/image_loader.h" -#include "core/io/marshalls.h" -#include "core/math/geometry_2d.h" -#include "core/os/os.h" -#include "scene/resources/bit_map.h" -#include "scene/resources/mesh.h" -#include "servers/camera/camera_feed.h" +#include "scene/resources/placeholder_textures.h" int Texture2D::get_width() const { int ret = 0; @@ -127,991 +120,6 @@ void Texture2D::_bind_methods() { Texture2D::Texture2D() { } -///////////////////// - -void ImageTexture::reload_from_file() { - String path = ResourceLoader::path_remap(get_path()); - if (!path.is_resource_file()) { - return; - } - - Ref<Image> img; - img.instantiate(); - - if (ImageLoader::load_image(path, img) == OK) { - set_image(img); - } else { - Resource::reload_from_file(); - notify_property_list_changed(); - emit_changed(); - } -} - -bool ImageTexture::_set(const StringName &p_name, const Variant &p_value) { - if (p_name == "image") { - set_image(p_value); - return true; - } - return false; -} - -bool ImageTexture::_get(const StringName &p_name, Variant &r_ret) const { - if (p_name == "image") { - r_ret = get_image(); - return true; - } - return false; -} - -void ImageTexture::_get_property_list(List<PropertyInfo> *p_list) const { - p_list->push_back(PropertyInfo(Variant::OBJECT, PNAME("image"), PROPERTY_HINT_RESOURCE_TYPE, "Image", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_RESOURCE_NOT_PERSISTENT)); -} - -Ref<ImageTexture> ImageTexture::create_from_image(const Ref<Image> &p_image) { - ERR_FAIL_COND_V_MSG(p_image.is_null(), Ref<ImageTexture>(), "Invalid image: null"); - ERR_FAIL_COND_V_MSG(p_image->is_empty(), Ref<ImageTexture>(), "Invalid image: image is empty"); - - Ref<ImageTexture> image_texture; - image_texture.instantiate(); - image_texture->set_image(p_image); - return image_texture; -} - -void ImageTexture::set_image(const Ref<Image> &p_image) { - ERR_FAIL_COND_MSG(p_image.is_null() || p_image->is_empty(), "Invalid image"); - w = p_image->get_width(); - h = p_image->get_height(); - format = p_image->get_format(); - mipmaps = p_image->has_mipmaps(); - - if (texture.is_null()) { - texture = RenderingServer::get_singleton()->texture_2d_create(p_image); - } else { - RID new_texture = RenderingServer::get_singleton()->texture_2d_create(p_image); - RenderingServer::get_singleton()->texture_replace(texture, new_texture); - } - notify_property_list_changed(); - emit_changed(); - - image_stored = true; -} - -Image::Format ImageTexture::get_format() const { - return format; -} - -void ImageTexture::update(const Ref<Image> &p_image) { - ERR_FAIL_COND_MSG(p_image.is_null(), "Invalid image"); - ERR_FAIL_COND_MSG(texture.is_null(), "Texture is not initialized."); - ERR_FAIL_COND_MSG(p_image->get_width() != w || p_image->get_height() != h, - "The new image dimensions must match the texture size."); - ERR_FAIL_COND_MSG(p_image->get_format() != format, - "The new image format must match the texture's image format."); - ERR_FAIL_COND_MSG(mipmaps != p_image->has_mipmaps(), - "The new image mipmaps configuration must match the texture's image mipmaps configuration"); - - RS::get_singleton()->texture_2d_update(texture, p_image); - - notify_property_list_changed(); - emit_changed(); - - alpha_cache.unref(); - image_stored = true; -} - -Ref<Image> ImageTexture::get_image() const { - if (image_stored) { - return RenderingServer::get_singleton()->texture_2d_get(texture); - } else { - return Ref<Image>(); - } -} - -int ImageTexture::get_width() const { - return w; -} - -int ImageTexture::get_height() const { - return h; -} - -RID ImageTexture::get_rid() const { - if (texture.is_null()) { - //we are in trouble, create something temporary - texture = RenderingServer::get_singleton()->texture_2d_placeholder_create(); - } - return texture; -} - -bool ImageTexture::has_alpha() const { - return (format == Image::FORMAT_LA8 || format == Image::FORMAT_RGBA8); -} - -void ImageTexture::draw(RID p_canvas_item, const Point2 &p_pos, const Color &p_modulate, bool p_transpose) const { - if ((w | h) == 0) { - return; - } - RenderingServer::get_singleton()->canvas_item_add_texture_rect(p_canvas_item, Rect2(p_pos, Size2(w, h)), texture, false, p_modulate, p_transpose); -} - -void ImageTexture::draw_rect(RID p_canvas_item, const Rect2 &p_rect, bool p_tile, const Color &p_modulate, bool p_transpose) const { - if ((w | h) == 0) { - return; - } - RenderingServer::get_singleton()->canvas_item_add_texture_rect(p_canvas_item, p_rect, texture, p_tile, p_modulate, p_transpose); -} - -void ImageTexture::draw_rect_region(RID p_canvas_item, const Rect2 &p_rect, const Rect2 &p_src_rect, const Color &p_modulate, bool p_transpose, bool p_clip_uv) const { - if ((w | h) == 0) { - return; - } - RenderingServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas_item, p_rect, texture, p_src_rect, p_modulate, p_transpose, p_clip_uv); -} - -bool ImageTexture::is_pixel_opaque(int p_x, int p_y) const { - if (!alpha_cache.is_valid()) { - Ref<Image> img = get_image(); - if (img.is_valid()) { - if (img->is_compressed()) { //must decompress, if compressed - Ref<Image> decom = img->duplicate(); - decom->decompress(); - img = decom; - } - alpha_cache.instantiate(); - alpha_cache->create_from_image_alpha(img); - } - } - - if (alpha_cache.is_valid()) { - int aw = int(alpha_cache->get_size().width); - int ah = int(alpha_cache->get_size().height); - if (aw == 0 || ah == 0) { - return true; - } - - int x = p_x * aw / w; - int y = p_y * ah / h; - - x = CLAMP(x, 0, aw); - y = CLAMP(y, 0, ah); - - return alpha_cache->get_bit(x, y); - } - - return true; -} - -void ImageTexture::set_size_override(const Size2i &p_size) { - Size2i s = p_size; - if (s.x != 0) { - w = s.x; - } - if (s.y != 0) { - h = s.y; - } - RenderingServer::get_singleton()->texture_set_size_override(texture, w, h); -} - -void ImageTexture::set_path(const String &p_path, bool p_take_over) { - if (texture.is_valid()) { - RenderingServer::get_singleton()->texture_set_path(texture, p_path); - } - - Resource::set_path(p_path, p_take_over); -} - -void ImageTexture::_bind_methods() { - ClassDB::bind_static_method("ImageTexture", D_METHOD("create_from_image", "image"), &ImageTexture::create_from_image); - ClassDB::bind_method(D_METHOD("get_format"), &ImageTexture::get_format); - - ClassDB::bind_method(D_METHOD("set_image", "image"), &ImageTexture::set_image); - ClassDB::bind_method(D_METHOD("update", "image"), &ImageTexture::update); - ClassDB::bind_method(D_METHOD("set_size_override", "size"), &ImageTexture::set_size_override); -} - -ImageTexture::ImageTexture() {} - -ImageTexture::~ImageTexture() { - if (texture.is_valid()) { - ERR_FAIL_NULL(RenderingServer::get_singleton()); - RenderingServer::get_singleton()->free(texture); - } -} - -///////////////////// - -void PortableCompressedTexture2D::_set_data(const Vector<uint8_t> &p_data) { - if (p_data.size() == 0) { - return; //nothing to do - } - - const uint8_t *data = p_data.ptr(); - uint32_t data_size = p_data.size(); - ERR_FAIL_COND(data_size < 20); - compression_mode = CompressionMode(decode_uint32(data + 0)); - format = Image::Format(decode_uint32(data + 4)); - uint32_t mipmap_count = decode_uint32(data + 8); - size.width = decode_uint32(data + 12); - size.height = decode_uint32(data + 16); - mipmaps = mipmap_count > 1; - - data += 20; - data_size -= 20; - - Ref<Image> image; - - switch (compression_mode) { - case COMPRESSION_MODE_LOSSLESS: - case COMPRESSION_MODE_LOSSY: { - Vector<uint8_t> image_data; - - ERR_FAIL_COND(data_size < 4); - for (uint32_t i = 0; i < mipmap_count; i++) { - uint32_t mipsize = decode_uint32(data); - data += 4; - data_size -= 4; - ERR_FAIL_COND(mipsize < data_size); - Ref<Image> img = memnew(Image(data, data_size)); - ERR_FAIL_COND(img->is_empty()); - if (img->get_format() != format) { // May happen due to webp/png in the tiny mipmaps. - img->convert(format); - } - image_data.append_array(img->get_data()); - - data += mipsize; - data_size -= mipsize; - } - - image = Ref<Image>(memnew(Image(size.width, size.height, mipmap_count > 1, format, image_data))); - - } break; - case COMPRESSION_MODE_BASIS_UNIVERSAL: { - ERR_FAIL_NULL(Image::basis_universal_unpacker_ptr); - image = Image::basis_universal_unpacker_ptr(data, data_size); - - } break; - case COMPRESSION_MODE_S3TC: - case COMPRESSION_MODE_ETC2: - case COMPRESSION_MODE_BPTC: { - image = Ref<Image>(memnew(Image(size.width, size.height, mipmap_count > 1, format, p_data.slice(20)))); - } break; - } - ERR_FAIL_COND(image.is_null()); - - if (texture.is_null()) { - texture = RenderingServer::get_singleton()->texture_2d_create(image); - } else { - RID new_texture = RenderingServer::get_singleton()->texture_2d_create(image); - RenderingServer::get_singleton()->texture_replace(texture, new_texture); - } - - image_stored = true; - RenderingServer::get_singleton()->texture_set_size_override(texture, size_override.width, size_override.height); - alpha_cache.unref(); - - if (keep_all_compressed_buffers || keep_compressed_buffer) { - compressed_buffer = p_data; - } else { - compressed_buffer.clear(); - } -} - -PortableCompressedTexture2D::CompressionMode PortableCompressedTexture2D::get_compression_mode() const { - return compression_mode; -} -Vector<uint8_t> PortableCompressedTexture2D::_get_data() const { - return compressed_buffer; -} - -void PortableCompressedTexture2D::create_from_image(const Ref<Image> &p_image, CompressionMode p_compression_mode, bool p_normal_map, float p_lossy_quality) { - ERR_FAIL_COND(p_image.is_null() || p_image->is_empty()); - - Vector<uint8_t> buffer; - - buffer.resize(20); - encode_uint32(p_compression_mode, buffer.ptrw()); - encode_uint32(p_image->get_format(), buffer.ptrw() + 4); - encode_uint32(p_image->get_mipmap_count() + 1, buffer.ptrw() + 8); - encode_uint32(p_image->get_width(), buffer.ptrw() + 12); - encode_uint32(p_image->get_height(), buffer.ptrw() + 16); - - switch (p_compression_mode) { - case COMPRESSION_MODE_LOSSLESS: - case COMPRESSION_MODE_LOSSY: { - for (int i = 0; i < p_image->get_mipmap_count() + 1; i++) { - Vector<uint8_t> data; - if (p_compression_mode == COMPRESSION_MODE_LOSSY) { - data = Image::webp_lossy_packer(p_image->get_image_from_mipmap(i), p_lossy_quality); - } else { - data = Image::webp_lossless_packer(p_image->get_image_from_mipmap(i)); - } - int data_len = data.size(); - buffer.resize(buffer.size() + 4); - encode_uint32(data_len, buffer.ptrw() + buffer.size() - 4); - buffer.append_array(data); - } - } break; - case COMPRESSION_MODE_BASIS_UNIVERSAL: { - Image::UsedChannels uc = p_image->detect_used_channels(p_normal_map ? Image::COMPRESS_SOURCE_NORMAL : Image::COMPRESS_SOURCE_GENERIC); - Vector<uint8_t> budata = Image::basis_universal_packer(p_image, uc); - buffer.append_array(budata); - - } break; - case COMPRESSION_MODE_S3TC: - case COMPRESSION_MODE_ETC2: - case COMPRESSION_MODE_BPTC: { - Ref<Image> copy = p_image->duplicate(); - switch (p_compression_mode) { - case COMPRESSION_MODE_S3TC: - copy->compress(Image::COMPRESS_S3TC); - break; - case COMPRESSION_MODE_ETC2: - copy->compress(Image::COMPRESS_ETC2); - break; - case COMPRESSION_MODE_BPTC: - copy->compress(Image::COMPRESS_BPTC); - break; - default: { - }; - } - - buffer.append_array(copy->get_data()); - - } break; - } - - _set_data(buffer); -} - -Image::Format PortableCompressedTexture2D::get_format() const { - return format; -} - -Ref<Image> PortableCompressedTexture2D::get_image() const { - if (image_stored) { - return RenderingServer::get_singleton()->texture_2d_get(texture); - } else { - return Ref<Image>(); - } -} - -int PortableCompressedTexture2D::get_width() const { - return size.width; -} - -int PortableCompressedTexture2D::get_height() const { - return size.height; -} - -RID PortableCompressedTexture2D::get_rid() const { - if (texture.is_null()) { - //we are in trouble, create something temporary - texture = RenderingServer::get_singleton()->texture_2d_placeholder_create(); - } - return texture; -} - -bool PortableCompressedTexture2D::has_alpha() const { - return (format == Image::FORMAT_LA8 || format == Image::FORMAT_RGBA8); -} - -void PortableCompressedTexture2D::draw(RID p_canvas_item, const Point2 &p_pos, const Color &p_modulate, bool p_transpose) const { - if (size.width == 0 || size.height == 0) { - return; - } - RenderingServer::get_singleton()->canvas_item_add_texture_rect(p_canvas_item, Rect2(p_pos, size), texture, false, p_modulate, p_transpose); -} - -void PortableCompressedTexture2D::draw_rect(RID p_canvas_item, const Rect2 &p_rect, bool p_tile, const Color &p_modulate, bool p_transpose) const { - if (size.width == 0 || size.height == 0) { - return; - } - RenderingServer::get_singleton()->canvas_item_add_texture_rect(p_canvas_item, p_rect, texture, p_tile, p_modulate, p_transpose); -} - -void PortableCompressedTexture2D::draw_rect_region(RID p_canvas_item, const Rect2 &p_rect, const Rect2 &p_src_rect, const Color &p_modulate, bool p_transpose, bool p_clip_uv) const { - if (size.width == 0 || size.height == 0) { - return; - } - RenderingServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas_item, p_rect, texture, p_src_rect, p_modulate, p_transpose, p_clip_uv); -} - -bool PortableCompressedTexture2D::is_pixel_opaque(int p_x, int p_y) const { - if (!alpha_cache.is_valid()) { - Ref<Image> img = get_image(); - if (img.is_valid()) { - if (img->is_compressed()) { //must decompress, if compressed - Ref<Image> decom = img->duplicate(); - decom->decompress(); - img = decom; - } - alpha_cache.instantiate(); - alpha_cache->create_from_image_alpha(img); - } - } - - if (alpha_cache.is_valid()) { - int aw = int(alpha_cache->get_size().width); - int ah = int(alpha_cache->get_size().height); - if (aw == 0 || ah == 0) { - return true; - } - - int x = p_x * aw / size.width; - int y = p_y * ah / size.height; - - x = CLAMP(x, 0, aw); - y = CLAMP(y, 0, ah); - - return alpha_cache->get_bit(x, y); - } - - return true; -} - -void PortableCompressedTexture2D::set_size_override(const Size2 &p_size) { - size_override = p_size; - RenderingServer::get_singleton()->texture_set_size_override(texture, size_override.width, size_override.height); -} - -Size2 PortableCompressedTexture2D::get_size_override() const { - return size_override; -} - -void PortableCompressedTexture2D::set_path(const String &p_path, bool p_take_over) { - if (texture.is_valid()) { - RenderingServer::get_singleton()->texture_set_path(texture, p_path); - } - - Resource::set_path(p_path, p_take_over); -} - -bool PortableCompressedTexture2D::keep_all_compressed_buffers = false; - -void PortableCompressedTexture2D::set_keep_all_compressed_buffers(bool p_keep) { - keep_all_compressed_buffers = p_keep; -} - -bool PortableCompressedTexture2D::is_keeping_all_compressed_buffers() { - return keep_all_compressed_buffers; -} - -void PortableCompressedTexture2D::set_keep_compressed_buffer(bool p_keep) { - keep_compressed_buffer = p_keep; - if (!p_keep) { - compressed_buffer.clear(); - } -} - -bool PortableCompressedTexture2D::is_keeping_compressed_buffer() const { - return keep_compressed_buffer; -} - -void PortableCompressedTexture2D::_bind_methods() { - ClassDB::bind_method(D_METHOD("create_from_image", "image", "compression_mode", "normal_map", "lossy_quality"), &PortableCompressedTexture2D::create_from_image, DEFVAL(false), DEFVAL(0.8)); - ClassDB::bind_method(D_METHOD("get_format"), &PortableCompressedTexture2D::get_format); - ClassDB::bind_method(D_METHOD("get_compression_mode"), &PortableCompressedTexture2D::get_compression_mode); - - ClassDB::bind_method(D_METHOD("set_size_override", "size"), &PortableCompressedTexture2D::set_size_override); - ClassDB::bind_method(D_METHOD("get_size_override"), &PortableCompressedTexture2D::get_size_override); - - ClassDB::bind_method(D_METHOD("set_keep_compressed_buffer", "keep"), &PortableCompressedTexture2D::set_keep_compressed_buffer); - ClassDB::bind_method(D_METHOD("is_keeping_compressed_buffer"), &PortableCompressedTexture2D::is_keeping_compressed_buffer); - - ClassDB::bind_method(D_METHOD("_set_data", "data"), &PortableCompressedTexture2D::_set_data); - ClassDB::bind_method(D_METHOD("_get_data"), &PortableCompressedTexture2D::_get_data); - - ClassDB::bind_static_method("PortableCompressedTexture2D", D_METHOD("set_keep_all_compressed_buffers", "keep"), &PortableCompressedTexture2D::set_keep_all_compressed_buffers); - ClassDB::bind_static_method("PortableCompressedTexture2D", D_METHOD("is_keeping_all_compressed_buffers"), &PortableCompressedTexture2D::is_keeping_all_compressed_buffers); - - ADD_PROPERTY(PropertyInfo(Variant::PACKED_BYTE_ARRAY, "_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "_set_data", "_get_data"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "size_override", PROPERTY_HINT_NONE, "suffix:px"), "set_size_override", "get_size_override"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "keep_compressed_buffer"), "set_keep_compressed_buffer", "is_keeping_compressed_buffer"); - - BIND_ENUM_CONSTANT(COMPRESSION_MODE_LOSSLESS); - BIND_ENUM_CONSTANT(COMPRESSION_MODE_LOSSY); - BIND_ENUM_CONSTANT(COMPRESSION_MODE_BASIS_UNIVERSAL); - BIND_ENUM_CONSTANT(COMPRESSION_MODE_S3TC); - BIND_ENUM_CONSTANT(COMPRESSION_MODE_ETC2); - BIND_ENUM_CONSTANT(COMPRESSION_MODE_BPTC); -} - -PortableCompressedTexture2D::PortableCompressedTexture2D() {} - -PortableCompressedTexture2D::~PortableCompressedTexture2D() { - if (texture.is_valid()) { - ERR_FAIL_NULL(RenderingServer::get_singleton()); - RenderingServer::get_singleton()->free(texture); - } -} - -////////////////////////////////////////// - -Ref<Image> CompressedTexture2D::load_image_from_file(Ref<FileAccess> f, int p_size_limit) { - uint32_t data_format = f->get_32(); - uint32_t w = f->get_16(); - uint32_t h = f->get_16(); - uint32_t mipmaps = f->get_32(); - Image::Format format = Image::Format(f->get_32()); - - if (data_format == DATA_FORMAT_PNG || data_format == DATA_FORMAT_WEBP) { - //look for a PNG or WebP file inside - - int sw = w; - int sh = h; - - //mipmaps need to be read independently, they will be later combined - Vector<Ref<Image>> mipmap_images; - uint64_t total_size = 0; - - bool first = true; - - for (uint32_t i = 0; i < mipmaps + 1; i++) { - uint32_t size = f->get_32(); - - if (p_size_limit > 0 && i < (mipmaps - 1) && (sw > p_size_limit || sh > p_size_limit)) { - //can't load this due to size limit - sw = MAX(sw >> 1, 1); - sh = MAX(sh >> 1, 1); - f->seek(f->get_position() + size); - continue; - } - - Vector<uint8_t> pv; - pv.resize(size); - { - uint8_t *wr = pv.ptrw(); - f->get_buffer(wr, size); - } - - Ref<Image> img; - if (data_format == DATA_FORMAT_PNG && Image::png_unpacker) { - img = Image::png_unpacker(pv); - } else if (data_format == DATA_FORMAT_WEBP && Image::webp_unpacker) { - img = Image::webp_unpacker(pv); - } - - if (img.is_null() || img->is_empty()) { - ERR_FAIL_COND_V(img.is_null() || img->is_empty(), Ref<Image>()); - } - - if (first) { - //format will actually be the format of the first image, - //as it may have changed on compression - format = img->get_format(); - first = false; - } else if (img->get_format() != format) { - img->convert(format); //all needs to be the same format - } - - total_size += img->get_data().size(); - - mipmap_images.push_back(img); - - sw = MAX(sw >> 1, 1); - sh = MAX(sh >> 1, 1); - } - - //print_line("mipmap read total: " + itos(mipmap_images.size())); - - Ref<Image> image; - image.instantiate(); - - if (mipmap_images.size() == 1) { - //only one image (which will most likely be the case anyway for this format) - image = mipmap_images[0]; - return image; - - } else { - //rarer use case, but needs to be supported - Vector<uint8_t> img_data; - img_data.resize(total_size); - - { - uint8_t *wr = img_data.ptrw(); - - int ofs = 0; - for (int i = 0; i < mipmap_images.size(); i++) { - Vector<uint8_t> id = mipmap_images[i]->get_data(); - int len = id.size(); - const uint8_t *r = id.ptr(); - memcpy(&wr[ofs], r, len); - ofs += len; - } - } - - image->set_data(w, h, true, mipmap_images[0]->get_format(), img_data); - return image; - } - - } else if (data_format == DATA_FORMAT_BASIS_UNIVERSAL) { - int sw = w; - int sh = h; - uint32_t size = f->get_32(); - if (p_size_limit > 0 && (sw > p_size_limit || sh > p_size_limit)) { - //can't load this due to size limit - sw = MAX(sw >> 1, 1); - sh = MAX(sh >> 1, 1); - f->seek(f->get_position() + size); - return Ref<Image>(); - } - Vector<uint8_t> pv; - pv.resize(size); - { - uint8_t *wr = pv.ptrw(); - f->get_buffer(wr, size); - } - Ref<Image> img; - img = Image::basis_universal_unpacker(pv); - if (img.is_null() || img->is_empty()) { - ERR_FAIL_COND_V(img.is_null() || img->is_empty(), Ref<Image>()); - } - format = img->get_format(); - sw = MAX(sw >> 1, 1); - sh = MAX(sh >> 1, 1); - return img; - } else if (data_format == DATA_FORMAT_IMAGE) { - int size = Image::get_image_data_size(w, h, format, mipmaps ? true : false); - - for (uint32_t i = 0; i < mipmaps + 1; i++) { - int tw, th; - int ofs = Image::get_image_mipmap_offset_and_dimensions(w, h, format, i, tw, th); - - if (p_size_limit > 0 && i < mipmaps && (p_size_limit > tw || p_size_limit > th)) { - if (ofs) { - f->seek(f->get_position() + ofs); - } - continue; //oops, size limit enforced, go to next - } - - Vector<uint8_t> data; - data.resize(size - ofs); - - { - uint8_t *wr = data.ptrw(); - f->get_buffer(wr, data.size()); - } - - Ref<Image> image = Image::create_from_data(tw, th, mipmaps - i ? true : false, format, data); - - return image; - } - } - - return Ref<Image>(); -} - -void CompressedTexture2D::set_path(const String &p_path, bool p_take_over) { - if (texture.is_valid()) { - RenderingServer::get_singleton()->texture_set_path(texture, p_path); - } - - Resource::set_path(p_path, p_take_over); -} - -void CompressedTexture2D::_requested_3d(void *p_ud) { - CompressedTexture2D *ct = (CompressedTexture2D *)p_ud; - Ref<CompressedTexture2D> ctex(ct); - ERR_FAIL_NULL(request_3d_callback); - request_3d_callback(ctex); -} - -void CompressedTexture2D::_requested_roughness(void *p_ud, const String &p_normal_path, RS::TextureDetectRoughnessChannel p_roughness_channel) { - CompressedTexture2D *ct = (CompressedTexture2D *)p_ud; - Ref<CompressedTexture2D> ctex(ct); - ERR_FAIL_NULL(request_roughness_callback); - request_roughness_callback(ctex, p_normal_path, p_roughness_channel); -} - -void CompressedTexture2D::_requested_normal(void *p_ud) { - CompressedTexture2D *ct = (CompressedTexture2D *)p_ud; - Ref<CompressedTexture2D> ctex(ct); - ERR_FAIL_NULL(request_normal_callback); - request_normal_callback(ctex); -} - -CompressedTexture2D::TextureFormatRequestCallback CompressedTexture2D::request_3d_callback = nullptr; -CompressedTexture2D::TextureFormatRoughnessRequestCallback CompressedTexture2D::request_roughness_callback = nullptr; -CompressedTexture2D::TextureFormatRequestCallback CompressedTexture2D::request_normal_callback = nullptr; - -Image::Format CompressedTexture2D::get_format() const { - return format; -} - -Error CompressedTexture2D::_load_data(const String &p_path, int &r_width, int &r_height, Ref<Image> &image, bool &r_request_3d, bool &r_request_normal, bool &r_request_roughness, int &mipmap_limit, int p_size_limit) { - alpha_cache.unref(); - - ERR_FAIL_COND_V(image.is_null(), ERR_INVALID_PARAMETER); - - Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ); - ERR_FAIL_COND_V_MSG(f.is_null(), ERR_CANT_OPEN, vformat("Unable to open file: %s.", p_path)); - - uint8_t header[4]; - f->get_buffer(header, 4); - if (header[0] != 'G' || header[1] != 'S' || header[2] != 'T' || header[3] != '2') { - ERR_FAIL_V_MSG(ERR_FILE_CORRUPT, "Compressed texture file is corrupt (Bad header)."); - } - - uint32_t version = f->get_32(); - - if (version > FORMAT_VERSION) { - ERR_FAIL_V_MSG(ERR_FILE_CORRUPT, "Compressed texture file is too new."); - } - r_width = f->get_32(); - r_height = f->get_32(); - uint32_t df = f->get_32(); //data format - - //skip reserved - mipmap_limit = int(f->get_32()); - //reserved - f->get_32(); - f->get_32(); - f->get_32(); - -#ifdef TOOLS_ENABLED - - r_request_3d = request_3d_callback && df & FORMAT_BIT_DETECT_3D; - r_request_roughness = request_roughness_callback && df & FORMAT_BIT_DETECT_ROUGNESS; - r_request_normal = request_normal_callback && df & FORMAT_BIT_DETECT_NORMAL; - -#else - - r_request_3d = false; - r_request_roughness = false; - r_request_normal = false; - -#endif - if (!(df & FORMAT_BIT_STREAM)) { - p_size_limit = 0; - } - - image = load_image_from_file(f, p_size_limit); - - if (image.is_null() || image->is_empty()) { - return ERR_CANT_OPEN; - } - - return OK; -} - -Error CompressedTexture2D::load(const String &p_path) { - int lw, lh; - Ref<Image> image; - image.instantiate(); - - bool request_3d; - bool request_normal; - bool request_roughness; - int mipmap_limit; - - Error err = _load_data(p_path, lw, lh, image, request_3d, request_normal, request_roughness, mipmap_limit); - if (err) { - return err; - } - - if (texture.is_valid()) { - RID new_texture = RS::get_singleton()->texture_2d_create(image); - RS::get_singleton()->texture_replace(texture, new_texture); - } else { - texture = RS::get_singleton()->texture_2d_create(image); - } - if (lw || lh) { - RS::get_singleton()->texture_set_size_override(texture, lw, lh); - } - - w = lw; - h = lh; - path_to_file = p_path; - format = image->get_format(); - - if (get_path().is_empty()) { - //temporarily set path if no path set for resource, helps find errors - RenderingServer::get_singleton()->texture_set_path(texture, p_path); - } - -#ifdef TOOLS_ENABLED - - if (request_3d) { - //print_line("request detect 3D at " + p_path); - RS::get_singleton()->texture_set_detect_3d_callback(texture, _requested_3d, this); - } else { - //print_line("not requesting detect 3D at " + p_path); - RS::get_singleton()->texture_set_detect_3d_callback(texture, nullptr, nullptr); - } - - if (request_roughness) { - //print_line("request detect srgb at " + p_path); - RS::get_singleton()->texture_set_detect_roughness_callback(texture, _requested_roughness, this); - } else { - //print_line("not requesting detect srgb at " + p_path); - RS::get_singleton()->texture_set_detect_roughness_callback(texture, nullptr, nullptr); - } - - if (request_normal) { - //print_line("request detect srgb at " + p_path); - RS::get_singleton()->texture_set_detect_normal_callback(texture, _requested_normal, this); - } else { - //print_line("not requesting detect normal at " + p_path); - RS::get_singleton()->texture_set_detect_normal_callback(texture, nullptr, nullptr); - } - -#endif - notify_property_list_changed(); - emit_changed(); - return OK; -} - -String CompressedTexture2D::get_load_path() const { - return path_to_file; -} - -int CompressedTexture2D::get_width() const { - return w; -} - -int CompressedTexture2D::get_height() const { - return h; -} - -RID CompressedTexture2D::get_rid() const { - if (!texture.is_valid()) { - texture = RS::get_singleton()->texture_2d_placeholder_create(); - } - return texture; -} - -void CompressedTexture2D::draw(RID p_canvas_item, const Point2 &p_pos, const Color &p_modulate, bool p_transpose) const { - if ((w | h) == 0) { - return; - } - RenderingServer::get_singleton()->canvas_item_add_texture_rect(p_canvas_item, Rect2(p_pos, Size2(w, h)), texture, false, p_modulate, p_transpose); -} - -void CompressedTexture2D::draw_rect(RID p_canvas_item, const Rect2 &p_rect, bool p_tile, const Color &p_modulate, bool p_transpose) const { - if ((w | h) == 0) { - return; - } - RenderingServer::get_singleton()->canvas_item_add_texture_rect(p_canvas_item, p_rect, texture, p_tile, p_modulate, p_transpose); -} - -void CompressedTexture2D::draw_rect_region(RID p_canvas_item, const Rect2 &p_rect, const Rect2 &p_src_rect, const Color &p_modulate, bool p_transpose, bool p_clip_uv) const { - if ((w | h) == 0) { - return; - } - RenderingServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas_item, p_rect, texture, p_src_rect, p_modulate, p_transpose, p_clip_uv); -} - -bool CompressedTexture2D::has_alpha() const { - return false; -} - -Ref<Image> CompressedTexture2D::get_image() const { - if (texture.is_valid()) { - return RS::get_singleton()->texture_2d_get(texture); - } else { - return Ref<Image>(); - } -} - -bool CompressedTexture2D::is_pixel_opaque(int p_x, int p_y) const { - if (!alpha_cache.is_valid()) { - Ref<Image> img = get_image(); - if (img.is_valid()) { - if (img->is_compressed()) { //must decompress, if compressed - Ref<Image> decom = img->duplicate(); - decom->decompress(); - img = decom; - } - - alpha_cache.instantiate(); - alpha_cache->create_from_image_alpha(img); - } - } - - if (alpha_cache.is_valid()) { - int aw = int(alpha_cache->get_size().width); - int ah = int(alpha_cache->get_size().height); - if (aw == 0 || ah == 0) { - return true; - } - - int x = p_x * aw / w; - int y = p_y * ah / h; - - x = CLAMP(x, 0, aw); - y = CLAMP(y, 0, ah); - - return alpha_cache->get_bit(x, y); - } - - return true; -} - -void CompressedTexture2D::reload_from_file() { - String path = get_path(); - if (!path.is_resource_file()) { - return; - } - - path = ResourceLoader::path_remap(path); //remap for translation - path = ResourceLoader::import_remap(path); //remap for import - if (!path.is_resource_file()) { - return; - } - - load(path); -} - -void CompressedTexture2D::_validate_property(PropertyInfo &p_property) const { -} - -void CompressedTexture2D::_bind_methods() { - ClassDB::bind_method(D_METHOD("load", "path"), &CompressedTexture2D::load); - ClassDB::bind_method(D_METHOD("get_load_path"), &CompressedTexture2D::get_load_path); - - ADD_PROPERTY(PropertyInfo(Variant::STRING, "load_path", PROPERTY_HINT_FILE, "*.ctex"), "load", "get_load_path"); -} - -CompressedTexture2D::CompressedTexture2D() {} - -CompressedTexture2D::~CompressedTexture2D() { - if (texture.is_valid()) { - ERR_FAIL_NULL(RenderingServer::get_singleton()); - RS::get_singleton()->free(texture); - } -} - -Ref<Resource> ResourceFormatLoaderCompressedTexture2D::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, CacheMode p_cache_mode) { - Ref<CompressedTexture2D> st; - st.instantiate(); - Error err = st->load(p_path); - if (r_error) { - *r_error = err; - } - if (err != OK) { - return Ref<Resource>(); - } - - return st; -} - -void ResourceFormatLoaderCompressedTexture2D::get_recognized_extensions(List<String> *p_extensions) const { - p_extensions->push_back("ctex"); -} - -bool ResourceFormatLoaderCompressedTexture2D::handles_type(const String &p_type) const { - return p_type == "CompressedTexture2D"; -} - -String ResourceFormatLoaderCompressedTexture2D::get_resource_type(const String &p_path) const { - if (p_path.get_extension().to_lower() == "ctex") { - return "CompressedTexture2D"; - } - return ""; -} - -//////////////////////////////////// - TypedArray<Image> Texture3D::_get_datai() const { Vector<Ref<Image>> data = get_data(); @@ -1163,6 +171,7 @@ Vector<Ref<Image>> Texture3D::get_data() const { } return data; } + void Texture3D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_format"), &Texture3D::get_format); ClassDB::bind_method(D_METHOD("get_width"), &Texture3D::get_width); @@ -1187,1652 +196,6 @@ Ref<Resource> Texture3D::create_placeholder() const { return placeholder; } -////////////////////////////////////////// - -Image::Format ImageTexture3D::get_format() const { - return format; -} -int ImageTexture3D::get_width() const { - return width; -} -int ImageTexture3D::get_height() const { - return height; -} -int ImageTexture3D::get_depth() const { - return depth; -} -bool ImageTexture3D::has_mipmaps() const { - return mipmaps; -} - -Error ImageTexture3D::_create(Image::Format p_format, int p_width, int p_height, int p_depth, bool p_mipmaps, const TypedArray<Image> &p_data) { - Vector<Ref<Image>> images; - images.resize(p_data.size()); - for (int i = 0; i < images.size(); i++) { - images.write[i] = p_data[i]; - } - return create(p_format, p_width, p_height, p_depth, p_mipmaps, images); -} - -void ImageTexture3D::_update(const TypedArray<Image> &p_data) { - Vector<Ref<Image>> images; - images.resize(p_data.size()); - for (int i = 0; i < images.size(); i++) { - images.write[i] = p_data[i]; - } - return update(images); -} - -Error ImageTexture3D::create(Image::Format p_format, int p_width, int p_height, int p_depth, bool p_mipmaps, const Vector<Ref<Image>> &p_data) { - RID tex = RenderingServer::get_singleton()->texture_3d_create(p_format, p_width, p_height, p_depth, p_mipmaps, p_data); - ERR_FAIL_COND_V(tex.is_null(), ERR_CANT_CREATE); - - if (texture.is_valid()) { - RenderingServer::get_singleton()->texture_replace(texture, tex); - } else { - texture = tex; - } - - format = p_format; - width = p_width; - height = p_height; - depth = p_depth; - mipmaps = p_mipmaps; - - return OK; -} - -void ImageTexture3D::update(const Vector<Ref<Image>> &p_data) { - ERR_FAIL_COND(!texture.is_valid()); - RenderingServer::get_singleton()->texture_3d_update(texture, p_data); -} - -Vector<Ref<Image>> ImageTexture3D::get_data() const { - ERR_FAIL_COND_V(!texture.is_valid(), Vector<Ref<Image>>()); - return RS::get_singleton()->texture_3d_get(texture); -} - -RID ImageTexture3D::get_rid() const { - if (!texture.is_valid()) { - texture = RS::get_singleton()->texture_3d_placeholder_create(); - } - return texture; -} -void ImageTexture3D::set_path(const String &p_path, bool p_take_over) { - if (texture.is_valid()) { - RenderingServer::get_singleton()->texture_set_path(texture, p_path); - } - - Resource::set_path(p_path, p_take_over); -} - -void ImageTexture3D::_bind_methods() { - ClassDB::bind_method(D_METHOD("create", "format", "width", "height", "depth", "use_mipmaps", "data"), &ImageTexture3D::_create); - ClassDB::bind_method(D_METHOD("update", "data"), &ImageTexture3D::_update); -} - -ImageTexture3D::ImageTexture3D() { -} - -ImageTexture3D::~ImageTexture3D() { - if (texture.is_valid()) { - ERR_FAIL_NULL(RenderingServer::get_singleton()); - RS::get_singleton()->free(texture); - } -} - -//////////////////////////////////////////// - -void CompressedTexture3D::set_path(const String &p_path, bool p_take_over) { - if (texture.is_valid()) { - RenderingServer::get_singleton()->texture_set_path(texture, p_path); - } - - Resource::set_path(p_path, p_take_over); -} - -Image::Format CompressedTexture3D::get_format() const { - return format; -} - -Error CompressedTexture3D::_load_data(const String &p_path, Vector<Ref<Image>> &r_data, Image::Format &r_format, int &r_width, int &r_height, int &r_depth, bool &r_mipmaps) { - Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ); - ERR_FAIL_COND_V_MSG(f.is_null(), ERR_CANT_OPEN, vformat("Unable to open file: %s.", p_path)); - - uint8_t header[4]; - f->get_buffer(header, 4); - ERR_FAIL_COND_V(header[0] != 'G' || header[1] != 'S' || header[2] != 'T' || header[3] != 'L', ERR_FILE_UNRECOGNIZED); - - //stored as compressed textures (used for lossless and lossy compression) - uint32_t version = f->get_32(); - - if (version > FORMAT_VERSION) { - ERR_FAIL_V_MSG(ERR_FILE_CORRUPT, "Compressed texture file is too new."); - } - - r_depth = f->get_32(); //depth - f->get_32(); //ignored (mode) - f->get_32(); // ignored (data format) - - f->get_32(); //ignored - int mipmap_count = f->get_32(); - f->get_32(); //ignored - f->get_32(); //ignored - - r_mipmaps = mipmap_count != 0; - - r_data.clear(); - - for (int i = 0; i < (r_depth + mipmap_count); i++) { - Ref<Image> image = CompressedTexture2D::load_image_from_file(f, 0); - ERR_FAIL_COND_V(image.is_null() || image->is_empty(), ERR_CANT_OPEN); - if (i == 0) { - r_format = image->get_format(); - r_width = image->get_width(); - r_height = image->get_height(); - } - r_data.push_back(image); - } - - return OK; -} - -Error CompressedTexture3D::load(const String &p_path) { - Vector<Ref<Image>> data; - - int tw, th, td; - Image::Format tfmt; - bool tmm; - - Error err = _load_data(p_path, data, tfmt, tw, th, td, tmm); - if (err) { - return err; - } - - if (texture.is_valid()) { - RID new_texture = RS::get_singleton()->texture_3d_create(tfmt, tw, th, td, tmm, data); - RS::get_singleton()->texture_replace(texture, new_texture); - } else { - texture = RS::get_singleton()->texture_3d_create(tfmt, tw, th, td, tmm, data); - } - - w = tw; - h = th; - d = td; - mipmaps = tmm; - format = tfmt; - - path_to_file = p_path; - - if (get_path().is_empty()) { - //temporarily set path if no path set for resource, helps find errors - RenderingServer::get_singleton()->texture_set_path(texture, p_path); - } - - notify_property_list_changed(); - emit_changed(); - return OK; -} - -String CompressedTexture3D::get_load_path() const { - return path_to_file; -} - -int CompressedTexture3D::get_width() const { - return w; -} - -int CompressedTexture3D::get_height() const { - return h; -} - -int CompressedTexture3D::get_depth() const { - return d; -} - -bool CompressedTexture3D::has_mipmaps() const { - return mipmaps; -} - -RID CompressedTexture3D::get_rid() const { - if (!texture.is_valid()) { - texture = RS::get_singleton()->texture_3d_placeholder_create(); - } - return texture; -} - -Vector<Ref<Image>> CompressedTexture3D::get_data() const { - if (texture.is_valid()) { - return RS::get_singleton()->texture_3d_get(texture); - } else { - return Vector<Ref<Image>>(); - } -} - -void CompressedTexture3D::reload_from_file() { - String path = get_path(); - if (!path.is_resource_file()) { - return; - } - - path = ResourceLoader::path_remap(path); //remap for translation - path = ResourceLoader::import_remap(path); //remap for import - if (!path.is_resource_file()) { - return; - } - - load(path); -} - -void CompressedTexture3D::_validate_property(PropertyInfo &p_property) const { -} - -void CompressedTexture3D::_bind_methods() { - ClassDB::bind_method(D_METHOD("load", "path"), &CompressedTexture3D::load); - ClassDB::bind_method(D_METHOD("get_load_path"), &CompressedTexture3D::get_load_path); - - ADD_PROPERTY(PropertyInfo(Variant::STRING, "load_path", PROPERTY_HINT_FILE, "*.ctex"), "load", "get_load_path"); -} - -CompressedTexture3D::CompressedTexture3D() {} - -CompressedTexture3D::~CompressedTexture3D() { - if (texture.is_valid()) { - ERR_FAIL_NULL(RenderingServer::get_singleton()); - RS::get_singleton()->free(texture); - } -} - -///////////////////////////// - -Ref<Resource> ResourceFormatLoaderCompressedTexture3D::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, CacheMode p_cache_mode) { - Ref<CompressedTexture3D> st; - st.instantiate(); - Error err = st->load(p_path); - if (r_error) { - *r_error = err; - } - if (err != OK) { - return Ref<Resource>(); - } - - return st; -} - -void ResourceFormatLoaderCompressedTexture3D::get_recognized_extensions(List<String> *p_extensions) const { - p_extensions->push_back("ctex3d"); -} - -bool ResourceFormatLoaderCompressedTexture3D::handles_type(const String &p_type) const { - return p_type == "CompressedTexture3D"; -} - -String ResourceFormatLoaderCompressedTexture3D::get_resource_type(const String &p_path) const { - if (p_path.get_extension().to_lower() == "ctex3d") { - return "CompressedTexture3D"; - } - return ""; -} - -//////////////////////////////////////////// - -int AtlasTexture::get_width() const { - if (region.size.width == 0) { - if (atlas.is_valid()) { - return atlas->get_width(); - } - return 1; - } else { - return region.size.width + margin.size.width; - } -} - -int AtlasTexture::get_height() const { - if (region.size.height == 0) { - if (atlas.is_valid()) { - return atlas->get_height(); - } - return 1; - } else { - return region.size.height + margin.size.height; - } -} - -RID AtlasTexture::get_rid() const { - if (atlas.is_valid()) { - return atlas->get_rid(); - } - - return RID(); -} - -bool AtlasTexture::has_alpha() const { - if (atlas.is_valid()) { - return atlas->has_alpha(); - } - - return false; -} - -void AtlasTexture::set_atlas(const Ref<Texture2D> &p_atlas) { - ERR_FAIL_COND(p_atlas == this); - if (atlas == p_atlas) { - return; - } - // Support recursive AtlasTextures. - if (Ref<AtlasTexture>(atlas).is_valid()) { - atlas->disconnect(CoreStringNames::get_singleton()->changed, callable_mp((Resource *)this, &AtlasTexture::emit_changed)); - } - atlas = p_atlas; - if (Ref<AtlasTexture>(atlas).is_valid()) { - atlas->connect(CoreStringNames::get_singleton()->changed, callable_mp((Resource *)this, &AtlasTexture::emit_changed)); - } - - emit_changed(); -} - -Ref<Texture2D> AtlasTexture::get_atlas() const { - return atlas; -} - -void AtlasTexture::set_region(const Rect2 &p_region) { - if (region == p_region) { - return; - } - region = p_region; - emit_changed(); -} - -Rect2 AtlasTexture::get_region() const { - return region; -} - -void AtlasTexture::set_margin(const Rect2 &p_margin) { - if (margin == p_margin) { - return; - } - margin = p_margin; - emit_changed(); -} - -Rect2 AtlasTexture::get_margin() const { - return margin; -} - -void AtlasTexture::set_filter_clip(const bool p_enable) { - filter_clip = p_enable; - emit_changed(); -} - -bool AtlasTexture::has_filter_clip() const { - return filter_clip; -} - -void AtlasTexture::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_atlas", "atlas"), &AtlasTexture::set_atlas); - ClassDB::bind_method(D_METHOD("get_atlas"), &AtlasTexture::get_atlas); - - ClassDB::bind_method(D_METHOD("set_region", "region"), &AtlasTexture::set_region); - ClassDB::bind_method(D_METHOD("get_region"), &AtlasTexture::get_region); - - ClassDB::bind_method(D_METHOD("set_margin", "margin"), &AtlasTexture::set_margin); - ClassDB::bind_method(D_METHOD("get_margin"), &AtlasTexture::get_margin); - - ClassDB::bind_method(D_METHOD("set_filter_clip", "enable"), &AtlasTexture::set_filter_clip); - ClassDB::bind_method(D_METHOD("has_filter_clip"), &AtlasTexture::has_filter_clip); - - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "atlas", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_atlas", "get_atlas"); - ADD_PROPERTY(PropertyInfo(Variant::RECT2, "region", PROPERTY_HINT_NONE, "suffix:px"), "set_region", "get_region"); - ADD_PROPERTY(PropertyInfo(Variant::RECT2, "margin", PROPERTY_HINT_NONE, "suffix:px"), "set_margin", "get_margin"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "filter_clip"), "set_filter_clip", "has_filter_clip"); -} - -void AtlasTexture::draw(RID p_canvas_item, const Point2 &p_pos, const Color &p_modulate, bool p_transpose) const { - if (!atlas.is_valid()) { - return; - } - - Rect2 rc = region; - - if (rc.size.width == 0) { - rc.size.width = atlas->get_width(); - } - - if (rc.size.height == 0) { - rc.size.height = atlas->get_height(); - } - - atlas->draw_rect_region(p_canvas_item, Rect2(p_pos + margin.position, rc.size), rc, p_modulate, p_transpose, filter_clip); -} - -void AtlasTexture::draw_rect(RID p_canvas_item, const Rect2 &p_rect, bool p_tile, const Color &p_modulate, bool p_transpose) const { - if (!atlas.is_valid()) { - return; - } - - Rect2 rc = region; - - if (rc.size.width == 0) { - rc.size.width = atlas->get_width(); - } - - if (rc.size.height == 0) { - rc.size.height = atlas->get_height(); - } - - Vector2 scale = p_rect.size / (region.size + margin.size); - Rect2 dr(p_rect.position + margin.position * scale, rc.size * scale); - - atlas->draw_rect_region(p_canvas_item, dr, rc, p_modulate, p_transpose, filter_clip); -} - -void AtlasTexture::draw_rect_region(RID p_canvas_item, const Rect2 &p_rect, const Rect2 &p_src_rect, const Color &p_modulate, bool p_transpose, bool p_clip_uv) const { - //this might not necessarily work well if using a rect, needs to be fixed properly - if (!atlas.is_valid()) { - return; - } - - Rect2 dr; - Rect2 src_c; - get_rect_region(p_rect, p_src_rect, dr, src_c); - - atlas->draw_rect_region(p_canvas_item, dr, src_c, p_modulate, p_transpose, filter_clip); -} - -bool AtlasTexture::get_rect_region(const Rect2 &p_rect, const Rect2 &p_src_rect, Rect2 &r_rect, Rect2 &r_src_rect) const { - if (!atlas.is_valid()) { - return false; - } - - Rect2 src = p_src_rect; - if (src.size == Size2()) { - src.size = region.size; - } - Vector2 scale = p_rect.size / src.size; - - src.position += (region.position - margin.position); - Rect2 src_clipped = region.intersection(src); - if (src_clipped.size == Size2()) { - return false; - } - - Vector2 ofs = (src_clipped.position - src.position); - if (scale.x < 0) { - ofs.x += (src_clipped.size.x - src.size.x); - } - if (scale.y < 0) { - ofs.y += (src_clipped.size.y - src.size.y); - } - - r_rect = Rect2(p_rect.position + ofs * scale, src_clipped.size * scale); - r_src_rect = src_clipped; - return true; -} - -bool AtlasTexture::is_pixel_opaque(int p_x, int p_y) const { - if (!atlas.is_valid()) { - return true; - } - - int x = p_x + region.position.x - margin.position.x; - int y = p_y + region.position.y - margin.position.y; - - // margin edge may outside of atlas - if (x < 0 || x >= atlas->get_width()) { - return false; - } - if (y < 0 || y >= atlas->get_height()) { - return false; - } - - return atlas->is_pixel_opaque(x, y); -} - -Ref<Image> AtlasTexture::get_image() const { - if (!atlas.is_valid() || !atlas->get_image().is_valid()) { - return Ref<Image>(); - } - - return atlas->get_image()->get_region(region); -} - -AtlasTexture::AtlasTexture() {} - -///////////////////////////////////////// - -int MeshTexture::get_width() const { - return size.width; -} - -int MeshTexture::get_height() const { - return size.height; -} - -RID MeshTexture::get_rid() const { - return RID(); -} - -bool MeshTexture::has_alpha() const { - return false; -} - -void MeshTexture::set_mesh(const Ref<Mesh> &p_mesh) { - mesh = p_mesh; -} - -Ref<Mesh> MeshTexture::get_mesh() const { - return mesh; -} - -void MeshTexture::set_image_size(const Size2 &p_size) { - size = p_size; -} - -Size2 MeshTexture::get_image_size() const { - return size; -} - -void MeshTexture::set_base_texture(const Ref<Texture2D> &p_texture) { - base_texture = p_texture; -} - -Ref<Texture2D> MeshTexture::get_base_texture() const { - return base_texture; -} - -void MeshTexture::draw(RID p_canvas_item, const Point2 &p_pos, const Color &p_modulate, bool p_transpose) const { - if (mesh.is_null() || base_texture.is_null()) { - return; - } - Transform2D xform; - xform.set_origin(p_pos); - if (p_transpose) { - SWAP(xform.columns[0][1], xform.columns[1][0]); - SWAP(xform.columns[0][0], xform.columns[1][1]); - } - RenderingServer::get_singleton()->canvas_item_add_mesh(p_canvas_item, mesh->get_rid(), xform, p_modulate, base_texture->get_rid()); -} - -void MeshTexture::draw_rect(RID p_canvas_item, const Rect2 &p_rect, bool p_tile, const Color &p_modulate, bool p_transpose) const { - if (mesh.is_null() || base_texture.is_null()) { - return; - } - Transform2D xform; - Vector2 origin = p_rect.position; - if (p_rect.size.x < 0) { - origin.x += size.x; - } - if (p_rect.size.y < 0) { - origin.y += size.y; - } - xform.set_origin(origin); - xform.set_scale(p_rect.size / size); - - if (p_transpose) { - SWAP(xform.columns[0][1], xform.columns[1][0]); - SWAP(xform.columns[0][0], xform.columns[1][1]); - } - RenderingServer::get_singleton()->canvas_item_add_mesh(p_canvas_item, mesh->get_rid(), xform, p_modulate, base_texture->get_rid()); -} - -void MeshTexture::draw_rect_region(RID p_canvas_item, const Rect2 &p_rect, const Rect2 &p_src_rect, const Color &p_modulate, bool p_transpose, bool p_clip_uv) const { - if (mesh.is_null() || base_texture.is_null()) { - return; - } - Transform2D xform; - Vector2 origin = p_rect.position; - if (p_rect.size.x < 0) { - origin.x += size.x; - } - if (p_rect.size.y < 0) { - origin.y += size.y; - } - xform.set_origin(origin); - xform.set_scale(p_rect.size / size); - - if (p_transpose) { - SWAP(xform.columns[0][1], xform.columns[1][0]); - SWAP(xform.columns[0][0], xform.columns[1][1]); - } - RenderingServer::get_singleton()->canvas_item_add_mesh(p_canvas_item, mesh->get_rid(), xform, p_modulate, base_texture->get_rid()); -} - -bool MeshTexture::get_rect_region(const Rect2 &p_rect, const Rect2 &p_src_rect, Rect2 &r_rect, Rect2 &r_src_rect) const { - r_rect = p_rect; - r_src_rect = p_src_rect; - return true; -} - -bool MeshTexture::is_pixel_opaque(int p_x, int p_y) const { - return true; -} - -void MeshTexture::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_mesh", "mesh"), &MeshTexture::set_mesh); - ClassDB::bind_method(D_METHOD("get_mesh"), &MeshTexture::get_mesh); - ClassDB::bind_method(D_METHOD("set_image_size", "size"), &MeshTexture::set_image_size); - ClassDB::bind_method(D_METHOD("get_image_size"), &MeshTexture::get_image_size); - ClassDB::bind_method(D_METHOD("set_base_texture", "texture"), &MeshTexture::set_base_texture); - ClassDB::bind_method(D_METHOD("get_base_texture"), &MeshTexture::get_base_texture); - - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "mesh", PROPERTY_HINT_RESOURCE_TYPE, "Mesh"), "set_mesh", "get_mesh"); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "base_texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_base_texture", "get_base_texture"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "image_size", PROPERTY_HINT_RANGE, "0,16384,1,suffix:px"), "set_image_size", "get_image_size"); -} - -MeshTexture::MeshTexture() { -} - -////////////////////////////////////////// - -void CurveTexture::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_width", "width"), &CurveTexture::set_width); - - ClassDB::bind_method(D_METHOD("set_curve", "curve"), &CurveTexture::set_curve); - ClassDB::bind_method(D_METHOD("get_curve"), &CurveTexture::get_curve); - - ClassDB::bind_method(D_METHOD("set_texture_mode", "texture_mode"), &CurveTexture::set_texture_mode); - ClassDB::bind_method(D_METHOD("get_texture_mode"), &CurveTexture::get_texture_mode); - - ClassDB::bind_method(D_METHOD("_update"), &CurveTexture::_update); - - ADD_PROPERTY(PropertyInfo(Variant::INT, "width", PROPERTY_HINT_RANGE, "1,4096,suffix:px"), "set_width", "get_width"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "texture_mode", PROPERTY_HINT_ENUM, "RGB,Red"), "set_texture_mode", "get_texture_mode"); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_curve", "get_curve"); - - BIND_ENUM_CONSTANT(TEXTURE_MODE_RGB); - BIND_ENUM_CONSTANT(TEXTURE_MODE_RED); -} - -void CurveTexture::set_width(int p_width) { - ERR_FAIL_COND(p_width < 32 || p_width > 4096); - - if (_width == p_width) { - return; - } - - _width = p_width; - _update(); -} - -int CurveTexture::get_width() const { - return _width; -} - -void CurveTexture::ensure_default_setup(float p_min, float p_max) { - if (_curve.is_null()) { - Ref<Curve> curve = Ref<Curve>(memnew(Curve)); - curve->add_point(Vector2(0, 1)); - curve->add_point(Vector2(1, 1)); - curve->set_min_value(p_min); - curve->set_max_value(p_max); - set_curve(curve); - // Min and max is 0..1 by default - } -} - -void CurveTexture::set_curve(Ref<Curve> p_curve) { - if (_curve != p_curve) { - if (_curve.is_valid()) { - _curve->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(this, &CurveTexture::_update)); - } - _curve = p_curve; - if (_curve.is_valid()) { - _curve->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &CurveTexture::_update)); - } - _update(); - } -} - -void CurveTexture::_update() { - Vector<uint8_t> data; - data.resize(_width * sizeof(float) * (texture_mode == TEXTURE_MODE_RGB ? 3 : 1)); - - // The array is locked in that scope - { - uint8_t *wd8 = data.ptrw(); - float *wd = (float *)wd8; - - if (_curve.is_valid()) { - Curve &curve = **_curve; - for (int i = 0; i < _width; ++i) { - float t = i / static_cast<float>(_width); - if (texture_mode == TEXTURE_MODE_RGB) { - wd[i * 3 + 0] = curve.sample_baked(t); - wd[i * 3 + 1] = wd[i * 3 + 0]; - wd[i * 3 + 2] = wd[i * 3 + 0]; - } else { - wd[i] = curve.sample_baked(t); - } - } - - } else { - for (int i = 0; i < _width; ++i) { - if (texture_mode == TEXTURE_MODE_RGB) { - wd[i * 3 + 0] = 0; - wd[i * 3 + 1] = 0; - wd[i * 3 + 2] = 0; - } else { - wd[i] = 0; - } - } - } - } - - Ref<Image> image = memnew(Image(_width, 1, false, texture_mode == TEXTURE_MODE_RGB ? Image::FORMAT_RGBF : Image::FORMAT_RF, data)); - - if (_texture.is_valid()) { - if (_current_texture_mode != texture_mode || _current_width != _width) { - RID new_texture = RS::get_singleton()->texture_2d_create(image); - RS::get_singleton()->texture_replace(_texture, new_texture); - } else { - RS::get_singleton()->texture_2d_update(_texture, image); - } - } else { - _texture = RS::get_singleton()->texture_2d_create(image); - } - _current_texture_mode = texture_mode; - _current_width = _width; - - emit_changed(); -} - -Ref<Curve> CurveTexture::get_curve() const { - return _curve; -} - -void CurveTexture::set_texture_mode(TextureMode p_mode) { - ERR_FAIL_COND(p_mode < TEXTURE_MODE_RGB || p_mode > TEXTURE_MODE_RED); - if (texture_mode == p_mode) { - return; - } - texture_mode = p_mode; - _update(); -} -CurveTexture::TextureMode CurveTexture::get_texture_mode() const { - return texture_mode; -} - -RID CurveTexture::get_rid() const { - if (!_texture.is_valid()) { - _texture = RS::get_singleton()->texture_2d_placeholder_create(); - } - return _texture; -} - -CurveTexture::CurveTexture() {} - -CurveTexture::~CurveTexture() { - if (_texture.is_valid()) { - ERR_FAIL_NULL(RenderingServer::get_singleton()); - RS::get_singleton()->free(_texture); - } -} - -////////////////// - -void CurveXYZTexture::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_width", "width"), &CurveXYZTexture::set_width); - - ClassDB::bind_method(D_METHOD("set_curve_x", "curve"), &CurveXYZTexture::set_curve_x); - ClassDB::bind_method(D_METHOD("get_curve_x"), &CurveXYZTexture::get_curve_x); - - ClassDB::bind_method(D_METHOD("set_curve_y", "curve"), &CurveXYZTexture::set_curve_y); - ClassDB::bind_method(D_METHOD("get_curve_y"), &CurveXYZTexture::get_curve_y); - - ClassDB::bind_method(D_METHOD("set_curve_z", "curve"), &CurveXYZTexture::set_curve_z); - ClassDB::bind_method(D_METHOD("get_curve_z"), &CurveXYZTexture::get_curve_z); - - ClassDB::bind_method(D_METHOD("_update"), &CurveXYZTexture::_update); - - ADD_PROPERTY(PropertyInfo(Variant::INT, "width", PROPERTY_HINT_RANGE, "1,4096,suffix:px"), "set_width", "get_width"); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "curve_x", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_curve_x", "get_curve_x"); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "curve_y", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_curve_y", "get_curve_y"); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "curve_z", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_curve_z", "get_curve_z"); -} - -void CurveXYZTexture::set_width(int p_width) { - ERR_FAIL_COND(p_width < 32 || p_width > 4096); - - if (_width == p_width) { - return; - } - - _width = p_width; - _update(); -} - -int CurveXYZTexture::get_width() const { - return _width; -} - -void CurveXYZTexture::ensure_default_setup(float p_min, float p_max) { - if (_curve_x.is_null()) { - Ref<Curve> curve = Ref<Curve>(memnew(Curve)); - curve->add_point(Vector2(0, 1)); - curve->add_point(Vector2(1, 1)); - curve->set_min_value(p_min); - curve->set_max_value(p_max); - set_curve_x(curve); - } - - if (_curve_y.is_null()) { - Ref<Curve> curve = Ref<Curve>(memnew(Curve)); - curve->add_point(Vector2(0, 1)); - curve->add_point(Vector2(1, 1)); - curve->set_min_value(p_min); - curve->set_max_value(p_max); - set_curve_y(curve); - } - - if (_curve_z.is_null()) { - Ref<Curve> curve = Ref<Curve>(memnew(Curve)); - curve->add_point(Vector2(0, 1)); - curve->add_point(Vector2(1, 1)); - curve->set_min_value(p_min); - curve->set_max_value(p_max); - set_curve_z(curve); - } -} - -void CurveXYZTexture::set_curve_x(Ref<Curve> p_curve) { - if (_curve_x != p_curve) { - if (_curve_x.is_valid()) { - _curve_x->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(this, &CurveXYZTexture::_update)); - } - _curve_x = p_curve; - if (_curve_x.is_valid()) { - _curve_x->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &CurveXYZTexture::_update), CONNECT_REFERENCE_COUNTED); - } - _update(); - } -} - -void CurveXYZTexture::set_curve_y(Ref<Curve> p_curve) { - if (_curve_y != p_curve) { - if (_curve_y.is_valid()) { - _curve_y->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(this, &CurveXYZTexture::_update)); - } - _curve_y = p_curve; - if (_curve_y.is_valid()) { - _curve_y->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &CurveXYZTexture::_update), CONNECT_REFERENCE_COUNTED); - } - _update(); - } -} - -void CurveXYZTexture::set_curve_z(Ref<Curve> p_curve) { - if (_curve_z != p_curve) { - if (_curve_z.is_valid()) { - _curve_z->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(this, &CurveXYZTexture::_update)); - } - _curve_z = p_curve; - if (_curve_z.is_valid()) { - _curve_z->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &CurveXYZTexture::_update), CONNECT_REFERENCE_COUNTED); - } - _update(); - } -} - -void CurveXYZTexture::_update() { - Vector<uint8_t> data; - data.resize(_width * sizeof(float) * 3); - - // The array is locked in that scope - { - uint8_t *wd8 = data.ptrw(); - float *wd = (float *)wd8; - - if (_curve_x.is_valid()) { - Curve &curve_x = **_curve_x; - for (int i = 0; i < _width; ++i) { - float t = i / static_cast<float>(_width); - wd[i * 3 + 0] = curve_x.sample_baked(t); - } - - } else { - for (int i = 0; i < _width; ++i) { - wd[i * 3 + 0] = 0; - } - } - - if (_curve_y.is_valid()) { - Curve &curve_y = **_curve_y; - for (int i = 0; i < _width; ++i) { - float t = i / static_cast<float>(_width); - wd[i * 3 + 1] = curve_y.sample_baked(t); - } - - } else { - for (int i = 0; i < _width; ++i) { - wd[i * 3 + 1] = 0; - } - } - - if (_curve_z.is_valid()) { - Curve &curve_z = **_curve_z; - for (int i = 0; i < _width; ++i) { - float t = i / static_cast<float>(_width); - wd[i * 3 + 2] = curve_z.sample_baked(t); - } - - } else { - for (int i = 0; i < _width; ++i) { - wd[i * 3 + 2] = 0; - } - } - } - - Ref<Image> image = memnew(Image(_width, 1, false, Image::FORMAT_RGBF, data)); - - if (_texture.is_valid()) { - if (_current_width != _width) { - RID new_texture = RS::get_singleton()->texture_2d_create(image); - RS::get_singleton()->texture_replace(_texture, new_texture); - } else { - RS::get_singleton()->texture_2d_update(_texture, image); - } - } else { - _texture = RS::get_singleton()->texture_2d_create(image); - } - _current_width = _width; - - emit_changed(); -} - -Ref<Curve> CurveXYZTexture::get_curve_x() const { - return _curve_x; -} - -Ref<Curve> CurveXYZTexture::get_curve_y() const { - return _curve_y; -} - -Ref<Curve> CurveXYZTexture::get_curve_z() const { - return _curve_z; -} - -RID CurveXYZTexture::get_rid() const { - if (!_texture.is_valid()) { - _texture = RS::get_singleton()->texture_2d_placeholder_create(); - } - return _texture; -} - -CurveXYZTexture::CurveXYZTexture() {} - -CurveXYZTexture::~CurveXYZTexture() { - if (_texture.is_valid()) { - ERR_FAIL_NULL(RenderingServer::get_singleton()); - RS::get_singleton()->free(_texture); - } -} - -////////////////// - -GradientTexture1D::GradientTexture1D() { - _queue_update(); -} - -GradientTexture1D::~GradientTexture1D() { - if (texture.is_valid()) { - ERR_FAIL_NULL(RenderingServer::get_singleton()); - RS::get_singleton()->free(texture); - } -} - -void GradientTexture1D::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_gradient", "gradient"), &GradientTexture1D::set_gradient); - ClassDB::bind_method(D_METHOD("get_gradient"), &GradientTexture1D::get_gradient); - - ClassDB::bind_method(D_METHOD("set_width", "width"), &GradientTexture1D::set_width); - // The `get_width()` method is already exposed by the parent class Texture2D. - - ClassDB::bind_method(D_METHOD("set_use_hdr", "enabled"), &GradientTexture1D::set_use_hdr); - ClassDB::bind_method(D_METHOD("is_using_hdr"), &GradientTexture1D::is_using_hdr); - - ClassDB::bind_method(D_METHOD("_update"), &GradientTexture1D::_update); - - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "gradient", PROPERTY_HINT_RESOURCE_TYPE, "Gradient", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_EDITOR_INSTANTIATE_OBJECT), "set_gradient", "get_gradient"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "width", PROPERTY_HINT_RANGE, "1,16384,suffix:px"), "set_width", "get_width"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_hdr"), "set_use_hdr", "is_using_hdr"); -} - -void GradientTexture1D::set_gradient(Ref<Gradient> p_gradient) { - if (p_gradient == gradient) { - return; - } - if (gradient.is_valid()) { - gradient->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(this, &GradientTexture1D::_update)); - } - gradient = p_gradient; - if (gradient.is_valid()) { - gradient->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &GradientTexture1D::_update)); - } - _update(); - emit_changed(); -} - -Ref<Gradient> GradientTexture1D::get_gradient() const { - return gradient; -} - -void GradientTexture1D::_queue_update() { - if (update_pending) { - return; - } - - update_pending = true; - call_deferred(SNAME("_update")); -} - -void GradientTexture1D::_update() { - update_pending = false; - - if (gradient.is_null()) { - return; - } - - if (use_hdr) { - // High dynamic range. - Ref<Image> image = memnew(Image(width, 1, false, Image::FORMAT_RGBAF)); - Gradient &g = **gradient; - // `create()` isn't available for non-uint8_t data, so fill in the data manually. - for (int i = 0; i < width; i++) { - float ofs = float(i) / (width - 1); - image->set_pixel(i, 0, g.get_color_at_offset(ofs)); - } - - if (texture.is_valid()) { - RID new_texture = RS::get_singleton()->texture_2d_create(image); - RS::get_singleton()->texture_replace(texture, new_texture); - } else { - texture = RS::get_singleton()->texture_2d_create(image); - } - } else { - // Low dynamic range. "Overbright" colors will be clamped. - Vector<uint8_t> data; - data.resize(width * 4); - { - uint8_t *wd8 = data.ptrw(); - Gradient &g = **gradient; - - for (int i = 0; i < width; i++) { - float ofs = float(i) / (width - 1); - Color color = g.get_color_at_offset(ofs); - - wd8[i * 4 + 0] = uint8_t(CLAMP(color.r * 255.0, 0, 255)); - wd8[i * 4 + 1] = uint8_t(CLAMP(color.g * 255.0, 0, 255)); - wd8[i * 4 + 2] = uint8_t(CLAMP(color.b * 255.0, 0, 255)); - wd8[i * 4 + 3] = uint8_t(CLAMP(color.a * 255.0, 0, 255)); - } - } - - Ref<Image> image = memnew(Image(width, 1, false, Image::FORMAT_RGBA8, data)); - - if (texture.is_valid()) { - RID new_texture = RS::get_singleton()->texture_2d_create(image); - RS::get_singleton()->texture_replace(texture, new_texture); - } else { - texture = RS::get_singleton()->texture_2d_create(image); - } - } - - emit_changed(); -} - -void GradientTexture1D::set_width(int p_width) { - ERR_FAIL_COND_MSG(p_width <= 0 || p_width > 16384, "Texture dimensions have to be within 1 to 16384 range."); - width = p_width; - _queue_update(); -} - -int GradientTexture1D::get_width() const { - return width; -} - -void GradientTexture1D::set_use_hdr(bool p_enabled) { - if (p_enabled == use_hdr) { - return; - } - - use_hdr = p_enabled; - _queue_update(); -} - -bool GradientTexture1D::is_using_hdr() const { - return use_hdr; -} - -Ref<Image> GradientTexture1D::get_image() const { - if (!texture.is_valid()) { - return Ref<Image>(); - } - return RenderingServer::get_singleton()->texture_2d_get(texture); -} - -////////////////// - -GradientTexture2D::GradientTexture2D() { - _queue_update(); -} - -GradientTexture2D::~GradientTexture2D() { - if (texture.is_valid()) { - ERR_FAIL_NULL(RenderingServer::get_singleton()); - RS::get_singleton()->free(texture); - } -} - -void GradientTexture2D::set_gradient(Ref<Gradient> p_gradient) { - if (gradient == p_gradient) { - return; - } - if (gradient.is_valid()) { - gradient->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(this, &GradientTexture2D::_queue_update)); - } - gradient = p_gradient; - if (gradient.is_valid()) { - gradient->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &GradientTexture2D::_queue_update)); - } - _update(); - emit_changed(); -} - -Ref<Gradient> GradientTexture2D::get_gradient() const { - return gradient; -} - -void GradientTexture2D::_queue_update() { - if (update_pending) { - return; - } - update_pending = true; - call_deferred(SNAME("_update")); -} - -void GradientTexture2D::_update() { - update_pending = false; - - if (gradient.is_null()) { - return; - } - Ref<Image> image; - image.instantiate(); - - if (gradient->get_point_count() <= 1) { // No need to interpolate. - image->initialize_data(width, height, false, (use_hdr) ? Image::FORMAT_RGBAF : Image::FORMAT_RGBA8); - image->fill((gradient->get_point_count() == 1) ? gradient->get_color(0) : Color(0, 0, 0, 1)); - } else { - if (use_hdr) { - image->initialize_data(width, height, false, Image::FORMAT_RGBAF); - Gradient &g = **gradient; - // `create()` isn't available for non-uint8_t data, so fill in the data manually. - for (int y = 0; y < height; y++) { - for (int x = 0; x < width; x++) { - float ofs = _get_gradient_offset_at(x, y); - image->set_pixel(x, y, g.get_color_at_offset(ofs)); - } - } - } else { - Vector<uint8_t> data; - data.resize(width * height * 4); - { - uint8_t *wd8 = data.ptrw(); - Gradient &g = **gradient; - for (int y = 0; y < height; y++) { - for (int x = 0; x < width; x++) { - float ofs = _get_gradient_offset_at(x, y); - const Color &c = g.get_color_at_offset(ofs); - - wd8[(x + (y * width)) * 4 + 0] = uint8_t(CLAMP(c.r * 255.0, 0, 255)); - wd8[(x + (y * width)) * 4 + 1] = uint8_t(CLAMP(c.g * 255.0, 0, 255)); - wd8[(x + (y * width)) * 4 + 2] = uint8_t(CLAMP(c.b * 255.0, 0, 255)); - wd8[(x + (y * width)) * 4 + 3] = uint8_t(CLAMP(c.a * 255.0, 0, 255)); - } - } - } - image->set_data(width, height, false, Image::FORMAT_RGBA8, data); - } - } - - if (texture.is_valid()) { - RID new_texture = RS::get_singleton()->texture_2d_create(image); - RS::get_singleton()->texture_replace(texture, new_texture); - } else { - texture = RS::get_singleton()->texture_2d_create(image); - } - emit_changed(); -} - -float GradientTexture2D::_get_gradient_offset_at(int x, int y) const { - if (fill_to == fill_from) { - return 0; - } - float ofs = 0; - Vector2 pos; - if (width > 1) { - pos.x = static_cast<float>(x) / (width - 1); - } - if (height > 1) { - pos.y = static_cast<float>(y) / (height - 1); - } - if (fill == Fill::FILL_LINEAR) { - Vector2 segment[2]; - segment[0] = fill_from; - segment[1] = fill_to; - Vector2 closest = Geometry2D::get_closest_point_to_segment_uncapped(pos, &segment[0]); - ofs = (closest - fill_from).length() / (fill_to - fill_from).length(); - if ((closest - fill_from).dot(fill_to - fill_from) < 0) { - ofs *= -1; - } - } else if (fill == Fill::FILL_RADIAL) { - ofs = (pos - fill_from).length() / (fill_to - fill_from).length(); - } else if (fill == Fill::FILL_SQUARE) { - ofs = MAX(Math::abs(pos.x - fill_from.x), Math::abs(pos.y - fill_from.y)) / MAX(Math::abs(fill_to.x - fill_from.x), Math::abs(fill_to.y - fill_from.y)); - } - if (repeat == Repeat::REPEAT_NONE) { - ofs = CLAMP(ofs, 0.0, 1.0); - } else if (repeat == Repeat::REPEAT) { - ofs = Math::fmod(ofs, 1.0f); - if (ofs < 0) { - ofs = 1 + ofs; - } - } else if (repeat == Repeat::REPEAT_MIRROR) { - ofs = Math::abs(ofs); - ofs = Math::fmod(ofs, 2.0f); - if (ofs > 1.0) { - ofs = 2.0 - ofs; - } - } - return ofs; -} - -void GradientTexture2D::set_width(int p_width) { - ERR_FAIL_COND_MSG(p_width <= 0 || p_width > 16384, "Texture dimensions have to be within 1 to 16384 range."); - width = p_width; - _queue_update(); -} - -int GradientTexture2D::get_width() const { - return width; -} - -void GradientTexture2D::set_height(int p_height) { - ERR_FAIL_COND_MSG(p_height <= 0 || p_height > 16384, "Texture dimensions have to be within 1 to 16384 range."); - height = p_height; - _queue_update(); -} -int GradientTexture2D::get_height() const { - return height; -} - -void GradientTexture2D::set_use_hdr(bool p_enabled) { - if (p_enabled == use_hdr) { - return; - } - - use_hdr = p_enabled; - _queue_update(); -} - -bool GradientTexture2D::is_using_hdr() const { - return use_hdr; -} - -void GradientTexture2D::set_fill_from(Vector2 p_fill_from) { - fill_from = p_fill_from; - _queue_update(); -} - -Vector2 GradientTexture2D::get_fill_from() const { - return fill_from; -} - -void GradientTexture2D::set_fill_to(Vector2 p_fill_to) { - fill_to = p_fill_to; - _queue_update(); -} - -Vector2 GradientTexture2D::get_fill_to() const { - return fill_to; -} - -void GradientTexture2D::set_fill(Fill p_fill) { - fill = p_fill; - _queue_update(); -} - -GradientTexture2D::Fill GradientTexture2D::get_fill() const { - return fill; -} - -void GradientTexture2D::set_repeat(Repeat p_repeat) { - repeat = p_repeat; - _queue_update(); -} - -GradientTexture2D::Repeat GradientTexture2D::get_repeat() const { - return repeat; -} - -RID GradientTexture2D::get_rid() const { - if (!texture.is_valid()) { - texture = RS::get_singleton()->texture_2d_placeholder_create(); - } - return texture; -} - -Ref<Image> GradientTexture2D::get_image() const { - if (!texture.is_valid()) { - return Ref<Image>(); - } - return RenderingServer::get_singleton()->texture_2d_get(texture); -} - -void GradientTexture2D::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_gradient", "gradient"), &GradientTexture2D::set_gradient); - ClassDB::bind_method(D_METHOD("get_gradient"), &GradientTexture2D::get_gradient); - - ClassDB::bind_method(D_METHOD("set_width", "width"), &GradientTexture2D::set_width); - ClassDB::bind_method(D_METHOD("set_height", "height"), &GradientTexture2D::set_height); - - ClassDB::bind_method(D_METHOD("set_use_hdr", "enabled"), &GradientTexture2D::set_use_hdr); - ClassDB::bind_method(D_METHOD("is_using_hdr"), &GradientTexture2D::is_using_hdr); - - ClassDB::bind_method(D_METHOD("set_fill", "fill"), &GradientTexture2D::set_fill); - ClassDB::bind_method(D_METHOD("get_fill"), &GradientTexture2D::get_fill); - ClassDB::bind_method(D_METHOD("set_fill_from", "fill_from"), &GradientTexture2D::set_fill_from); - ClassDB::bind_method(D_METHOD("get_fill_from"), &GradientTexture2D::get_fill_from); - ClassDB::bind_method(D_METHOD("set_fill_to", "fill_to"), &GradientTexture2D::set_fill_to); - ClassDB::bind_method(D_METHOD("get_fill_to"), &GradientTexture2D::get_fill_to); - - ClassDB::bind_method(D_METHOD("set_repeat", "repeat"), &GradientTexture2D::set_repeat); - ClassDB::bind_method(D_METHOD("get_repeat"), &GradientTexture2D::get_repeat); - - ClassDB::bind_method(D_METHOD("_update"), &GradientTexture2D::_update); - - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "gradient", PROPERTY_HINT_RESOURCE_TYPE, "Gradient", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_EDITOR_INSTANTIATE_OBJECT), "set_gradient", "get_gradient"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "width", PROPERTY_HINT_RANGE, "1,2048,or_greater,suffix:px"), "set_width", "get_width"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "height", PROPERTY_HINT_RANGE, "1,2048,or_greater,suffix:px"), "set_height", "get_height"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_hdr"), "set_use_hdr", "is_using_hdr"); - - ADD_GROUP("Fill", "fill_"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "fill", PROPERTY_HINT_ENUM, "Linear,Radial,Square"), "set_fill", "get_fill"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "fill_from"), "set_fill_from", "get_fill_from"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "fill_to"), "set_fill_to", "get_fill_to"); - - ADD_GROUP("Repeat", "repeat_"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "repeat", PROPERTY_HINT_ENUM, "No Repeat,Repeat,Mirror Repeat"), "set_repeat", "get_repeat"); - - BIND_ENUM_CONSTANT(FILL_LINEAR); - BIND_ENUM_CONSTANT(FILL_RADIAL); - BIND_ENUM_CONSTANT(FILL_SQUARE); - - BIND_ENUM_CONSTANT(REPEAT_NONE); - BIND_ENUM_CONSTANT(REPEAT); - BIND_ENUM_CONSTANT(REPEAT_MIRROR); -} - -////////////////////////////////////// - -void AnimatedTexture::_update_proxy() { - RWLockRead r(rw_lock); - - float delta; - if (prev_ticks == 0) { - delta = 0; - prev_ticks = OS::get_singleton()->get_ticks_usec(); - } else { - uint64_t ticks = OS::get_singleton()->get_ticks_usec(); - delta = float(double(ticks - prev_ticks) / 1000000.0); - prev_ticks = ticks; - } - - time += delta; - - float speed = speed_scale == 0 ? 0 : abs(1.0 / speed_scale); - - int iter_max = frame_count; - while (iter_max && !pause) { - float frame_limit = frames[current_frame].duration * speed; - - if (time > frame_limit) { - if (speed_scale > 0.0) { - current_frame++; - } else { - current_frame--; - } - if (current_frame >= frame_count) { - if (one_shot) { - current_frame = frame_count - 1; - } else { - current_frame = 0; - } - } else if (current_frame < 0) { - if (one_shot) { - current_frame = 0; - } else { - current_frame = frame_count - 1; - } - } - time -= frame_limit; - - } else { - break; - } - iter_max--; - } - - if (frames[current_frame].texture.is_valid()) { - RenderingServer::get_singleton()->texture_proxy_update(proxy, frames[current_frame].texture->get_rid()); - } -} - -void AnimatedTexture::set_frames(int p_frames) { - ERR_FAIL_COND(p_frames < 1 || p_frames > MAX_FRAMES); - - RWLockWrite r(rw_lock); - - frame_count = p_frames; -} - -int AnimatedTexture::get_frames() const { - return frame_count; -} - -void AnimatedTexture::set_current_frame(int p_frame) { - ERR_FAIL_COND(p_frame < 0 || p_frame >= frame_count); - - RWLockWrite r(rw_lock); - - current_frame = p_frame; - time = 0; -} - -int AnimatedTexture::get_current_frame() const { - return current_frame; -} - -void AnimatedTexture::set_pause(bool p_pause) { - RWLockWrite r(rw_lock); - pause = p_pause; -} - -bool AnimatedTexture::get_pause() const { - return pause; -} - -void AnimatedTexture::set_one_shot(bool p_one_shot) { - RWLockWrite r(rw_lock); - one_shot = p_one_shot; -} - -bool AnimatedTexture::get_one_shot() const { - return one_shot; -} - -void AnimatedTexture::set_frame_texture(int p_frame, const Ref<Texture2D> &p_texture) { - ERR_FAIL_COND(p_texture == this); - ERR_FAIL_INDEX(p_frame, MAX_FRAMES); - - RWLockWrite w(rw_lock); - - frames[p_frame].texture = p_texture; -} - -Ref<Texture2D> AnimatedTexture::get_frame_texture(int p_frame) const { - ERR_FAIL_INDEX_V(p_frame, MAX_FRAMES, Ref<Texture2D>()); - - RWLockRead r(rw_lock); - - return frames[p_frame].texture; -} - -void AnimatedTexture::set_frame_duration(int p_frame, float p_duration) { - ERR_FAIL_INDEX(p_frame, MAX_FRAMES); - - RWLockWrite r(rw_lock); - - frames[p_frame].duration = p_duration; -} - -float AnimatedTexture::get_frame_duration(int p_frame) const { - ERR_FAIL_INDEX_V(p_frame, MAX_FRAMES, 0); - - RWLockRead r(rw_lock); - - return frames[p_frame].duration; -} - -void AnimatedTexture::set_speed_scale(float p_scale) { - ERR_FAIL_COND(p_scale < -1000 || p_scale >= 1000); - - RWLockWrite r(rw_lock); - - speed_scale = p_scale; -} - -float AnimatedTexture::get_speed_scale() const { - return speed_scale; -} - -int AnimatedTexture::get_width() const { - RWLockRead r(rw_lock); - - if (!frames[current_frame].texture.is_valid()) { - return 1; - } - - return frames[current_frame].texture->get_width(); -} - -int AnimatedTexture::get_height() const { - RWLockRead r(rw_lock); - - if (!frames[current_frame].texture.is_valid()) { - return 1; - } - - return frames[current_frame].texture->get_height(); -} - -RID AnimatedTexture::get_rid() const { - return proxy; -} - -bool AnimatedTexture::has_alpha() const { - RWLockRead r(rw_lock); - - if (!frames[current_frame].texture.is_valid()) { - return false; - } - - return frames[current_frame].texture->has_alpha(); -} - -Ref<Image> AnimatedTexture::get_image() const { - RWLockRead r(rw_lock); - - if (!frames[current_frame].texture.is_valid()) { - return Ref<Image>(); - } - - return frames[current_frame].texture->get_image(); -} - -bool AnimatedTexture::is_pixel_opaque(int p_x, int p_y) const { - RWLockRead r(rw_lock); - - if (frames[current_frame].texture.is_valid()) { - return frames[current_frame].texture->is_pixel_opaque(p_x, p_y); - } - return true; -} - -void AnimatedTexture::_validate_property(PropertyInfo &p_property) const { - String prop = p_property.name; - if (prop.begins_with("frame_")) { - int frame = prop.get_slicec('/', 0).get_slicec('_', 1).to_int(); - if (frame >= frame_count) { - p_property.usage = PROPERTY_USAGE_NONE; - } - } -} - -void AnimatedTexture::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_frames", "frames"), &AnimatedTexture::set_frames); - ClassDB::bind_method(D_METHOD("get_frames"), &AnimatedTexture::get_frames); - - ClassDB::bind_method(D_METHOD("set_current_frame", "frame"), &AnimatedTexture::set_current_frame); - ClassDB::bind_method(D_METHOD("get_current_frame"), &AnimatedTexture::get_current_frame); - - ClassDB::bind_method(D_METHOD("set_pause", "pause"), &AnimatedTexture::set_pause); - ClassDB::bind_method(D_METHOD("get_pause"), &AnimatedTexture::get_pause); - - ClassDB::bind_method(D_METHOD("set_one_shot", "one_shot"), &AnimatedTexture::set_one_shot); - ClassDB::bind_method(D_METHOD("get_one_shot"), &AnimatedTexture::get_one_shot); - - ClassDB::bind_method(D_METHOD("set_speed_scale", "scale"), &AnimatedTexture::set_speed_scale); - ClassDB::bind_method(D_METHOD("get_speed_scale"), &AnimatedTexture::get_speed_scale); - - ClassDB::bind_method(D_METHOD("set_frame_texture", "frame", "texture"), &AnimatedTexture::set_frame_texture); - ClassDB::bind_method(D_METHOD("get_frame_texture", "frame"), &AnimatedTexture::get_frame_texture); - - ClassDB::bind_method(D_METHOD("set_frame_duration", "frame", "duration"), &AnimatedTexture::set_frame_duration); - ClassDB::bind_method(D_METHOD("get_frame_duration", "frame"), &AnimatedTexture::get_frame_duration); - - ADD_PROPERTY(PropertyInfo(Variant::INT, "frames", PROPERTY_HINT_RANGE, "1," + itos(MAX_FRAMES), PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), "set_frames", "get_frames"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "current_frame", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_current_frame", "get_current_frame"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "pause"), "set_pause", "get_pause"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "one_shot"), "set_one_shot", "get_one_shot"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "speed_scale", PROPERTY_HINT_RANGE, "-60,60,0.1,or_less,or_greater"), "set_speed_scale", "get_speed_scale"); - - for (int i = 0; i < MAX_FRAMES; i++) { - ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "frame_" + itos(i) + "/texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_INTERNAL), "set_frame_texture", "get_frame_texture", i); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "frame_" + itos(i) + "/duration", PROPERTY_HINT_RANGE, "0.0,16.0,0.01,or_greater,suffix:s", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_INTERNAL), "set_frame_duration", "get_frame_duration", i); - } - - BIND_CONSTANT(MAX_FRAMES); -} - -AnimatedTexture::AnimatedTexture() { - //proxy = RS::get_singleton()->texture_create(); - proxy_ph = RS::get_singleton()->texture_2d_placeholder_create(); - proxy = RS::get_singleton()->texture_proxy_create(proxy_ph); - - RenderingServer::get_singleton()->texture_set_force_redraw_if_visible(proxy, true); - RenderingServer::get_singleton()->connect("frame_pre_draw", callable_mp(this, &AnimatedTexture::_update_proxy)); -} - -AnimatedTexture::~AnimatedTexture() { - ERR_FAIL_NULL(RenderingServer::get_singleton()); - RS::get_singleton()->free(proxy); - RS::get_singleton()->free(proxy_ph); -} - -/////////////////////////////// - Image::Format TextureLayered::get_format() const { Image::Format ret = Image::FORMAT_MAX; GDVIRTUAL_REQUIRED_CALL(_get_format, ret); @@ -2896,654 +259,3 @@ void TextureLayered::_bind_methods() { GDVIRTUAL_BIND(_has_mipmaps); GDVIRTUAL_BIND(_get_layer_data, "layer_index"); } - -/////////////////////////////// -Image::Format ImageTextureLayered::get_format() const { - return format; -} - -int ImageTextureLayered::get_width() const { - return width; -} - -int ImageTextureLayered::get_height() const { - return height; -} - -int ImageTextureLayered::get_layers() const { - return layers; -} - -bool ImageTextureLayered::has_mipmaps() const { - return mipmaps; -} - -ImageTextureLayered::LayeredType ImageTextureLayered::get_layered_type() const { - return layered_type; -} - -Error ImageTextureLayered::_create_from_images(const TypedArray<Image> &p_images) { - Vector<Ref<Image>> images; - for (int i = 0; i < p_images.size(); i++) { - Ref<Image> img = p_images[i]; - ERR_FAIL_COND_V(img.is_null(), ERR_INVALID_PARAMETER); - images.push_back(img); - } - - return create_from_images(images); -} - -TypedArray<Image> ImageTextureLayered::_get_images() const { - TypedArray<Image> images; - for (int i = 0; i < layers; i++) { - images.push_back(get_layer_data(i)); - } - return images; -} - -void ImageTextureLayered::_set_images(const TypedArray<Image> &p_images) { - ERR_FAIL_COND(_create_from_images(p_images) != OK); -} - -Error ImageTextureLayered::create_from_images(Vector<Ref<Image>> p_images) { - int new_layers = p_images.size(); - ERR_FAIL_COND_V(new_layers == 0, ERR_INVALID_PARAMETER); - if (layered_type == LAYERED_TYPE_CUBEMAP) { - ERR_FAIL_COND_V_MSG(new_layers != 6, ERR_INVALID_PARAMETER, - "Cubemaps require exactly 6 layers"); - } else if (layered_type == LAYERED_TYPE_CUBEMAP_ARRAY) { - ERR_FAIL_COND_V_MSG((new_layers % 6) != 0, ERR_INVALID_PARAMETER, - "Cubemap array layers must be a multiple of 6"); - } - - ERR_FAIL_COND_V(p_images[0].is_null() || p_images[0]->is_empty(), ERR_INVALID_PARAMETER); - - Image::Format new_format = p_images[0]->get_format(); - int new_width = p_images[0]->get_width(); - int new_height = p_images[0]->get_height(); - bool new_mipmaps = p_images[0]->has_mipmaps(); - - for (int i = 1; i < p_images.size(); i++) { - ERR_FAIL_COND_V_MSG(p_images[i]->get_format() != new_format, ERR_INVALID_PARAMETER, - "All images must share the same format"); - ERR_FAIL_COND_V_MSG(p_images[i]->get_width() != new_width || p_images[i]->get_height() != new_height, ERR_INVALID_PARAMETER, - "All images must share the same dimensions"); - ERR_FAIL_COND_V_MSG(p_images[i]->has_mipmaps() != new_mipmaps, ERR_INVALID_PARAMETER, - "All images must share the usage of mipmaps"); - } - - if (texture.is_valid()) { - RID new_texture = RS::get_singleton()->texture_2d_layered_create(p_images, RS::TextureLayeredType(layered_type)); - ERR_FAIL_COND_V(!new_texture.is_valid(), ERR_CANT_CREATE); - RS::get_singleton()->texture_replace(texture, new_texture); - } else { - texture = RS::get_singleton()->texture_2d_layered_create(p_images, RS::TextureLayeredType(layered_type)); - ERR_FAIL_COND_V(!texture.is_valid(), ERR_CANT_CREATE); - } - - format = new_format; - width = new_width; - height = new_height; - layers = new_layers; - mipmaps = new_mipmaps; - return OK; -} - -void ImageTextureLayered::update_layer(const Ref<Image> &p_image, int p_layer) { - ERR_FAIL_COND_MSG(texture.is_null(), "Texture is not initialized."); - ERR_FAIL_COND_MSG(p_image.is_null(), "Invalid image."); - ERR_FAIL_COND_MSG(p_image->get_format() != format, "Image format must match texture's image format."); - ERR_FAIL_COND_MSG(p_image->get_width() != width || p_image->get_height() != height, "Image size must match texture's image size."); - ERR_FAIL_COND_MSG(p_image->has_mipmaps() != mipmaps, "Image mipmap configuration must match texture's image mipmap configuration."); - ERR_FAIL_INDEX_MSG(p_layer, layers, "Layer index is out of bounds."); - RS::get_singleton()->texture_2d_update(texture, p_image, p_layer); -} - -Ref<Image> ImageTextureLayered::get_layer_data(int p_layer) const { - ERR_FAIL_INDEX_V(p_layer, layers, Ref<Image>()); - return RS::get_singleton()->texture_2d_layer_get(texture, p_layer); -} - -RID ImageTextureLayered::get_rid() const { - if (texture.is_null()) { - texture = RS::get_singleton()->texture_2d_layered_placeholder_create(RS::TextureLayeredType(layered_type)); - } - return texture; -} - -void ImageTextureLayered::set_path(const String &p_path, bool p_take_over) { - if (texture.is_valid()) { - RS::get_singleton()->texture_set_path(texture, p_path); - } - - Resource::set_path(p_path, p_take_over); -} - -void ImageTextureLayered::_bind_methods() { - ClassDB::bind_method(D_METHOD("create_from_images", "images"), &ImageTextureLayered::_create_from_images); - ClassDB::bind_method(D_METHOD("update_layer", "image", "layer"), &ImageTextureLayered::update_layer); - - ClassDB::bind_method(D_METHOD("_get_images"), &ImageTextureLayered::_get_images); - ClassDB::bind_method(D_METHOD("_set_images", "images"), &ImageTextureLayered::_set_images); - - ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "_images", PROPERTY_HINT_ARRAY_TYPE, "Image", PROPERTY_USAGE_INTERNAL), "_set_images", "_get_images"); -} - -ImageTextureLayered::ImageTextureLayered(LayeredType p_layered_type) { - layered_type = p_layered_type; -} - -ImageTextureLayered::~ImageTextureLayered() { - if (texture.is_valid()) { - ERR_FAIL_NULL(RenderingServer::get_singleton()); - RS::get_singleton()->free(texture); - } -} - -void Texture2DArray::_bind_methods() { - ClassDB::bind_method(D_METHOD("create_placeholder"), &Texture2DArray::create_placeholder); -} - -Ref<Resource> Texture2DArray::create_placeholder() const { - Ref<PlaceholderTexture2DArray> placeholder; - placeholder.instantiate(); - placeholder->set_size(Size2i(get_width(), get_height())); - placeholder->set_layers(get_layers()); - return placeholder; -} - -void Cubemap::_bind_methods() { - ClassDB::bind_method(D_METHOD("create_placeholder"), &Cubemap::create_placeholder); -} - -Ref<Resource> Cubemap::create_placeholder() const { - Ref<PlaceholderCubemap> placeholder; - placeholder.instantiate(); - placeholder->set_size(Size2i(get_width(), get_height())); - placeholder->set_layers(get_layers()); - return placeholder; -} - -void CubemapArray::_bind_methods() { - ClassDB::bind_method(D_METHOD("create_placeholder"), &CubemapArray::create_placeholder); -} - -Ref<Resource> CubemapArray::create_placeholder() const { - Ref<PlaceholderCubemapArray> placeholder; - placeholder.instantiate(); - placeholder->set_size(Size2i(get_width(), get_height())); - placeholder->set_layers(get_layers()); - return placeholder; -} - -/////////////////////////////////////////// - -void CompressedTextureLayered::set_path(const String &p_path, bool p_take_over) { - if (texture.is_valid()) { - RenderingServer::get_singleton()->texture_set_path(texture, p_path); - } - - Resource::set_path(p_path, p_take_over); -} - -Image::Format CompressedTextureLayered::get_format() const { - return format; -} - -Error CompressedTextureLayered::_load_data(const String &p_path, Vector<Ref<Image>> &images, int &mipmap_limit, int p_size_limit) { - ERR_FAIL_COND_V(images.size() != 0, ERR_INVALID_PARAMETER); - - Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ); - ERR_FAIL_COND_V_MSG(f.is_null(), ERR_CANT_OPEN, vformat("Unable to open file: %s.", p_path)); - - uint8_t header[4]; - f->get_buffer(header, 4); - if (header[0] != 'G' || header[1] != 'S' || header[2] != 'T' || header[3] != 'L') { - ERR_FAIL_V_MSG(ERR_FILE_CORRUPT, "Compressed texture layered file is corrupt (Bad header)."); - } - - uint32_t version = f->get_32(); - - if (version > FORMAT_VERSION) { - ERR_FAIL_V_MSG(ERR_FILE_CORRUPT, "Compressed texture file is too new."); - } - - uint32_t layer_count = f->get_32(); //layer count - uint32_t type = f->get_32(); //layer count - ERR_FAIL_COND_V((int)type != layered_type, ERR_INVALID_DATA); - - uint32_t df = f->get_32(); //data format - mipmap_limit = int(f->get_32()); - //reserved - f->get_32(); - f->get_32(); - f->get_32(); - - if (!(df & FORMAT_BIT_STREAM)) { - p_size_limit = 0; - } - - images.resize(layer_count); - - for (uint32_t i = 0; i < layer_count; i++) { - Ref<Image> image = CompressedTexture2D::load_image_from_file(f, p_size_limit); - ERR_FAIL_COND_V(image.is_null() || image->is_empty(), ERR_CANT_OPEN); - images.write[i] = image; - } - - return OK; -} - -Error CompressedTextureLayered::load(const String &p_path) { - Vector<Ref<Image>> images; - - int mipmap_limit; - - Error err = _load_data(p_path, images, mipmap_limit); - if (err) { - return err; - } - - if (texture.is_valid()) { - RID new_texture = RS::get_singleton()->texture_2d_layered_create(images, RS::TextureLayeredType(layered_type)); - RS::get_singleton()->texture_replace(texture, new_texture); - } else { - texture = RS::get_singleton()->texture_2d_layered_create(images, RS::TextureLayeredType(layered_type)); - } - - w = images[0]->get_width(); - h = images[0]->get_height(); - mipmaps = images[0]->has_mipmaps(); - format = images[0]->get_format(); - layers = images.size(); - - path_to_file = p_path; - - if (get_path().is_empty()) { - //temporarily set path if no path set for resource, helps find errors - RenderingServer::get_singleton()->texture_set_path(texture, p_path); - } - - notify_property_list_changed(); - emit_changed(); - return OK; -} - -String CompressedTextureLayered::get_load_path() const { - return path_to_file; -} - -int CompressedTextureLayered::get_width() const { - return w; -} - -int CompressedTextureLayered::get_height() const { - return h; -} - -int CompressedTextureLayered::get_layers() const { - return layers; -} - -bool CompressedTextureLayered::has_mipmaps() const { - return mipmaps; -} - -TextureLayered::LayeredType CompressedTextureLayered::get_layered_type() const { - return layered_type; -} - -RID CompressedTextureLayered::get_rid() const { - if (!texture.is_valid()) { - texture = RS::get_singleton()->texture_2d_layered_placeholder_create(RS::TextureLayeredType(layered_type)); - } - return texture; -} - -Ref<Image> CompressedTextureLayered::get_layer_data(int p_layer) const { - if (texture.is_valid()) { - return RS::get_singleton()->texture_2d_layer_get(texture, p_layer); - } else { - return Ref<Image>(); - } -} - -void CompressedTextureLayered::reload_from_file() { - String path = get_path(); - if (!path.is_resource_file()) { - return; - } - - path = ResourceLoader::path_remap(path); //remap for translation - path = ResourceLoader::import_remap(path); //remap for import - if (!path.is_resource_file()) { - return; - } - - load(path); -} - -void CompressedTextureLayered::_validate_property(PropertyInfo &p_property) const { -} - -void CompressedTextureLayered::_bind_methods() { - ClassDB::bind_method(D_METHOD("load", "path"), &CompressedTextureLayered::load); - ClassDB::bind_method(D_METHOD("get_load_path"), &CompressedTextureLayered::get_load_path); - - ADD_PROPERTY(PropertyInfo(Variant::STRING, "load_path", PROPERTY_HINT_FILE, "*.ctex"), "load", "get_load_path"); -} - -CompressedTextureLayered::CompressedTextureLayered(LayeredType p_type) { - layered_type = p_type; -} - -CompressedTextureLayered::~CompressedTextureLayered() { - if (texture.is_valid()) { - ERR_FAIL_NULL(RenderingServer::get_singleton()); - RS::get_singleton()->free(texture); - } -} - -///////////////////////////////////////////////// - -Ref<Resource> ResourceFormatLoaderCompressedTextureLayered::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, CacheMode p_cache_mode) { - Ref<CompressedTextureLayered> ct; - if (p_path.get_extension().to_lower() == "ctexarray") { - Ref<CompressedTexture2DArray> c; - c.instantiate(); - ct = c; - } else if (p_path.get_extension().to_lower() == "ccube") { - Ref<CompressedCubemap> c; - c.instantiate(); - ct = c; - } else if (p_path.get_extension().to_lower() == "ccubearray") { - Ref<CompressedCubemapArray> c; - c.instantiate(); - ct = c; - } else { - if (r_error) { - *r_error = ERR_FILE_UNRECOGNIZED; - } - return Ref<Resource>(); - } - Error err = ct->load(p_path); - if (r_error) { - *r_error = err; - } - if (err != OK) { - return Ref<Resource>(); - } - - return ct; -} - -void ResourceFormatLoaderCompressedTextureLayered::get_recognized_extensions(List<String> *p_extensions) const { - p_extensions->push_back("ctexarray"); - p_extensions->push_back("ccube"); - p_extensions->push_back("ccubearray"); -} - -bool ResourceFormatLoaderCompressedTextureLayered::handles_type(const String &p_type) const { - return p_type == "CompressedTexture2DArray" || p_type == "CompressedCubemap" || p_type == "CompressedCubemapArray"; -} - -String ResourceFormatLoaderCompressedTextureLayered::get_resource_type(const String &p_path) const { - if (p_path.get_extension().to_lower() == "ctexarray") { - return "CompressedTexture2DArray"; - } - if (p_path.get_extension().to_lower() == "ccube") { - return "CompressedCubemap"; - } - if (p_path.get_extension().to_lower() == "ccubearray") { - return "CompressedCubemapArray"; - } - return ""; -} - -/////////////////////////////// - -void CameraTexture::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_camera_feed_id", "feed_id"), &CameraTexture::set_camera_feed_id); - ClassDB::bind_method(D_METHOD("get_camera_feed_id"), &CameraTexture::get_camera_feed_id); - - ClassDB::bind_method(D_METHOD("set_which_feed", "which_feed"), &CameraTexture::set_which_feed); - ClassDB::bind_method(D_METHOD("get_which_feed"), &CameraTexture::get_which_feed); - - ClassDB::bind_method(D_METHOD("set_camera_active", "active"), &CameraTexture::set_camera_active); - ClassDB::bind_method(D_METHOD("get_camera_active"), &CameraTexture::get_camera_active); - - ADD_PROPERTY(PropertyInfo(Variant::INT, "camera_feed_id"), "set_camera_feed_id", "get_camera_feed_id"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "which_feed"), "set_which_feed", "get_which_feed"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "camera_is_active"), "set_camera_active", "get_camera_active"); -} - -int CameraTexture::get_width() const { - Ref<CameraFeed> feed = CameraServer::get_singleton()->get_feed_by_id(camera_feed_id); - if (feed.is_valid()) { - return feed->get_base_width(); - } else { - return 0; - } -} - -int CameraTexture::get_height() const { - Ref<CameraFeed> feed = CameraServer::get_singleton()->get_feed_by_id(camera_feed_id); - if (feed.is_valid()) { - return feed->get_base_height(); - } else { - return 0; - } -} - -bool CameraTexture::has_alpha() const { - return false; -} - -RID CameraTexture::get_rid() const { - Ref<CameraFeed> feed = CameraServer::get_singleton()->get_feed_by_id(camera_feed_id); - if (feed.is_valid()) { - return feed->get_texture(which_feed); - } else { - if (_texture.is_null()) { - _texture = RenderingServer::get_singleton()->texture_2d_placeholder_create(); - } - return _texture; - } -} - -Ref<Image> CameraTexture::get_image() const { - // not (yet) supported - return Ref<Image>(); -} - -void CameraTexture::set_camera_feed_id(int p_new_id) { - camera_feed_id = p_new_id; - notify_property_list_changed(); -} - -int CameraTexture::get_camera_feed_id() const { - return camera_feed_id; -} - -void CameraTexture::set_which_feed(CameraServer::FeedImage p_which) { - which_feed = p_which; - notify_property_list_changed(); -} - -CameraServer::FeedImage CameraTexture::get_which_feed() const { - return which_feed; -} - -void CameraTexture::set_camera_active(bool p_active) { - Ref<CameraFeed> feed = CameraServer::get_singleton()->get_feed_by_id(camera_feed_id); - if (feed.is_valid()) { - feed->set_active(p_active); - notify_property_list_changed(); - } -} - -bool CameraTexture::get_camera_active() const { - Ref<CameraFeed> feed = CameraServer::get_singleton()->get_feed_by_id(camera_feed_id); - if (feed.is_valid()) { - return feed->is_active(); - } else { - return false; - } -} - -CameraTexture::CameraTexture() {} - -CameraTexture::~CameraTexture() { - if (_texture.is_valid()) { - ERR_FAIL_NULL(RenderingServer::get_singleton()); - RenderingServer::get_singleton()->free(_texture); - } -} - -/////////////////////////// - -void PlaceholderTexture2D::set_size(Size2 p_size) { - size = p_size; -} - -int PlaceholderTexture2D::get_width() const { - return size.width; -} - -int PlaceholderTexture2D::get_height() const { - return size.height; -} - -bool PlaceholderTexture2D::has_alpha() const { - return false; -} - -Ref<Image> PlaceholderTexture2D::get_image() const { - return Ref<Image>(); -} - -RID PlaceholderTexture2D::get_rid() const { - return rid; -} - -void PlaceholderTexture2D::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_size", "size"), &PlaceholderTexture2D::set_size); - - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "size", PROPERTY_HINT_NONE, "suffix:px"), "set_size", "get_size"); -} - -PlaceholderTexture2D::PlaceholderTexture2D() { - rid = RS::get_singleton()->texture_2d_placeholder_create(); -} - -PlaceholderTexture2D::~PlaceholderTexture2D() { - ERR_FAIL_NULL(RenderingServer::get_singleton()); - RS::get_singleton()->free(rid); -} - -/////////////////////////////////////////////// - -void PlaceholderTexture3D::set_size(const Vector3i &p_size) { - size = p_size; -} - -Vector3i PlaceholderTexture3D::get_size() const { - return size; -} - -Image::Format PlaceholderTexture3D::get_format() const { - return Image::FORMAT_RGB8; -} - -int PlaceholderTexture3D::get_width() const { - return size.x; -} - -int PlaceholderTexture3D::get_height() const { - return size.y; -} - -int PlaceholderTexture3D::get_depth() const { - return size.z; -} - -bool PlaceholderTexture3D::has_mipmaps() const { - return false; -} - -Vector<Ref<Image>> PlaceholderTexture3D::get_data() const { - return Vector<Ref<Image>>(); -} - -void PlaceholderTexture3D::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_size", "size"), &PlaceholderTexture3D::set_size); - ClassDB::bind_method(D_METHOD("get_size"), &PlaceholderTexture3D::get_size); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR3I, "size", PROPERTY_HINT_NONE, "suffix:px"), "set_size", "get_size"); -} - -PlaceholderTexture3D::PlaceholderTexture3D() { - rid = RS::get_singleton()->texture_3d_placeholder_create(); -} -PlaceholderTexture3D::~PlaceholderTexture3D() { - ERR_FAIL_NULL(RenderingServer::get_singleton()); - RS::get_singleton()->free(rid); -} - -///////////////////////////////////////////////// - -void PlaceholderTextureLayered::set_size(const Size2i &p_size) { - size = p_size; -} - -Size2i PlaceholderTextureLayered::get_size() const { - return size; -} - -void PlaceholderTextureLayered::set_layers(int p_layers) { - layers = p_layers; -} - -Image::Format PlaceholderTextureLayered::get_format() const { - return Image::FORMAT_RGB8; -} - -TextureLayered::LayeredType PlaceholderTextureLayered::get_layered_type() const { - return layered_type; -} - -int PlaceholderTextureLayered::get_width() const { - return size.x; -} - -int PlaceholderTextureLayered::get_height() const { - return size.y; -} - -int PlaceholderTextureLayered::get_layers() const { - return layers; -} - -bool PlaceholderTextureLayered::has_mipmaps() const { - return false; -} - -Ref<Image> PlaceholderTextureLayered::get_layer_data(int p_layer) const { - return Ref<Image>(); -} - -void PlaceholderTextureLayered::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_size", "size"), &PlaceholderTextureLayered::set_size); - ClassDB::bind_method(D_METHOD("get_size"), &PlaceholderTextureLayered::get_size); - ClassDB::bind_method(D_METHOD("set_layers", "layers"), &PlaceholderTextureLayered::set_layers); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "size", PROPERTY_HINT_NONE, "suffix:px"), "set_size", "get_size"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "layers", PROPERTY_HINT_RANGE, "1,4096"), "set_layers", "get_layers"); -} - -PlaceholderTextureLayered::PlaceholderTextureLayered(LayeredType p_type) { - layered_type = p_type; - rid = RS::get_singleton()->texture_2d_layered_placeholder_create(RS::TextureLayeredType(layered_type)); -} -PlaceholderTextureLayered::~PlaceholderTextureLayered() { - ERR_FAIL_NULL(RenderingServer::get_singleton()); - RS::get_singleton()->free(rid); -} diff --git a/scene/resources/texture.h b/scene/resources/texture.h index 6b5e87ae21..e7840804bf 100644 --- a/scene/resources/texture.h +++ b/scene/resources/texture.h @@ -87,297 +87,6 @@ public: Texture2D(); }; -class BitMap; - -class ImageTexture : public Texture2D { - GDCLASS(ImageTexture, Texture2D); - RES_BASE_EXTENSION("tex"); - - mutable RID texture; - Image::Format format = Image::FORMAT_L8; - bool mipmaps = false; - int w = 0; - int h = 0; - Size2 size_override; - mutable Ref<BitMap> alpha_cache; - bool image_stored = false; - -protected: - virtual void reload_from_file() override; - - 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; - - static void _bind_methods(); - -public: - void set_image(const Ref<Image> &p_image); - static Ref<ImageTexture> create_from_image(const Ref<Image> &p_image); - - Image::Format get_format() const; - - void update(const Ref<Image> &p_image); - Ref<Image> get_image() const override; - - int get_width() const override; - int get_height() const override; - - virtual RID get_rid() const override; - - bool has_alpha() const override; - virtual void draw(RID p_canvas_item, const Point2 &p_pos, const Color &p_modulate = Color(1, 1, 1), bool p_transpose = false) const override; - virtual void draw_rect(RID p_canvas_item, const Rect2 &p_rect, bool p_tile = false, const Color &p_modulate = Color(1, 1, 1), bool p_transpose = false) const override; - virtual void draw_rect_region(RID p_canvas_item, const Rect2 &p_rect, const Rect2 &p_src_rect, const Color &p_modulate = Color(1, 1, 1), bool p_transpose = false, bool p_clip_uv = true) const override; - - bool is_pixel_opaque(int p_x, int p_y) const override; - - void set_size_override(const Size2i &p_size); - - virtual void set_path(const String &p_path, bool p_take_over = false) override; - - ImageTexture(); - ~ImageTexture(); -}; - -class PortableCompressedTexture2D : public Texture2D { - GDCLASS(PortableCompressedTexture2D, Texture2D); - -public: - enum CompressionMode { - COMPRESSION_MODE_LOSSLESS, - COMPRESSION_MODE_LOSSY, - COMPRESSION_MODE_BASIS_UNIVERSAL, - COMPRESSION_MODE_S3TC, - COMPRESSION_MODE_ETC2, - COMPRESSION_MODE_BPTC, - }; - -private: - CompressionMode compression_mode = COMPRESSION_MODE_LOSSLESS; - static bool keep_all_compressed_buffers; - bool keep_compressed_buffer = false; - Vector<uint8_t> compressed_buffer; - Size2 size; - Size2 size_override; - bool mipmaps = false; - Image::Format format = Image::FORMAT_L8; - - mutable RID texture; - mutable Ref<BitMap> alpha_cache; - - bool image_stored = false; - -protected: - Vector<uint8_t> _get_data() const; - void _set_data(const Vector<uint8_t> &p_data); - - static void _bind_methods(); - -public: - CompressionMode get_compression_mode() const; - void create_from_image(const Ref<Image> &p_image, CompressionMode p_compression_mode, bool p_normal_map = false, float p_lossy_quality = 0.8); - - Image::Format get_format() const; - - void update(const Ref<Image> &p_image); - Ref<Image> get_image() const override; - - int get_width() const override; - int get_height() const override; - - virtual RID get_rid() const override; - - bool has_alpha() const override; - virtual void draw(RID p_canvas_item, const Point2 &p_pos, const Color &p_modulate = Color(1, 1, 1), bool p_transpose = false) const override; - virtual void draw_rect(RID p_canvas_item, const Rect2 &p_rect, bool p_tile = false, const Color &p_modulate = Color(1, 1, 1), bool p_transpose = false) const override; - virtual void draw_rect_region(RID p_canvas_item, const Rect2 &p_rect, const Rect2 &p_src_rect, const Color &p_modulate = Color(1, 1, 1), bool p_transpose = false, bool p_clip_uv = true) const override; - - bool is_pixel_opaque(int p_x, int p_y) const override; - - virtual void set_path(const String &p_path, bool p_take_over = false) override; - - void set_size_override(const Size2 &p_size); - Size2 get_size_override() const; - - void set_keep_compressed_buffer(bool p_keep); - bool is_keeping_compressed_buffer() const; - - static void set_keep_all_compressed_buffers(bool p_keep); - static bool is_keeping_all_compressed_buffers(); - - PortableCompressedTexture2D(); - ~PortableCompressedTexture2D(); -}; - -VARIANT_ENUM_CAST(PortableCompressedTexture2D::CompressionMode) - -class CompressedTexture2D : public Texture2D { - GDCLASS(CompressedTexture2D, Texture2D); - -public: - enum DataFormat { - DATA_FORMAT_IMAGE, - DATA_FORMAT_PNG, - DATA_FORMAT_WEBP, - DATA_FORMAT_BASIS_UNIVERSAL, - }; - - enum { - FORMAT_VERSION = 1 - }; - - enum FormatBits { - FORMAT_BIT_STREAM = 1 << 22, - FORMAT_BIT_HAS_MIPMAPS = 1 << 23, - FORMAT_BIT_DETECT_3D = 1 << 24, - //FORMAT_BIT_DETECT_SRGB = 1 << 25, - FORMAT_BIT_DETECT_NORMAL = 1 << 26, - FORMAT_BIT_DETECT_ROUGNESS = 1 << 27, - }; - -private: - Error _load_data(const String &p_path, int &r_width, int &r_height, Ref<Image> &image, bool &r_request_3d, bool &r_request_normal, bool &r_request_roughness, int &mipmap_limit, int p_size_limit = 0); - String path_to_file; - mutable RID texture; - Image::Format format = Image::FORMAT_L8; - int w = 0; - int h = 0; - mutable Ref<BitMap> alpha_cache; - - virtual void reload_from_file() override; - - static void _requested_3d(void *p_ud); - static void _requested_roughness(void *p_ud, const String &p_normal_path, RS::TextureDetectRoughnessChannel p_roughness_channel); - static void _requested_normal(void *p_ud); - -protected: - static void _bind_methods(); - void _validate_property(PropertyInfo &p_property) const; - -public: - static Ref<Image> load_image_from_file(Ref<FileAccess> p_file, int p_size_limit); - - typedef void (*TextureFormatRequestCallback)(const Ref<CompressedTexture2D> &); - typedef void (*TextureFormatRoughnessRequestCallback)(const Ref<CompressedTexture2D> &, const String &p_normal_path, RS::TextureDetectRoughnessChannel p_roughness_channel); - - static TextureFormatRequestCallback request_3d_callback; - static TextureFormatRoughnessRequestCallback request_roughness_callback; - static TextureFormatRequestCallback request_normal_callback; - - Image::Format get_format() const; - Error load(const String &p_path); - String get_load_path() const; - - int get_width() const override; - int get_height() const override; - virtual RID get_rid() const override; - - virtual void set_path(const String &p_path, bool p_take_over) override; - - virtual void draw(RID p_canvas_item, const Point2 &p_pos, const Color &p_modulate = Color(1, 1, 1), bool p_transpose = false) const override; - virtual void draw_rect(RID p_canvas_item, const Rect2 &p_rect, bool p_tile = false, const Color &p_modulate = Color(1, 1, 1), bool p_transpose = false) const override; - virtual void draw_rect_region(RID p_canvas_item, const Rect2 &p_rect, const Rect2 &p_src_rect, const Color &p_modulate = Color(1, 1, 1), bool p_transpose = false, bool p_clip_uv = true) const override; - - virtual bool has_alpha() const override; - bool is_pixel_opaque(int p_x, int p_y) const override; - - virtual Ref<Image> get_image() const override; - - CompressedTexture2D(); - ~CompressedTexture2D(); -}; - -class ResourceFormatLoaderCompressedTexture2D : public ResourceFormatLoader { -public: - virtual Ref<Resource> load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE); - virtual void get_recognized_extensions(List<String> *p_extensions) const; - virtual bool handles_type(const String &p_type) const; - virtual String get_resource_type(const String &p_path) const; -}; - -class AtlasTexture : public Texture2D { - GDCLASS(AtlasTexture, Texture2D); - RES_BASE_EXTENSION("atlastex"); - -protected: - Ref<Texture2D> atlas; - Rect2 region; - Rect2 margin; - bool filter_clip = false; - - static void _bind_methods(); - -public: - virtual int get_width() const override; - virtual int get_height() const override; - virtual RID get_rid() const override; - - virtual bool has_alpha() const override; - - void set_atlas(const Ref<Texture2D> &p_atlas); - Ref<Texture2D> get_atlas() const; - - void set_region(const Rect2 &p_region); - Rect2 get_region() const; - - void set_margin(const Rect2 &p_margin); - Rect2 get_margin() const; - - void set_filter_clip(const bool p_enable); - bool has_filter_clip() const; - - virtual void draw(RID p_canvas_item, const Point2 &p_pos, const Color &p_modulate = Color(1, 1, 1), bool p_transpose = false) const override; - virtual void draw_rect(RID p_canvas_item, const Rect2 &p_rect, bool p_tile = false, const Color &p_modulate = Color(1, 1, 1), bool p_transpose = false) const override; - virtual void draw_rect_region(RID p_canvas_item, const Rect2 &p_rect, const Rect2 &p_src_rect, const Color &p_modulate = Color(1, 1, 1), bool p_transpose = false, bool p_clip_uv = true) const override; - virtual bool get_rect_region(const Rect2 &p_rect, const Rect2 &p_src_rect, Rect2 &r_rect, Rect2 &r_src_rect) const override; - - bool is_pixel_opaque(int p_x, int p_y) const override; - - virtual Ref<Image> get_image() const override; - - AtlasTexture(); -}; - -class Mesh; - -class MeshTexture : public Texture2D { - GDCLASS(MeshTexture, Texture2D); - RES_BASE_EXTENSION("meshtex"); - - Ref<Texture2D> base_texture; - Ref<Mesh> mesh; - Size2i size; - -protected: - static void _bind_methods(); - -public: - virtual int get_width() const override; - virtual int get_height() const override; - virtual RID get_rid() const override; - - virtual bool has_alpha() const override; - - void set_mesh(const Ref<Mesh> &p_mesh); - Ref<Mesh> get_mesh() const; - - void set_image_size(const Size2 &p_size); - Size2 get_image_size() const; - - void set_base_texture(const Ref<Texture2D> &p_texture); - Ref<Texture2D> get_base_texture() const; - - virtual void draw(RID p_canvas_item, const Point2 &p_pos, const Color &p_modulate = Color(1, 1, 1), bool p_transpose = false) const override; - virtual void draw_rect(RID p_canvas_item, const Rect2 &p_rect, bool p_tile = false, const Color &p_modulate = Color(1, 1, 1), bool p_transpose = false) const override; - virtual void draw_rect_region(RID p_canvas_item, const Rect2 &p_rect, const Rect2 &p_src_rect, const Color &p_modulate = Color(1, 1, 1), bool p_transpose = false, bool p_clip_uv = true) const override; - virtual bool get_rect_region(const Rect2 &p_rect, const Rect2 &p_src_rect, Rect2 &r_rect, Rect2 &r_src_rect) const override; - - bool is_pixel_opaque(int p_x, int p_y) const override; - - MeshTexture(); -}; - class TextureLayered : public Texture { GDCLASS(TextureLayered, Texture); @@ -411,173 +120,6 @@ public: VARIANT_ENUM_CAST(TextureLayered::LayeredType) -class ImageTextureLayered : public TextureLayered { - GDCLASS(ImageTextureLayered, TextureLayered); - - LayeredType layered_type; - - mutable RID texture; - Image::Format format = Image::FORMAT_L8; - - int width = 0; - int height = 0; - int layers = 0; - bool mipmaps = false; - - Error _create_from_images(const TypedArray<Image> &p_images); - - TypedArray<Image> _get_images() const; - void _set_images(const TypedArray<Image> &p_images); - -protected: - static void _bind_methods(); - -public: - virtual Image::Format get_format() const override; - virtual int get_width() const override; - virtual int get_height() const override; - virtual int get_layers() const override; - virtual bool has_mipmaps() const override; - virtual LayeredType get_layered_type() const override; - - Error create_from_images(Vector<Ref<Image>> p_images); - void update_layer(const Ref<Image> &p_image, int p_layer); - virtual Ref<Image> get_layer_data(int p_layer) const override; - - virtual RID get_rid() const override; - virtual void set_path(const String &p_path, bool p_take_over = false) override; - - ImageTextureLayered(LayeredType p_layered_type); - ~ImageTextureLayered(); -}; - -class Texture2DArray : public ImageTextureLayered { - GDCLASS(Texture2DArray, ImageTextureLayered) - -protected: - static void _bind_methods(); - -public: - Texture2DArray() : - ImageTextureLayered(LAYERED_TYPE_2D_ARRAY) {} - - virtual Ref<Resource> create_placeholder() const; -}; - -class Cubemap : public ImageTextureLayered { - GDCLASS(Cubemap, ImageTextureLayered); - -protected: - static void _bind_methods(); - -public: - Cubemap() : - ImageTextureLayered(LAYERED_TYPE_CUBEMAP) {} - - virtual Ref<Resource> create_placeholder() const; -}; - -class CubemapArray : public ImageTextureLayered { - GDCLASS(CubemapArray, ImageTextureLayered); - -protected: - static void _bind_methods(); - -public: - CubemapArray() : - ImageTextureLayered(LAYERED_TYPE_CUBEMAP_ARRAY) {} - - virtual Ref<Resource> create_placeholder() const; -}; - -class CompressedTextureLayered : public TextureLayered { - GDCLASS(CompressedTextureLayered, TextureLayered); - -public: - enum DataFormat { - DATA_FORMAT_IMAGE, - DATA_FORMAT_PNG, - DATA_FORMAT_WEBP, - DATA_FORMAT_BASIS_UNIVERSAL, - }; - - enum { - FORMAT_VERSION = 1 - }; - - enum FormatBits { - FORMAT_BIT_STREAM = 1 << 22, - FORMAT_BIT_HAS_MIPMAPS = 1 << 23, - }; - -private: - Error _load_data(const String &p_path, Vector<Ref<Image>> &images, int &mipmap_limit, int p_size_limit = 0); - String path_to_file; - mutable RID texture; - Image::Format format = Image::FORMAT_L8; - int w = 0; - int h = 0; - int layers = 0; - bool mipmaps = false; - LayeredType layered_type = LayeredType::LAYERED_TYPE_2D_ARRAY; - - virtual void reload_from_file() override; - -protected: - static void _bind_methods(); - void _validate_property(PropertyInfo &p_property) const; - -public: - Image::Format get_format() const override; - Error load(const String &p_path); - String get_load_path() const; - virtual LayeredType get_layered_type() const override; - - int get_width() const override; - int get_height() const override; - int get_layers() const override; - virtual bool has_mipmaps() const override; - virtual RID get_rid() const override; - - virtual void set_path(const String &p_path, bool p_take_over) override; - - virtual Ref<Image> get_layer_data(int p_layer) const override; - - CompressedTextureLayered(LayeredType p_layered_type); - ~CompressedTextureLayered(); -}; - -class CompressedTexture2DArray : public CompressedTextureLayered { - GDCLASS(CompressedTexture2DArray, CompressedTextureLayered) -public: - CompressedTexture2DArray() : - CompressedTextureLayered(LAYERED_TYPE_2D_ARRAY) {} -}; - -class CompressedCubemap : public CompressedTextureLayered { - GDCLASS(CompressedCubemap, CompressedTextureLayered); - -public: - CompressedCubemap() : - CompressedTextureLayered(LAYERED_TYPE_CUBEMAP) {} -}; - -class CompressedCubemapArray : public CompressedTextureLayered { - GDCLASS(CompressedCubemapArray, CompressedTextureLayered); - -public: - CompressedCubemapArray() : - CompressedTextureLayered(LAYERED_TYPE_CUBEMAP_ARRAY) {} -}; - -class ResourceFormatLoaderCompressedTextureLayered : public ResourceFormatLoader { -public: - virtual Ref<Resource> load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE); - virtual void get_recognized_extensions(List<String> *p_extensions) const; - virtual bool handles_type(const String &p_type) const; - virtual String get_resource_type(const String &p_path) const; -}; - class Texture3D : public Texture { GDCLASS(Texture3D, Texture); @@ -602,495 +144,4 @@ public: virtual Ref<Resource> create_placeholder() const; }; -class ImageTexture3D : public Texture3D { - GDCLASS(ImageTexture3D, Texture3D); - - mutable RID texture; - - Image::Format format = Image::FORMAT_L8; - int width = 1; - int height = 1; - int depth = 1; - bool mipmaps = false; - -protected: - static void _bind_methods(); - - Error _create(Image::Format p_format, int p_width, int p_height, int p_depth, bool p_mipmaps, const TypedArray<Image> &p_data); - void _update(const TypedArray<Image> &p_data); - -public: - virtual Image::Format get_format() const override; - virtual int get_width() const override; - virtual int get_height() const override; - virtual int get_depth() const override; - virtual bool has_mipmaps() const override; - - Error create(Image::Format p_format, int p_width, int p_height, int p_depth, bool p_mipmaps, const Vector<Ref<Image>> &p_data); - void update(const Vector<Ref<Image>> &p_data); - virtual Vector<Ref<Image>> get_data() const override; - - virtual RID get_rid() const override; - virtual void set_path(const String &p_path, bool p_take_over = false) override; - - ImageTexture3D(); - ~ImageTexture3D(); -}; - -class CompressedTexture3D : public Texture3D { - GDCLASS(CompressedTexture3D, Texture3D); - -public: - enum DataFormat { - DATA_FORMAT_IMAGE, - DATA_FORMAT_PNG, - DATA_FORMAT_WEBP, - DATA_FORMAT_BASIS_UNIVERSAL, - }; - - enum { - FORMAT_VERSION = 1 - }; - - enum FormatBits { - FORMAT_BIT_STREAM = 1 << 22, - FORMAT_BIT_HAS_MIPMAPS = 1 << 23, - }; - -private: - Error _load_data(const String &p_path, Vector<Ref<Image>> &r_data, Image::Format &r_format, int &r_width, int &r_height, int &r_depth, bool &r_mipmaps); - String path_to_file; - mutable RID texture; - Image::Format format = Image::FORMAT_L8; - int w = 0; - int h = 0; - int d = 0; - bool mipmaps = false; - - virtual void reload_from_file() override; - -protected: - static void _bind_methods(); - void _validate_property(PropertyInfo &p_property) const; - -public: - Image::Format get_format() const override; - Error load(const String &p_path); - String get_load_path() const; - - int get_width() const override; - int get_height() const override; - int get_depth() const override; - virtual bool has_mipmaps() const override; - virtual RID get_rid() const override; - - virtual void set_path(const String &p_path, bool p_take_over) override; - - virtual Vector<Ref<Image>> get_data() const override; - - CompressedTexture3D(); - ~CompressedTexture3D(); -}; - -class ResourceFormatLoaderCompressedTexture3D : public ResourceFormatLoader { -public: - virtual Ref<Resource> load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE); - virtual void get_recognized_extensions(List<String> *p_extensions) const; - virtual bool handles_type(const String &p_type) const; - virtual String get_resource_type(const String &p_path) const; -}; - -class CurveTexture : public Texture2D { - GDCLASS(CurveTexture, Texture2D); - RES_BASE_EXTENSION("curvetex") -public: - enum TextureMode { - TEXTURE_MODE_RGB, - TEXTURE_MODE_RED, - }; - -private: - mutable RID _texture; - Ref<Curve> _curve; - int _width = 256; - int _current_width = 0; - TextureMode texture_mode = TEXTURE_MODE_RGB; - TextureMode _current_texture_mode = TEXTURE_MODE_RGB; - - void _update(); - -protected: - static void _bind_methods(); - -public: - void set_width(int p_width); - int get_width() const override; - - void set_texture_mode(TextureMode p_mode); - TextureMode get_texture_mode() const; - - void ensure_default_setup(float p_min = 0, float p_max = 1); - - void set_curve(Ref<Curve> p_curve); - Ref<Curve> get_curve() const; - - virtual RID get_rid() const override; - - virtual int get_height() const override { return 1; } - virtual bool has_alpha() const override { return false; } - - CurveTexture(); - ~CurveTexture(); -}; - -VARIANT_ENUM_CAST(CurveTexture::TextureMode) - -class CurveXYZTexture : public Texture2D { - GDCLASS(CurveXYZTexture, Texture2D); - RES_BASE_EXTENSION("curvetex") - -private: - mutable RID _texture; - Ref<Curve> _curve_x; - Ref<Curve> _curve_y; - Ref<Curve> _curve_z; - int _width = 256; - int _current_width = 0; - - void _update(); - -protected: - static void _bind_methods(); - -public: - void set_width(int p_width); - int get_width() const override; - - void ensure_default_setup(float p_min = 0, float p_max = 1); - - void set_curve_x(Ref<Curve> p_curve); - Ref<Curve> get_curve_x() const; - - void set_curve_y(Ref<Curve> p_curve); - Ref<Curve> get_curve_y() const; - - void set_curve_z(Ref<Curve> p_curve); - Ref<Curve> get_curve_z() const; - - virtual RID get_rid() const override; - - virtual int get_height() const override { return 1; } - virtual bool has_alpha() const override { return false; } - - CurveXYZTexture(); - ~CurveXYZTexture(); -}; - -class GradientTexture1D : public Texture2D { - GDCLASS(GradientTexture1D, Texture2D); - -private: - Ref<Gradient> gradient; - bool update_pending = false; - RID texture; - int width = 256; - bool use_hdr = false; - - void _queue_update(); - void _update(); - -protected: - static void _bind_methods(); - -public: - void set_gradient(Ref<Gradient> p_gradient); - Ref<Gradient> get_gradient() const; - - void set_width(int p_width); - int get_width() const override; - - void set_use_hdr(bool p_enabled); - bool is_using_hdr() const; - - virtual RID get_rid() const override { return texture; } - virtual int get_height() const override { return 1; } - virtual bool has_alpha() const override { return true; } - - virtual Ref<Image> get_image() const override; - - GradientTexture1D(); - virtual ~GradientTexture1D(); -}; - -class GradientTexture2D : public Texture2D { - GDCLASS(GradientTexture2D, Texture2D); - -public: - enum Fill { - FILL_LINEAR, - FILL_RADIAL, - FILL_SQUARE, - }; - enum Repeat { - REPEAT_NONE, - REPEAT, - REPEAT_MIRROR, - }; - -private: - Ref<Gradient> gradient; - mutable RID texture; - - int width = 64; - int height = 64; - - bool use_hdr = false; - - Vector2 fill_from; - Vector2 fill_to = Vector2(1, 0); - - Fill fill = FILL_LINEAR; - Repeat repeat = REPEAT_NONE; - - float _get_gradient_offset_at(int x, int y) const; - - bool update_pending = false; - void _queue_update(); - void _update(); - -protected: - static void _bind_methods(); - -public: - void set_gradient(Ref<Gradient> p_gradient); - Ref<Gradient> get_gradient() const; - - void set_width(int p_width); - virtual int get_width() const override; - void set_height(int p_height); - virtual int get_height() const override; - - void set_use_hdr(bool p_enabled); - bool is_using_hdr() const; - - void set_fill(Fill p_fill); - Fill get_fill() const; - void set_fill_from(Vector2 p_fill_from); - Vector2 get_fill_from() const; - void set_fill_to(Vector2 p_fill_to); - Vector2 get_fill_to() const; - - void set_repeat(Repeat p_repeat); - Repeat get_repeat() const; - - virtual RID get_rid() const override; - virtual bool has_alpha() const override { return true; } - virtual Ref<Image> get_image() const override; - - GradientTexture2D(); - virtual ~GradientTexture2D(); -}; - -VARIANT_ENUM_CAST(GradientTexture2D::Fill); -VARIANT_ENUM_CAST(GradientTexture2D::Repeat); - -class AnimatedTexture : public Texture2D { - GDCLASS(AnimatedTexture, Texture2D); - - //use readers writers lock for this, since its far more times read than written to - RWLock rw_lock; - -public: - enum { - MAX_FRAMES = 256 - }; - -private: - RID proxy_ph; - RID proxy; - - struct Frame { - Ref<Texture2D> texture; - float duration = 1.0; - }; - - Frame frames[MAX_FRAMES]; - int frame_count = 1.0; - int current_frame = 0; - bool pause = false; - bool one_shot = false; - float speed_scale = 1.0; - - float time = 0.0; - - uint64_t prev_ticks = 0; - - void _update_proxy(); - -protected: - static void _bind_methods(); - void _validate_property(PropertyInfo &p_property) const; - -public: - void set_frames(int p_frames); - int get_frames() const; - - void set_current_frame(int p_frame); - int get_current_frame() const; - - void set_pause(bool p_pause); - bool get_pause() const; - - void set_one_shot(bool p_one_shot); - bool get_one_shot() const; - - void set_frame_texture(int p_frame, const Ref<Texture2D> &p_texture); - Ref<Texture2D> get_frame_texture(int p_frame) const; - - void set_frame_duration(int p_frame, float p_duration); - float get_frame_duration(int p_frame) const; - - void set_speed_scale(float p_scale); - float get_speed_scale() const; - - virtual int get_width() const override; - virtual int get_height() const override; - virtual RID get_rid() const override; - - virtual bool has_alpha() const override; - - virtual Ref<Image> get_image() const override; - - bool is_pixel_opaque(int p_x, int p_y) const override; - - AnimatedTexture(); - ~AnimatedTexture(); -}; - -class CameraTexture : public Texture2D { - GDCLASS(CameraTexture, Texture2D); - -private: - mutable RID _texture; - int camera_feed_id = 0; - CameraServer::FeedImage which_feed = CameraServer::FEED_RGBA_IMAGE; - -protected: - static void _bind_methods(); - -public: - virtual int get_width() const override; - virtual int get_height() const override; - virtual RID get_rid() const override; - virtual bool has_alpha() const override; - - virtual Ref<Image> get_image() const override; - - void set_camera_feed_id(int p_new_id); - int get_camera_feed_id() const; - - void set_which_feed(CameraServer::FeedImage p_which); - CameraServer::FeedImage get_which_feed() const; - - void set_camera_active(bool p_active); - bool get_camera_active() const; - - CameraTexture(); - ~CameraTexture(); -}; - -class PlaceholderTexture2D : public Texture2D { - GDCLASS(PlaceholderTexture2D, Texture2D) - - RID rid; - Size2 size = Size2(1, 1); - -protected: - static void _bind_methods(); - -public: - void set_size(Size2 p_size); - - virtual int get_width() const override; - virtual int get_height() const override; - virtual RID get_rid() const override; - virtual bool has_alpha() const override; - - virtual Ref<Image> get_image() const override; - - PlaceholderTexture2D(); - ~PlaceholderTexture2D(); -}; - -class PlaceholderTexture3D : public Texture3D { - GDCLASS(PlaceholderTexture3D, Texture3D) - - RID rid; - Vector3i size = Vector3i(1, 1, 1); - -protected: - static void _bind_methods(); - -public: - void set_size(const Vector3i &p_size); - Vector3i get_size() const; - virtual Image::Format get_format() const override; - virtual int get_width() const override; - virtual int get_height() const override; - virtual int get_depth() const override; - virtual bool has_mipmaps() const override; - virtual Vector<Ref<Image>> get_data() const override; - - PlaceholderTexture3D(); - ~PlaceholderTexture3D(); -}; - -class PlaceholderTextureLayered : public TextureLayered { - GDCLASS(PlaceholderTextureLayered, TextureLayered) - - RID rid; - Size2i size = Size2i(1, 1); - int layers = 1; - LayeredType layered_type = LAYERED_TYPE_2D_ARRAY; - -protected: - static void _bind_methods(); - -public: - void set_size(const Size2i &p_size); - Size2i get_size() const; - void set_layers(int p_layers); - virtual Image::Format get_format() const override; - virtual LayeredType get_layered_type() const override; - virtual int get_width() const override; - virtual int get_height() const override; - virtual int get_layers() const override; - virtual bool has_mipmaps() const override; - virtual Ref<Image> get_layer_data(int p_layer) const override; - - PlaceholderTextureLayered(LayeredType p_type); - ~PlaceholderTextureLayered(); -}; - -class PlaceholderTexture2DArray : public PlaceholderTextureLayered { - GDCLASS(PlaceholderTexture2DArray, PlaceholderTextureLayered) -public: - PlaceholderTexture2DArray() : - PlaceholderTextureLayered(LAYERED_TYPE_2D_ARRAY) {} -}; - -class PlaceholderCubemap : public PlaceholderTextureLayered { - GDCLASS(PlaceholderCubemap, PlaceholderTextureLayered) -public: - PlaceholderCubemap() : - PlaceholderTextureLayered(LAYERED_TYPE_CUBEMAP) {} -}; - -class PlaceholderCubemapArray : public PlaceholderTextureLayered { - GDCLASS(PlaceholderCubemapArray, PlaceholderTextureLayered) -public: - PlaceholderCubemapArray() : - PlaceholderTextureLayered(LAYERED_TYPE_CUBEMAP_ARRAY) {} -}; - #endif // TEXTURE_H diff --git a/scene/resources/texture_rd.cpp b/scene/resources/texture_rd.cpp new file mode 100644 index 0000000000..6f25af6863 --- /dev/null +++ b/scene/resources/texture_rd.cpp @@ -0,0 +1,346 @@ +/**************************************************************************/ +/* texture_rd.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "texture_rd.h" + +//////////////////////////////////////////////////////////////////////////// +// Texture2DRD + +void Texture2DRD::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_texture_rd_rid", "texture_rd_rid"), &Texture2DRD::set_texture_rd_rid); + ClassDB::bind_method(D_METHOD("get_texture_rd_rid"), &Texture2DRD::get_texture_rd_rid); + + ADD_PROPERTY(PropertyInfo(Variant::RID, "texture_rd_rid"), "set_texture_rd_rid", "get_texture_rd_rid"); +} + +int Texture2DRD::get_width() const { + return size.width; +} + +int Texture2DRD::get_height() const { + return size.height; +} + +RID Texture2DRD::get_rid() const { + if (texture_rid.is_null()) { + // We are in trouble, create something temporary. + texture_rid = RenderingServer::get_singleton()->texture_2d_placeholder_create(); + } + + return texture_rid; +} + +bool Texture2DRD::has_alpha() const { + return false; +} + +Ref<Image> Texture2DRD::get_image() const { + ERR_FAIL_NULL_V(RS::get_singleton(), Ref<Image>()); + if (texture_rid.is_valid()) { + return RS::get_singleton()->texture_2d_get(texture_rid); + } else { + return Ref<Image>(); + } +} + +void Texture2DRD::set_texture_rd_rid(RID p_texture_rd_rid) { + ERR_FAIL_NULL(RS::get_singleton()); + + if (p_texture_rd_rid.is_valid()) { + ERR_FAIL_NULL(RD::get_singleton()); + ERR_FAIL_COND(!RD::get_singleton()->texture_is_valid(p_texture_rd_rid)); + + RD::TextureFormat tf = RD::get_singleton()->texture_get_format(p_texture_rd_rid); + ERR_FAIL_COND(tf.texture_type != RD::TEXTURE_TYPE_2D); + ERR_FAIL_COND(tf.depth > 1); + ERR_FAIL_COND(tf.array_layers > 1); + + size.width = tf.width; + size.height = tf.height; + + texture_rd_rid = p_texture_rd_rid; + + if (texture_rid.is_valid()) { + RS::get_singleton()->texture_replace(texture_rid, RS::get_singleton()->texture_rd_create(p_texture_rd_rid)); + } else { + texture_rid = RS::get_singleton()->texture_rd_create(p_texture_rd_rid); + } + + notify_property_list_changed(); + emit_changed(); + } else if (texture_rid.is_valid()) { + RS::get_singleton()->free(texture_rid); + texture_rid = RID(); + size = Size2i(); + + notify_property_list_changed(); + emit_changed(); + } +} + +RID Texture2DRD::get_texture_rd_rid() const { + return texture_rd_rid; +} + +Texture2DRD::Texture2DRD() { + size = Size2i(); +} + +Texture2DRD::~Texture2DRD() { + if (texture_rid.is_valid()) { + ERR_FAIL_NULL(RS::get_singleton()); + RS::get_singleton()->free(texture_rid); + texture_rid = RID(); + } +} + +//////////////////////////////////////////////////////////////////////////// +// TextureLayeredRD + +void TextureLayeredRD::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_texture_rd_rid", "texture_rd_rid"), &TextureLayeredRD::set_texture_rd_rid); + ClassDB::bind_method(D_METHOD("get_texture_rd_rid"), &TextureLayeredRD::get_texture_rd_rid); + + ADD_PROPERTY(PropertyInfo(Variant::RID, "texture_rd_rid"), "set_texture_rd_rid", "get_texture_rd_rid"); +} + +TextureLayered::LayeredType TextureLayeredRD::get_layered_type() const { + return layer_type; +} + +Image::Format TextureLayeredRD::get_format() const { + return image_format; +} + +int TextureLayeredRD::get_width() const { + return size.width; +} + +int TextureLayeredRD::get_height() const { + return size.height; +} + +int TextureLayeredRD::get_layers() const { + return (int)layers; +} + +bool TextureLayeredRD::has_mipmaps() const { + return mipmaps > 1; +} + +RID TextureLayeredRD::get_rid() const { + if (texture_rid.is_null()) { + // We are in trouble, create something temporary. + texture_rid = RenderingServer::get_singleton()->texture_2d_placeholder_create(); + } + + return texture_rid; +} + +Ref<Image> TextureLayeredRD::get_layer_data(int p_layer) const { + ERR_FAIL_INDEX_V(p_layer, (int)layers, Ref<Image>()); + return RS::get_singleton()->texture_2d_layer_get(texture_rid, p_layer); +} + +void TextureLayeredRD::set_texture_rd_rid(RID p_texture_rd_rid) { + ERR_FAIL_NULL(RS::get_singleton()); + + if (p_texture_rd_rid.is_valid()) { + ERR_FAIL_NULL(RD::get_singleton()); + ERR_FAIL_COND(!RD::get_singleton()->texture_is_valid(p_texture_rd_rid)); + + RS::TextureLayeredType rs_layer_type; + RD::TextureFormat tf = RD::get_singleton()->texture_get_format(p_texture_rd_rid); + ERR_FAIL_COND(tf.texture_type != RD::TEXTURE_TYPE_2D_ARRAY); + ERR_FAIL_COND(tf.depth > 1); + switch (layer_type) { + case LAYERED_TYPE_2D_ARRAY: { + ERR_FAIL_COND(tf.array_layers <= 1); + rs_layer_type = RS::TEXTURE_LAYERED_2D_ARRAY; + } break; + case LAYERED_TYPE_CUBEMAP: { + ERR_FAIL_COND(tf.array_layers != 6); + rs_layer_type = RS::TEXTURE_LAYERED_CUBEMAP; + } break; + case LAYERED_TYPE_CUBEMAP_ARRAY: { + ERR_FAIL_COND((tf.array_layers == 0) || ((tf.array_layers % 6) != 0)); + rs_layer_type = RS::TEXTURE_LAYERED_CUBEMAP_ARRAY; + } break; + default: { + ERR_FAIL_MSG("Unknown layer type selected"); + } break; + } + + size.width = tf.width; + size.height = tf.height; + layers = tf.array_layers; + mipmaps = tf.mipmaps; + + texture_rd_rid = p_texture_rd_rid; + + if (texture_rid.is_valid()) { + RS::get_singleton()->texture_replace(texture_rid, RS::get_singleton()->texture_rd_create(p_texture_rd_rid, rs_layer_type)); + } else { + texture_rid = RS::get_singleton()->texture_rd_create(p_texture_rd_rid, rs_layer_type); + } + + image_format = RS::get_singleton()->texture_get_format(texture_rid); + + notify_property_list_changed(); + emit_changed(); + } else if (texture_rid.is_valid()) { + RS::get_singleton()->free(texture_rid); + texture_rid = RID(); + image_format = Image::FORMAT_MAX; + size = Size2i(); + layers = 0; + mipmaps = 0; + + notify_property_list_changed(); + emit_changed(); + } +} + +RID TextureLayeredRD::get_texture_rd_rid() const { + return texture_rd_rid; +} + +TextureLayeredRD::TextureLayeredRD(LayeredType p_layer_type) { + layer_type = p_layer_type; + size = Size2i(); + image_format = Image::FORMAT_MAX; + layers = 0; + mipmaps = 0; +} + +TextureLayeredRD::~TextureLayeredRD() { + if (texture_rid.is_valid()) { + ERR_FAIL_NULL(RS::get_singleton()); + RS::get_singleton()->free(texture_rid); + texture_rid = RID(); + } +} + +//////////////////////////////////////////////////////////////////////////// +// Texture3DRD + +void Texture3DRD::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_texture_rd_rid", "texture_rd_rid"), &Texture3DRD::set_texture_rd_rid); + ClassDB::bind_method(D_METHOD("get_texture_rd_rid"), &Texture3DRD::get_texture_rd_rid); + + ADD_PROPERTY(PropertyInfo(Variant::RID, "texture_rd_rid"), "set_texture_rd_rid", "get_texture_rd_rid"); +} + +Image::Format Texture3DRD::get_format() const { + return image_format; +} + +int Texture3DRD::get_width() const { + return size.x; +} + +int Texture3DRD::get_height() const { + return size.y; +} + +int Texture3DRD::get_depth() const { + return size.z; +} + +bool Texture3DRD::has_mipmaps() const { + return mipmaps > 1; +} + +RID Texture3DRD::get_rid() const { + if (texture_rid.is_null()) { + // We are in trouble, create something temporary. + texture_rid = RenderingServer::get_singleton()->texture_2d_placeholder_create(); + } + + return texture_rid; +} + +void Texture3DRD::set_texture_rd_rid(RID p_texture_rd_rid) { + ERR_FAIL_NULL(RS::get_singleton()); + + if (p_texture_rd_rid.is_valid()) { + ERR_FAIL_NULL(RD::get_singleton()); + ERR_FAIL_COND(!RD::get_singleton()->texture_is_valid(p_texture_rd_rid)); + + RD::TextureFormat tf = RD::get_singleton()->texture_get_format(p_texture_rd_rid); + ERR_FAIL_COND(tf.texture_type != RD::TEXTURE_TYPE_3D); + ERR_FAIL_COND(tf.array_layers > 1); + + size.x = tf.width; + size.y = tf.height; + size.z = tf.depth; + mipmaps = tf.mipmaps; + + texture_rd_rid = p_texture_rd_rid; + + if (texture_rid.is_valid()) { + RS::get_singleton()->texture_replace(texture_rid, RS::get_singleton()->texture_rd_create(p_texture_rd_rid)); + } else { + texture_rid = RS::get_singleton()->texture_rd_create(p_texture_rd_rid); + } + + image_format = RS::get_singleton()->texture_get_format(texture_rid); + + notify_property_list_changed(); + emit_changed(); + } else if (texture_rid.is_valid()) { + RS::get_singleton()->free(texture_rid); + texture_rid = RID(); + image_format = Image::FORMAT_MAX; + size = Vector3i(); + mipmaps = 0; + + notify_property_list_changed(); + emit_changed(); + } +} + +RID Texture3DRD::get_texture_rd_rid() const { + return texture_rd_rid; +} + +Texture3DRD::Texture3DRD() { + image_format = Image::FORMAT_MAX; + size = Vector3i(); + mipmaps = 0; +} + +Texture3DRD::~Texture3DRD() { + if (texture_rid.is_valid()) { + ERR_FAIL_NULL(RS::get_singleton()); + RS::get_singleton()->free(texture_rid); + texture_rid = RID(); + } +} diff --git a/scene/resources/texture_rd.h b/scene/resources/texture_rd.h new file mode 100644 index 0000000000..f88d6c5155 --- /dev/null +++ b/scene/resources/texture_rd.h @@ -0,0 +1,153 @@ +/**************************************************************************/ +/* texture_rd.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef TEXTURE_RD_H +#define TEXTURE_RD_H + +// Note, these classes are part of the Rendering Device based renderer. +// They are included here to ensure the correct order of registration +// is performed. +// Once the renderer has been moved into a module, these classes should +// be moved as well. + +#include "scene/resources/texture.h" + +class Texture2DRD : public Texture2D { + GDCLASS(Texture2DRD, Texture2D) + + mutable RID texture_rid; + RID texture_rd_rid; + Size2i size; + +protected: + static void _bind_methods(); + +public: + virtual int get_width() const override; + virtual int get_height() const override; + virtual RID get_rid() const override; + virtual bool has_alpha() const override; + + virtual Ref<Image> get_image() const override; + + void set_texture_rd_rid(RID p_texture_rd_rid); + RID get_texture_rd_rid() const; + + Texture2DRD(); + ~Texture2DRD(); +}; + +class TextureLayeredRD : public TextureLayered { + GDCLASS(TextureLayeredRD, TextureLayered) + + LayeredType layer_type; + + mutable RID texture_rid; + RID texture_rd_rid; + + Image::Format image_format; + Size2i size; + uint32_t layers = 0; + uint32_t mipmaps = 0; + +protected: + static void _bind_methods(); + +public: + virtual Image::Format get_format() const override; + virtual LayeredType get_layered_type() const override; + virtual int get_width() const override; + virtual int get_height() const override; + virtual int get_layers() const override; + virtual bool has_mipmaps() const override; + virtual RID get_rid() const override; + + virtual Ref<Image> get_layer_data(int p_layer) const override; + + void set_texture_rd_rid(RID p_texture_rd_rid); + RID get_texture_rd_rid() const; + + TextureLayeredRD(LayeredType p_layer_type); + ~TextureLayeredRD(); +}; + +class Texture2DArrayRD : public TextureLayeredRD { + GDCLASS(Texture2DArrayRD, TextureLayeredRD) + +public: + Texture2DArrayRD() : + TextureLayeredRD(LAYERED_TYPE_2D_ARRAY) {} +}; + +class TextureCubemapRD : public TextureLayeredRD { + GDCLASS(TextureCubemapRD, TextureLayeredRD) + +public: + TextureCubemapRD() : + TextureLayeredRD(LAYERED_TYPE_CUBEMAP) {} +}; + +class TextureCubemapArrayRD : public TextureLayeredRD { + GDCLASS(TextureCubemapArrayRD, TextureLayeredRD) + +public: + TextureCubemapArrayRD() : + TextureLayeredRD(LAYERED_TYPE_CUBEMAP_ARRAY) {} +}; + +class Texture3DRD : public Texture3D { + GDCLASS(Texture3DRD, Texture3D) + + mutable RID texture_rid; + RID texture_rd_rid; + + Image::Format image_format; + Vector3i size; + uint32_t mipmaps = 0; + +protected: + static void _bind_methods(); + +public: + virtual Image::Format get_format() const override; + virtual int get_width() const override; + virtual int get_height() const override; + virtual int get_depth() const override; + virtual bool has_mipmaps() const override; + virtual RID get_rid() const override; + + void set_texture_rd_rid(RID p_texture_rd_rid); + RID get_texture_rd_rid() const; + + Texture3DRD(); + ~Texture3DRD(); +}; + +#endif // TEXTURE_RD_H diff --git a/scene/resources/theme.cpp b/scene/resources/theme.cpp index bcbc8b94e7..799a8471b9 100644 --- a/scene/resources/theme.cpp +++ b/scene/resources/theme.cpp @@ -222,13 +222,13 @@ void Theme::set_default_font(const Ref<Font> &p_default_font) { } if (default_font.is_valid()) { - default_font->disconnect("changed", callable_mp(this, &Theme::_emit_theme_changed)); + default_font->disconnect_changed(callable_mp(this, &Theme::_emit_theme_changed)); } default_font = p_default_font; if (default_font.is_valid()) { - default_font->connect("changed", callable_mp(this, &Theme::_emit_theme_changed).bind(false), CONNECT_REFERENCE_COUNTED); + default_font->connect_changed(callable_mp(this, &Theme::_emit_theme_changed).bind(false), CONNECT_REFERENCE_COUNTED); } _emit_theme_changed(); @@ -268,13 +268,13 @@ void Theme::set_icon(const StringName &p_name, const StringName &p_theme_type, c bool existing = false; if (icon_map[p_theme_type].has(p_name) && icon_map[p_theme_type][p_name].is_valid()) { existing = true; - icon_map[p_theme_type][p_name]->disconnect("changed", callable_mp(this, &Theme::_emit_theme_changed)); + icon_map[p_theme_type][p_name]->disconnect_changed(callable_mp(this, &Theme::_emit_theme_changed)); } icon_map[p_theme_type][p_name] = p_icon; if (p_icon.is_valid()) { - icon_map[p_theme_type][p_name]->connect("changed", callable_mp(this, &Theme::_emit_theme_changed).bind(false), CONNECT_REFERENCE_COUNTED); + icon_map[p_theme_type][p_name]->connect_changed(callable_mp(this, &Theme::_emit_theme_changed).bind(false), CONNECT_REFERENCE_COUNTED); } _emit_theme_changed(!existing); @@ -314,7 +314,7 @@ void Theme::clear_icon(const StringName &p_name, const StringName &p_theme_type) ERR_FAIL_COND_MSG(!icon_map[p_theme_type].has(p_name), "Cannot clear the icon '" + String(p_name) + "' because it does not exist."); if (icon_map[p_theme_type][p_name].is_valid()) { - icon_map[p_theme_type][p_name]->disconnect("changed", callable_mp(this, &Theme::_emit_theme_changed)); + icon_map[p_theme_type][p_name]->disconnect_changed(callable_mp(this, &Theme::_emit_theme_changed)); } icon_map[p_theme_type].erase(p_name); @@ -353,7 +353,7 @@ void Theme::remove_icon_type(const StringName &p_theme_type) { for (const KeyValue<StringName, Ref<Texture2D>> &E : icon_map[p_theme_type]) { Ref<Texture2D> icon = E.value; if (icon.is_valid()) { - icon->disconnect("changed", callable_mp(this, &Theme::_emit_theme_changed)); + icon->disconnect_changed(callable_mp(this, &Theme::_emit_theme_changed)); } } @@ -378,13 +378,13 @@ void Theme::set_stylebox(const StringName &p_name, const StringName &p_theme_typ bool existing = false; if (style_map[p_theme_type].has(p_name) && style_map[p_theme_type][p_name].is_valid()) { existing = true; - style_map[p_theme_type][p_name]->disconnect("changed", callable_mp(this, &Theme::_emit_theme_changed)); + style_map[p_theme_type][p_name]->disconnect_changed(callable_mp(this, &Theme::_emit_theme_changed)); } style_map[p_theme_type][p_name] = p_style; if (p_style.is_valid()) { - style_map[p_theme_type][p_name]->connect("changed", callable_mp(this, &Theme::_emit_theme_changed).bind(false), CONNECT_REFERENCE_COUNTED); + style_map[p_theme_type][p_name]->connect_changed(callable_mp(this, &Theme::_emit_theme_changed).bind(false), CONNECT_REFERENCE_COUNTED); } _emit_theme_changed(!existing); @@ -424,7 +424,7 @@ void Theme::clear_stylebox(const StringName &p_name, const StringName &p_theme_t ERR_FAIL_COND_MSG(!style_map[p_theme_type].has(p_name), "Cannot clear the stylebox '" + String(p_name) + "' because it does not exist."); if (style_map[p_theme_type][p_name].is_valid()) { - style_map[p_theme_type][p_name]->disconnect("changed", callable_mp(this, &Theme::_emit_theme_changed)); + style_map[p_theme_type][p_name]->disconnect_changed(callable_mp(this, &Theme::_emit_theme_changed)); } style_map[p_theme_type].erase(p_name); @@ -463,7 +463,7 @@ void Theme::remove_stylebox_type(const StringName &p_theme_type) { for (const KeyValue<StringName, Ref<StyleBox>> &E : style_map[p_theme_type]) { Ref<StyleBox> style = E.value; if (style.is_valid()) { - style->disconnect("changed", callable_mp(this, &Theme::_emit_theme_changed)); + style->disconnect_changed(callable_mp(this, &Theme::_emit_theme_changed)); } } @@ -488,13 +488,13 @@ void Theme::set_font(const StringName &p_name, const StringName &p_theme_type, c bool existing = false; if (font_map[p_theme_type][p_name].is_valid()) { existing = true; - font_map[p_theme_type][p_name]->disconnect("changed", callable_mp(this, &Theme::_emit_theme_changed)); + font_map[p_theme_type][p_name]->disconnect_changed(callable_mp(this, &Theme::_emit_theme_changed)); } font_map[p_theme_type][p_name] = p_font; if (p_font.is_valid()) { - font_map[p_theme_type][p_name]->connect("changed", callable_mp(this, &Theme::_emit_theme_changed).bind(false), CONNECT_REFERENCE_COUNTED); + font_map[p_theme_type][p_name]->connect_changed(callable_mp(this, &Theme::_emit_theme_changed).bind(false), CONNECT_REFERENCE_COUNTED); } _emit_theme_changed(!existing); @@ -536,7 +536,7 @@ void Theme::clear_font(const StringName &p_name, const StringName &p_theme_type) ERR_FAIL_COND_MSG(!font_map[p_theme_type].has(p_name), "Cannot clear the font '" + String(p_name) + "' because it does not exist."); if (font_map[p_theme_type][p_name].is_valid()) { - font_map[p_theme_type][p_name]->disconnect("changed", callable_mp(this, &Theme::_emit_theme_changed)); + font_map[p_theme_type][p_name]->disconnect_changed(callable_mp(this, &Theme::_emit_theme_changed)); } font_map[p_theme_type].erase(p_name); @@ -575,7 +575,7 @@ void Theme::remove_font_type(const StringName &p_theme_type) { for (const KeyValue<StringName, Ref<Font>> &E : font_map[p_theme_type]) { Ref<Font> font = E.value; if (font.is_valid()) { - font->disconnect("changed", callable_mp(this, &Theme::_emit_theme_changed)); + font->disconnect_changed(callable_mp(this, &Theme::_emit_theme_changed)); } } @@ -1622,7 +1622,7 @@ void Theme::clear() { for (const KeyValue<StringName, Ref<Texture2D>> &F : E.value) { if (F.value.is_valid()) { Ref<Texture2D> icon = F.value; - icon->disconnect("changed", callable_mp(this, &Theme::_emit_theme_changed)); + icon->disconnect_changed(callable_mp(this, &Theme::_emit_theme_changed)); } } } @@ -1633,7 +1633,7 @@ void Theme::clear() { for (const KeyValue<StringName, Ref<StyleBox>> &F : E.value) { if (F.value.is_valid()) { Ref<StyleBox> style = F.value; - style->disconnect("changed", callable_mp(this, &Theme::_emit_theme_changed)); + style->disconnect_changed(callable_mp(this, &Theme::_emit_theme_changed)); } } } @@ -1644,7 +1644,7 @@ void Theme::clear() { for (const KeyValue<StringName, Ref<Font>> &F : E.value) { if (F.value.is_valid()) { Ref<Font> font = F.value; - font->disconnect("changed", callable_mp(this, &Theme::_emit_theme_changed)); + font->disconnect_changed(callable_mp(this, &Theme::_emit_theme_changed)); } } } diff --git a/scene/resources/tile_set.cpp b/scene/resources/tile_set.cpp index 3c0fb6b9fa..f340573c6a 100644 --- a/scene/resources/tile_set.cpp +++ b/scene/resources/tile_set.cpp @@ -30,13 +30,13 @@ #include "tile_set.h" -#include "core/core_string_names.h" #include "core/io/marshalls.h" #include "core/math/geometry_2d.h" #include "core/templates/local_vector.h" #include "core/templates/rb_set.h" #include "scene/gui/control.h" #include "scene/resources/convex_polygon_shape_2d.h" +#include "scene/resources/image_texture.h" #include "servers/navigation_server_2d.h" /////////////////////////////// TileMapPattern ////////////////////////////////////// @@ -487,7 +487,7 @@ int TileSet::add_source(Ref<TileSetSource> p_tile_set_source, int p_atlas_source p_tile_set_source->set_tile_set(this); _compute_next_source_id(); - sources[new_source_id]->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &TileSet::_source_changed)); + sources[new_source_id]->connect_changed(callable_mp(this, &TileSet::_source_changed)); terrains_cache_dirty = true; emit_changed(); @@ -498,7 +498,7 @@ int TileSet::add_source(Ref<TileSetSource> p_tile_set_source, int p_atlas_source void TileSet::remove_source(int p_source_id) { ERR_FAIL_COND_MSG(!sources.has(p_source_id), vformat("Cannot remove TileSet atlas source. No tileset atlas source with id %d.", p_source_id)); - sources[p_source_id]->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(this, &TileSet::_source_changed)); + sources[p_source_id]->disconnect_changed(callable_mp(this, &TileSet::_source_changed)); sources[p_source_id]->set_tile_set(nullptr); sources.erase(p_source_id); @@ -3814,13 +3814,13 @@ void TileSetAtlasSource::reset_state() { void TileSetAtlasSource::set_texture(Ref<Texture2D> p_texture) { if (texture.is_valid()) { - texture->disconnect(SNAME("changed"), callable_mp(this, &TileSetAtlasSource::_queue_update_padded_texture)); + texture->disconnect_changed(callable_mp(this, &TileSetAtlasSource::_queue_update_padded_texture)); } texture = p_texture; if (texture.is_valid()) { - texture->connect(SNAME("changed"), callable_mp(this, &TileSetAtlasSource::_queue_update_padded_texture)); + texture->connect_changed(callable_mp(this, &TileSetAtlasSource::_queue_update_padded_texture)); } _clear_tiles_outside_texture(); @@ -5116,6 +5116,8 @@ void TileData::remove_terrain(int p_terrain_set, int p_index) { if (terrain_set == p_terrain_set) { if (terrain == p_index) { terrain = -1; + } else if (terrain > p_index) { + terrain -= 1; } for (int i = 0; i < 16; i++) { diff --git a/scene/resources/tile_set.h b/scene/resources/tile_set.h index 70646ca5d8..4150da53db 100644 --- a/scene/resources/tile_set.h +++ b/scene/resources/tile_set.h @@ -39,6 +39,7 @@ #include "scene/main/canvas_item.h" #include "scene/resources/concave_polygon_shape_2d.h" #include "scene/resources/convex_polygon_shape_2d.h" +#include "scene/resources/image_texture.h" #include "scene/resources/navigation_polygon.h" #include "scene/resources/packed_scene.h" #include "scene/resources/physics_material.h" @@ -46,7 +47,6 @@ #ifndef DISABLE_DEPRECATED #include "scene/resources/shader.h" -#include "scene/resources/texture.h" #endif class TileMap; diff --git a/scene/resources/visual_shader.cpp b/scene/resources/visual_shader.cpp index 0c2c21380a..7f1c322c8f 100644 --- a/scene/resources/visual_shader.cpp +++ b/scene/resources/visual_shader.cpp @@ -46,6 +46,10 @@ bool VisualShaderNode::is_simple_decl() const { return simple_decl; } +int VisualShaderNode::get_default_input_port(PortType p_type) const { + return 0; +} + void VisualShaderNode::set_output_port_for_preview(int p_index) { port_preview = p_index; } @@ -378,6 +382,8 @@ bool VisualShaderNode::is_input_port_default(int p_port, Shader::Mode p_mode) co } void VisualShaderNode::_bind_methods() { + ClassDB::bind_method(D_METHOD("get_default_input_port", "type"), &VisualShaderNode::get_default_input_port); + ClassDB::bind_method(D_METHOD("set_output_port_for_preview", "port"), &VisualShaderNode::set_output_port_for_preview); ClassDB::bind_method(D_METHOD("get_output_port_for_preview"), &VisualShaderNode::get_output_port_for_preview); @@ -481,6 +487,12 @@ String VisualShaderNodeCustom::get_input_port_name(int p_port) const { return input_ports[p_port].name; } +int VisualShaderNodeCustom::get_default_input_port(PortType p_type) const { + int ret = 0; + GDVIRTUAL_CALL(_get_default_input_port, p_type, ret); + return ret; +} + int VisualShaderNodeCustom::get_output_port_count() const { return output_ports.size(); } @@ -649,6 +661,7 @@ void VisualShaderNodeCustom::_bind_methods() { GDVIRTUAL_BIND(_get_input_port_count); GDVIRTUAL_BIND(_get_input_port_type, "port"); GDVIRTUAL_BIND(_get_input_port_name, "port"); + GDVIRTUAL_BIND(_get_default_input_port, "type"); GDVIRTUAL_BIND(_get_output_port_count); GDVIRTUAL_BIND(_get_output_port_type, "port"); GDVIRTUAL_BIND(_get_output_port_name, "port"); @@ -767,7 +780,7 @@ void VisualShader::add_node(Type p_type, const Ref<VisualShaderNode> &p_node, co input->shader_type = p_type; } - n.node->connect("changed", callable_mp(this, &VisualShader::_queue_update)); + n.node->connect_changed(callable_mp(this, &VisualShader::_queue_update)); Ref<VisualShaderNodeCustom> custom = n.node; if (custom.is_valid()) { @@ -834,7 +847,7 @@ void VisualShader::remove_node(Type p_type, int p_id) { Graph *g = &graph[p_type]; ERR_FAIL_COND(!g->nodes.has(p_id)); - g->nodes[p_id].node->disconnect("changed", callable_mp(this, &VisualShader::_queue_update)); + g->nodes[p_id].node->disconnect_changed(callable_mp(this, &VisualShader::_queue_update)); g->nodes.erase(p_id); @@ -907,7 +920,7 @@ void VisualShader::replace_node(Type p_type, int p_id, const StringName &p_new_c } } - vsn->connect("changed", callable_mp(this, &VisualShader::_queue_update)); + vsn->connect_changed(callable_mp(this, &VisualShader::_queue_update)); g->nodes[p_id].node = Ref<VisualShaderNode>(vsn); _queue_update(); diff --git a/scene/resources/visual_shader.h b/scene/resources/visual_shader.h index 61418b680e..d3657eae07 100644 --- a/scene/resources/visual_shader.h +++ b/scene/resources/visual_shader.h @@ -289,6 +289,7 @@ public: virtual int get_input_port_count() const = 0; virtual PortType get_input_port_type(int p_port) const = 0; virtual String get_input_port_name(int p_port) const = 0; + virtual int get_default_input_port(PortType p_type) const; virtual void set_input_port_default_value(int p_port, const Variant &p_value, const Variant &p_prev_value = Variant()); Variant get_input_port_default_value(int p_port) const; // if NIL (default if node does not set anything) is returned, it means no default value is wanted if disconnected, thus no input var must be supplied (empty string will be supplied) @@ -367,6 +368,7 @@ protected: virtual int get_input_port_count() const override; virtual PortType get_input_port_type(int p_port) const override; virtual String get_input_port_name(int p_port) const override; + virtual int get_default_input_port(PortType p_type) const override; virtual int get_output_port_count() const override; virtual PortType get_output_port_type(int p_port) const override; @@ -384,6 +386,7 @@ protected: GDVIRTUAL0RC(int, _get_input_port_count) GDVIRTUAL1RC(PortType, _get_input_port_type, int) GDVIRTUAL1RC(String, _get_input_port_name, int) + GDVIRTUAL1RC(int, _get_default_input_port, PortType) GDVIRTUAL0RC(int, _get_output_port_count) GDVIRTUAL1RC(PortType, _get_output_port_type, int) GDVIRTUAL1RC(String, _get_output_port_name, int) diff --git a/scene/resources/visual_shader_nodes.cpp b/scene/resources/visual_shader_nodes.cpp index 7fdad8e930..4023de9023 100644 --- a/scene/resources/visual_shader_nodes.cpp +++ b/scene/resources/visual_shader_nodes.cpp @@ -30,6 +30,8 @@ #include "visual_shader_nodes.h" +#include "scene/resources/image_texture.h" + ////////////// Vector Base VisualShaderNodeVectorBase::PortType VisualShaderNodeVectorBase::get_input_port_type(int p_port) const { @@ -1725,6 +1727,135 @@ VisualShaderNodeLinearSceneDepth::VisualShaderNodeLinearSceneDepth() { simple_decl = false; } +////////////// World Position from Depth + +String VisualShaderNodeWorldPositionFromDepth::get_caption() const { + return "WorldPositionFromDepth"; +} + +int VisualShaderNodeWorldPositionFromDepth::get_input_port_count() const { + return 1; +} + +VisualShaderNodeWorldPositionFromDepth::PortType VisualShaderNodeWorldPositionFromDepth::get_input_port_type(int p_port) const { + return PORT_TYPE_VECTOR_2D; +} + +String VisualShaderNodeWorldPositionFromDepth::get_input_port_name(int p_port) const { + return "screen uv"; +} + +bool VisualShaderNodeWorldPositionFromDepth::is_input_port_default(int p_port, Shader::Mode p_mode) const { + if (p_port == 0) { + return true; + } + return false; +} + +int VisualShaderNodeWorldPositionFromDepth::get_output_port_count() const { + return 1; +} + +VisualShaderNodeWorldPositionFromDepth::PortType VisualShaderNodeWorldPositionFromDepth::get_output_port_type(int p_port) const { + return PORT_TYPE_VECTOR_3D; +} + +String VisualShaderNodeWorldPositionFromDepth::get_output_port_name(int p_port) const { + return "world position"; +} + +bool VisualShaderNodeWorldPositionFromDepth::has_output_port_preview(int p_port) const { + return false; +} + +String VisualShaderNodeWorldPositionFromDepth::generate_global(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const { + return "uniform sampler2D " + make_unique_id(p_type, p_id, "depth_tex") + " : hint_depth_texture, repeat_disable, filter_nearest;\n"; +} + +String VisualShaderNodeWorldPositionFromDepth::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const { + String code; + String uv = p_input_vars[0].is_empty() ? "SCREEN_UV" : p_input_vars[0]; + code += " {\n"; + + code += " float __log_depth = textureLod(" + make_unique_id(p_type, p_id, "depth_tex") + ", " + uv + ", 0.0).x;\n"; + if (!RenderingServer::get_singleton()->is_low_end()) { + code += " vec4 __depth_view = INV_PROJECTION_MATRIX * vec4(" + uv + " * 2.0 - 1.0, __log_depth, 1.0);\n"; + } else { + code += " vec4 __depth_view = INV_PROJECTION_MATRIX * vec4(vec3(" + uv + ", __log_depth) * 2.0 - 1.0, 1.0);\n"; + } + code += " __depth_view.xyz /= __depth_view.w;\n"; + code += vformat(" %s = (INV_VIEW_MATRIX * __depth_view).xyz;\n", p_output_vars[0]); + + code += " }\n"; + return code; +} + +VisualShaderNodeWorldPositionFromDepth::VisualShaderNodeWorldPositionFromDepth() { + simple_decl = false; +} + +////////////// Unpack Normals in World Space + +String VisualShaderNodeScreenNormalWorldSpace::get_caption() const { + return "ScreenNormalWorldSpace"; +} + +int VisualShaderNodeScreenNormalWorldSpace::get_input_port_count() const { + return 1; +} + +VisualShaderNodeScreenNormalWorldSpace::PortType VisualShaderNodeScreenNormalWorldSpace::get_input_port_type(int p_port) const { + return PORT_TYPE_VECTOR_2D; +} + +String VisualShaderNodeScreenNormalWorldSpace::get_input_port_name(int p_port) const { + return "screen uv"; +} + +bool VisualShaderNodeScreenNormalWorldSpace::is_input_port_default(int p_port, Shader::Mode p_mode) const { + if (p_port == 0) { + return true; + } + return false; +} + +int VisualShaderNodeScreenNormalWorldSpace::get_output_port_count() const { + return 1; +} + +VisualShaderNodeScreenNormalWorldSpace::PortType VisualShaderNodeScreenNormalWorldSpace::get_output_port_type(int p_port) const { + return PORT_TYPE_VECTOR_3D; +} + +String VisualShaderNodeScreenNormalWorldSpace::get_output_port_name(int p_port) const { + return "screen normal"; +} + +bool VisualShaderNodeScreenNormalWorldSpace::has_output_port_preview(int p_port) const { + return false; +} + +String VisualShaderNodeScreenNormalWorldSpace::generate_global(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const { + return "uniform sampler2D " + make_unique_id(p_type, p_id, "normal_rough_tex") + " : hint_normal_roughness_texture, repeat_disable, filter_nearest;\n"; +} + +String VisualShaderNodeScreenNormalWorldSpace::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const { + String code; + String uv = p_input_vars[0].is_empty() ? "SCREEN_UV" : p_input_vars[0]; + code += " {\n"; + + code += " vec3 __normals = textureLod(" + make_unique_id(p_type, p_id, "normal_rough_tex") + ", " + uv + ", 0.0).xyz;\n"; + code += " __normals = __normals * 2.0 - 1.0;\n"; + code += vformat(" %s = mat3(INV_VIEW_MATRIX) * __normals;\n", p_output_vars[0]); + + code += " }\n"; + return code; +} + +VisualShaderNodeScreenNormalWorldSpace::VisualShaderNodeScreenNormalWorldSpace() { + simple_decl = false; +} + ////////////// Float Op String VisualShaderNodeFloatOp::get_caption() const { @@ -4135,6 +4266,10 @@ String VisualShaderNodeStep::get_input_port_name(int p_port) const { return String(); } +int VisualShaderNodeStep::get_default_input_port(PortType p_type) const { + return 1; +} + int VisualShaderNodeStep::get_output_port_count() const { return 1; } @@ -4290,6 +4425,10 @@ String VisualShaderNodeSmoothStep::get_input_port_name(int p_port) const { return String(); } +int VisualShaderNodeSmoothStep::get_default_input_port(PortType p_type) const { + return 2; +} + int VisualShaderNodeSmoothStep::get_output_port_count() const { return 1; } @@ -7961,3 +8100,100 @@ VisualShaderNodeRemap::VisualShaderNodeRemap() { simple_decl = false; } + +////////////// RotationByAxis + +String VisualShaderNodeRotationByAxis::get_caption() const { + return "RotationByAxis"; +} + +int VisualShaderNodeRotationByAxis::get_input_port_count() const { + return 3; +} + +VisualShaderNodeRotationByAxis::PortType VisualShaderNodeRotationByAxis::get_input_port_type(int p_port) const { + switch (p_port) { + case 0: + return PORT_TYPE_VECTOR_3D; + case 1: + return PORT_TYPE_SCALAR; + case 2: + return PORT_TYPE_VECTOR_3D; + default: + break; + } + + return PORT_TYPE_SCALAR; +} + +String VisualShaderNodeRotationByAxis::get_input_port_name(int p_port) const { + switch (p_port) { + case 0: + return "input"; + case 1: + return "angle"; + case 2: + return "axis"; + default: + break; + } + + return ""; +} + +int VisualShaderNodeRotationByAxis::get_output_port_count() const { + return 2; +} + +VisualShaderNodeRotationByAxis::PortType VisualShaderNodeRotationByAxis::get_output_port_type(int p_port) const { + switch (p_port) { + case 0: + return PORT_TYPE_VECTOR_3D; + case 1: + return PORT_TYPE_TRANSFORM; + default: + break; + } + + return PORT_TYPE_SCALAR; +} + +String VisualShaderNodeRotationByAxis::get_output_port_name(int p_port) const { + switch (p_port) { + case 0: + return "output"; + case 1: + return "rotationMat"; + default: + break; + } + + return ""; +} + +bool VisualShaderNodeRotationByAxis::has_output_port_preview(int p_port) const { + return false; +} + +String VisualShaderNodeRotationByAxis::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const { + String code; + code += " {\n"; + code += vformat(" float __angle = %s;\n", p_input_vars[1]); + code += vformat(" vec3 __axis = normalize(%s);\n", p_input_vars[2]); + code += vformat(" mat3 __rot_matrix = mat3(\n"); + code += vformat(" vec3( cos(__angle)+__axis.x*__axis.x*(1.0 - cos(__angle)), __axis.x*__axis.y*(1.0-cos(__angle))-__axis.z*sin(__angle), __axis.x*__axis.z*(1.0-cos(__angle))+__axis.y*sin(__angle) ),\n"); + code += vformat(" vec3( __axis.y*__axis.x*(1.0-cos(__angle))+__axis.z*sin(__angle), cos(__angle)+__axis.y*__axis.y*(1.0-cos(__angle)), __axis.y*__axis.z*(1.0-cos(__angle))-__axis.x*sin(__angle) ),\n"); + code += vformat(" vec3( __axis.z*__axis.x*(1.0-cos(__angle))-__axis.y*sin(__angle), __axis.z*__axis.y*(1.0-cos(__angle))+__axis.x*sin(__angle), cos(__angle)+__axis.z*__axis.z*(1.0-cos(__angle)) )\n"); + code += vformat(" );\n"); + code += vformat(" %s = %s * __rot_matrix;\n", p_output_vars[0], p_input_vars[0]); + code += vformat(" %s = mat4(__rot_matrix);\n", p_output_vars[1]); + code += " }\n"; + return code; +} + +VisualShaderNodeRotationByAxis::VisualShaderNodeRotationByAxis() { + set_input_port_default_value(1, 0.0); + set_input_port_default_value(2, Vector3(0.0, 0.0, 0.0)); + + simple_decl = false; +} diff --git a/scene/resources/visual_shader_nodes.h b/scene/resources/visual_shader_nodes.h index fa6b134526..97d8df3c0b 100644 --- a/scene/resources/visual_shader_nodes.h +++ b/scene/resources/visual_shader_nodes.h @@ -31,8 +31,12 @@ #ifndef VISUAL_SHADER_NODES_H #define VISUAL_SHADER_NODES_H +#include "scene/resources/curve_texture.h" #include "scene/resources/visual_shader.h" +class Cubemap; +class Texture2DArray; + /////////////////////////////////////// /// Vector Base Node /////////////////////////////////////// @@ -676,6 +680,50 @@ public: VisualShaderNodeLinearSceneDepth(); }; +class VisualShaderNodeWorldPositionFromDepth : public VisualShaderNode { + GDCLASS(VisualShaderNodeWorldPositionFromDepth, VisualShaderNode); + +public: + virtual String get_caption() const override; + + virtual int get_input_port_count() const override; + virtual PortType get_input_port_type(int p_port) const override; + virtual String get_input_port_name(int p_port) const override; + virtual bool is_input_port_default(int p_port, Shader::Mode p_mode) const override; + + virtual int get_output_port_count() const override; + virtual PortType get_output_port_type(int p_port) const override; + virtual String get_output_port_name(int p_port) const override; + virtual bool has_output_port_preview(int p_port) const override; + + virtual String generate_global(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const override; + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; + + VisualShaderNodeWorldPositionFromDepth(); +}; + +class VisualShaderNodeScreenNormalWorldSpace : public VisualShaderNode { + GDCLASS(VisualShaderNodeScreenNormalWorldSpace, VisualShaderNode); + +public: + virtual String get_caption() const override; + + virtual int get_input_port_count() const override; + virtual PortType get_input_port_type(int p_port) const override; + virtual String get_input_port_name(int p_port) const override; + virtual bool is_input_port_default(int p_port, Shader::Mode p_mode) const override; + + virtual int get_output_port_count() const override; + virtual PortType get_output_port_type(int p_port) const override; + virtual String get_output_port_name(int p_port) const override; + virtual bool has_output_port_preview(int p_port) const override; + + virtual String generate_global(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const override; + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; + + VisualShaderNodeScreenNormalWorldSpace(); +}; + /////////////////////////////////////// /// OPS /////////////////////////////////////// @@ -1657,6 +1705,7 @@ public: virtual int get_input_port_count() const override; virtual PortType get_input_port_type(int p_port) const override; virtual String get_input_port_name(int p_port) const override; + virtual int get_default_input_port(PortType p_type) const override; virtual int get_output_port_count() const override; virtual PortType get_output_port_type(int p_port) const override; @@ -1703,6 +1752,7 @@ public: virtual int get_input_port_count() const override; virtual PortType get_input_port_type(int p_port) const override; virtual String get_input_port_name(int p_port) const override; + virtual int get_default_input_port(PortType p_type) const override; virtual int get_output_port_count() const override; virtual PortType get_output_port_type(int p_port) const override; @@ -2907,4 +2957,24 @@ public: VisualShaderNodeRemap(); }; +class VisualShaderNodeRotationByAxis : public VisualShaderNode { + GDCLASS(VisualShaderNodeRotationByAxis, VisualShaderNode); + +public: + virtual String get_caption() const override; + + virtual int get_input_port_count() const override; + virtual PortType get_input_port_type(int p_port) const override; + virtual String get_input_port_name(int p_port) const override; + + virtual int get_output_port_count() const override; + virtual PortType get_output_port_type(int p_port) const override; + virtual String get_output_port_name(int p_port) const override; + virtual bool has_output_port_preview(int p_port) const override; + + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; + + VisualShaderNodeRotationByAxis(); +}; + #endif // VISUAL_SHADER_NODES_H diff --git a/scene/resources/visual_shader_particle_nodes.cpp b/scene/resources/visual_shader_particle_nodes.cpp index 9cf42b681c..cfea6e21ee 100644 --- a/scene/resources/visual_shader_particle_nodes.cpp +++ b/scene/resources/visual_shader_particle_nodes.cpp @@ -30,7 +30,7 @@ #include "visual_shader_particle_nodes.h" -#include "core/core_string_names.h" +#include "scene/resources/image_texture.h" // VisualShaderNodeParticleEmitter @@ -637,21 +637,13 @@ void VisualShaderNodeParticleMeshEmitter::set_mesh(Ref<Mesh> p_mesh) { } if (mesh.is_valid()) { - Callable callable = callable_mp(this, &VisualShaderNodeParticleMeshEmitter::_update_textures); - - if (mesh->is_connected(CoreStringNames::get_singleton()->changed, callable)) { - mesh->disconnect(CoreStringNames::get_singleton()->changed, callable); - } + mesh->disconnect_changed(callable_mp(this, &VisualShaderNodeParticleMeshEmitter::_update_textures)); } mesh = p_mesh; if (mesh.is_valid()) { - Callable callable = callable_mp(this, &VisualShaderNodeParticleMeshEmitter::_update_textures); - - if (!mesh->is_connected(CoreStringNames::get_singleton()->changed, callable)) { - mesh->connect(CoreStringNames::get_singleton()->changed, callable); - } + mesh->connect_changed(callable_mp(this, &VisualShaderNodeParticleMeshEmitter::_update_textures)); } emit_changed(); @@ -732,7 +724,7 @@ void VisualShaderNodeParticleMeshEmitter::_bind_methods() { } VisualShaderNodeParticleMeshEmitter::VisualShaderNodeParticleMeshEmitter() { - connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &VisualShaderNodeParticleMeshEmitter::_update_textures)); + connect_changed(callable_mp(this, &VisualShaderNodeParticleMeshEmitter::_update_textures)); position_texture.instantiate(); normal_texture.instantiate(); diff --git a/scene/resources/visual_shader_particle_nodes.h b/scene/resources/visual_shader_particle_nodes.h index 08fb059534..652b5dff03 100644 --- a/scene/resources/visual_shader_particle_nodes.h +++ b/scene/resources/visual_shader_particle_nodes.h @@ -33,6 +33,8 @@ #include "scene/resources/visual_shader.h" +class ImageTexture; + // Emit nodes class VisualShaderNodeParticleEmitter : public VisualShaderNode { diff --git a/scene/resources/world_3d.cpp b/scene/resources/world_3d.cpp index 297d219caf..b8646c5387 100644 --- a/scene/resources/world_3d.cpp +++ b/scene/resources/world_3d.cpp @@ -68,6 +68,7 @@ RID World3D::get_navigation_map() const { NavigationServer3D::get_singleton()->map_set_active(navigation_map, true); NavigationServer3D::get_singleton()->map_set_cell_size(navigation_map, GLOBAL_GET("navigation/3d/default_cell_size")); NavigationServer3D::get_singleton()->map_set_cell_height(navigation_map, GLOBAL_GET("navigation/3d/default_cell_height")); + NavigationServer3D::get_singleton()->map_set_up(navigation_map, GLOBAL_GET("navigation/3d/default_up")); NavigationServer3D::get_singleton()->map_set_use_edge_connections(navigation_map, GLOBAL_GET("navigation/3d/use_edge_connections")); NavigationServer3D::get_singleton()->map_set_edge_connection_margin(navigation_map, GLOBAL_GET("navigation/3d/default_edge_connection_margin")); NavigationServer3D::get_singleton()->map_set_link_connection_radius(navigation_map, GLOBAL_GET("navigation/3d/default_link_connection_radius")); diff --git a/scene/resources/world_boundary_shape_3d.cpp b/scene/resources/world_boundary_shape_3d.cpp index 3074bd1fd8..beaaddc95e 100644 --- a/scene/resources/world_boundary_shape_3d.cpp +++ b/scene/resources/world_boundary_shape_3d.cpp @@ -69,7 +69,7 @@ void WorldBoundaryShape3D::_update_shape() { void WorldBoundaryShape3D::set_plane(const Plane &p_plane) { plane = p_plane; _update_shape(); - notify_change_to_owners(); + emit_changed(); } const Plane &WorldBoundaryShape3D::get_plane() const { diff --git a/scene/scene_string_names.cpp b/scene/scene_string_names.cpp index 536ffd1fe4..87835a9522 100644 --- a/scene/scene_string_names.cpp +++ b/scene/scene_string_names.cpp @@ -57,7 +57,6 @@ SceneStringNames::SceneStringNames() { sleeping_state_changed = StaticCString::create("sleeping_state_changed"); finished = StaticCString::create("finished"); - emission_finished = StaticCString::create("emission_finished"); animation_finished = StaticCString::create("animation_finished"); animation_changed = StaticCString::create("animation_changed"); animation_started = StaticCString::create("animation_started"); @@ -168,8 +167,6 @@ SceneStringNames::SceneStringNames() { _drop_data = StaticCString::create("_drop_data"); _can_drop_data = StaticCString::create("_can_drop_data"); - _im_update = StaticCString::create("_im_update"); // Sprite3D - baked_light_changed = StaticCString::create("baked_light_changed"); _baked_light_changed = StaticCString::create("_baked_light_changed"); diff --git a/scene/scene_string_names.h b/scene/scene_string_names.h index ca8f7a1e7d..ad1135e24c 100644 --- a/scene/scene_string_names.h +++ b/scene/scene_string_names.h @@ -93,7 +93,6 @@ public: StringName sort_children; StringName finished; - StringName emission_finished; StringName animation_finished; StringName animation_changed; StringName animation_started; @@ -180,8 +179,6 @@ public: StringName _get_minimum_size; - StringName _im_update; - StringName baked_light_changed; StringName _baked_light_changed; diff --git a/servers/display_server.cpp b/servers/display_server.cpp index 313e7218ed..5822a630db 100644 --- a/servers/display_server.cpp +++ b/servers/display_server.cpp @@ -497,6 +497,11 @@ Error DisplayServer::dialog_input_text(String p_title, String p_description, Str return OK; } +Error DisplayServer::file_dialog_show(const String &p_title, const String &p_current_directory, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const Callable &p_callback) { + WARN_PRINT("Native dialogs not supported by this display server."); + return OK; +} + int DisplayServer::keyboard_get_layout_count() const { return 0; } @@ -755,6 +760,8 @@ void DisplayServer::_bind_methods() { ClassDB::bind_method(D_METHOD("dialog_show", "title", "description", "buttons", "callback"), &DisplayServer::dialog_show); ClassDB::bind_method(D_METHOD("dialog_input_text", "title", "description", "existing_text", "callback"), &DisplayServer::dialog_input_text); + ClassDB::bind_method(D_METHOD("file_dialog_show", "title", "current_directory", "filename", "show_hidden", "mode", "filters", "callback"), &DisplayServer::file_dialog_show); + ClassDB::bind_method(D_METHOD("keyboard_get_layout_count"), &DisplayServer::keyboard_get_layout_count); ClassDB::bind_method(D_METHOD("keyboard_get_current_layout"), &DisplayServer::keyboard_get_current_layout); ClassDB::bind_method(D_METHOD("keyboard_set_current_layout", "index"), &DisplayServer::keyboard_set_current_layout); @@ -846,6 +853,12 @@ void DisplayServer::_bind_methods() { BIND_ENUM_CONSTANT(CURSOR_HELP); BIND_ENUM_CONSTANT(CURSOR_MAX); + BIND_ENUM_CONSTANT(FILE_DIALOG_MODE_OPEN_FILE); + BIND_ENUM_CONSTANT(FILE_DIALOG_MODE_OPEN_FILES); + BIND_ENUM_CONSTANT(FILE_DIALOG_MODE_OPEN_DIR); + BIND_ENUM_CONSTANT(FILE_DIALOG_MODE_OPEN_ANY); + BIND_ENUM_CONSTANT(FILE_DIALOG_MODE_SAVE_FILE); + BIND_ENUM_CONSTANT(WINDOW_MODE_WINDOWED); BIND_ENUM_CONSTANT(WINDOW_MODE_MINIMIZED); BIND_ENUM_CONSTANT(WINDOW_MODE_MAXIMIZED); diff --git a/servers/display_server.h b/servers/display_server.h index e6d9c51a67..5db9b3231f 100644 --- a/servers/display_server.h +++ b/servers/display_server.h @@ -493,6 +493,15 @@ public: virtual Error dialog_show(String p_title, String p_description, Vector<String> p_buttons, const Callable &p_callback); virtual Error dialog_input_text(String p_title, String p_description, String p_partial, const Callable &p_callback); + enum FileDialogMode { + FILE_DIALOG_MODE_OPEN_FILE, + FILE_DIALOG_MODE_OPEN_FILES, + FILE_DIALOG_MODE_OPEN_DIR, + FILE_DIALOG_MODE_OPEN_ANY, + FILE_DIALOG_MODE_SAVE_FILE + }; + virtual Error file_dialog_show(const String &p_title, const String &p_current_directory, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const Callable &p_callback); + virtual int keyboard_get_layout_count() const; virtual int keyboard_get_current_layout() const; virtual void keyboard_set_current_layout(int p_index); @@ -546,5 +555,6 @@ VARIANT_ENUM_CAST(DisplayServer::VirtualKeyboardType); VARIANT_ENUM_CAST(DisplayServer::CursorShape) VARIANT_ENUM_CAST(DisplayServer::VSyncMode) VARIANT_ENUM_CAST(DisplayServer::TTSUtteranceEvent) +VARIANT_ENUM_CAST(DisplayServer::FileDialogMode) #endif // DISPLAY_SERVER_H diff --git a/servers/navigation_server_2d.cpp b/servers/navigation_server_2d.cpp index e906db2acf..cd92d9dd2f 100644 --- a/servers/navigation_server_2d.cpp +++ b/servers/navigation_server_2d.cpp @@ -391,6 +391,8 @@ void NavigationServer2D::_bind_methods() { ClassDB::bind_method(D_METHOD("query_path", "parameters", "result"), &NavigationServer2D::query_path); ClassDB::bind_method(D_METHOD("region_create"), &NavigationServer2D::region_create); + ClassDB::bind_method(D_METHOD("region_set_enabled", "region", "enabled"), &NavigationServer2D::region_set_enabled); + ClassDB::bind_method(D_METHOD("region_get_enabled", "region"), &NavigationServer2D::region_get_enabled); ClassDB::bind_method(D_METHOD("region_set_use_edge_connections", "region", "enabled"), &NavigationServer2D::region_set_use_edge_connections); ClassDB::bind_method(D_METHOD("region_get_use_edge_connections", "region"), &NavigationServer2D::region_get_use_edge_connections); ClassDB::bind_method(D_METHOD("region_set_enter_cost", "region", "enter_cost"), &NavigationServer2D::region_set_enter_cost); @@ -413,6 +415,8 @@ void NavigationServer2D::_bind_methods() { ClassDB::bind_method(D_METHOD("link_create"), &NavigationServer2D::link_create); ClassDB::bind_method(D_METHOD("link_set_map", "link", "map"), &NavigationServer2D::link_set_map); ClassDB::bind_method(D_METHOD("link_get_map", "link"), &NavigationServer2D::link_get_map); + ClassDB::bind_method(D_METHOD("link_set_enabled", "link", "enabled"), &NavigationServer2D::link_set_enabled); + ClassDB::bind_method(D_METHOD("link_get_enabled", "link"), &NavigationServer2D::link_get_enabled); ClassDB::bind_method(D_METHOD("link_set_bidirectional", "link", "bidirectional"), &NavigationServer2D::link_set_bidirectional); ClassDB::bind_method(D_METHOD("link_is_bidirectional", "link"), &NavigationServer2D::link_is_bidirectional); ClassDB::bind_method(D_METHOD("link_set_navigation_layers", "link", "navigation_layers"), &NavigationServer2D::link_set_navigation_layers); @@ -536,6 +540,8 @@ RID FORWARD_2_C(map_get_closest_point_owner, RID, p_map, const Vector2 &, p_poin RID FORWARD_0(region_create); +void FORWARD_2(region_set_enabled, RID, p_region, bool, p_enabled, rid_to_rid, bool_to_bool); +bool FORWARD_1_C(region_get_enabled, RID, p_region, rid_to_rid); void FORWARD_2(region_set_use_edge_connections, RID, p_region, bool, p_enabled, rid_to_rid, bool_to_bool); bool FORWARD_1_C(region_get_use_edge_connections, RID, p_region, rid_to_rid); @@ -564,6 +570,8 @@ RID FORWARD_0(link_create); void FORWARD_2(link_set_map, RID, p_link, RID, p_map, rid_to_rid, rid_to_rid); RID FORWARD_1_C(link_get_map, RID, p_link, rid_to_rid); +void FORWARD_2(link_set_enabled, RID, p_link, bool, p_enabled, rid_to_rid, bool_to_bool); +bool FORWARD_1_C(link_get_enabled, RID, p_link, rid_to_rid); void FORWARD_2(link_set_bidirectional, RID, p_link, bool, p_bidirectional, rid_to_rid, bool_to_bool); bool FORWARD_1_C(link_is_bidirectional, RID, p_link, rid_to_rid); void FORWARD_2(link_set_navigation_layers, RID, p_link, uint32_t, p_navigation_layers, rid_to_rid, uint32_to_uint32); diff --git a/servers/navigation_server_2d.h b/servers/navigation_server_2d.h index b9b1e2a75e..c78edaf878 100644 --- a/servers/navigation_server_2d.h +++ b/servers/navigation_server_2d.h @@ -101,6 +101,9 @@ public: /// Creates a new region. virtual RID region_create(); + virtual void region_set_enabled(RID p_region, bool p_enabled); + virtual bool region_get_enabled(RID p_region) const; + virtual void region_set_use_edge_connections(RID p_region, bool p_enabled); virtual bool region_get_use_edge_connections(RID p_region) const; @@ -144,6 +147,9 @@ public: virtual void link_set_map(RID p_link, RID p_map); virtual RID link_get_map(RID p_link) const; + virtual void link_set_enabled(RID p_link, bool p_enabled); + virtual bool link_get_enabled(RID p_link) const; + /// Set whether this link travels in both directions. virtual void link_set_bidirectional(RID p_link, bool p_bidirectional); virtual bool link_is_bidirectional(RID p_link) const; diff --git a/servers/navigation_server_3d.cpp b/servers/navigation_server_3d.cpp index 3b26945c86..04facdb8d9 100644 --- a/servers/navigation_server_3d.cpp +++ b/servers/navigation_server_3d.cpp @@ -67,6 +67,8 @@ void NavigationServer3D::_bind_methods() { ClassDB::bind_method(D_METHOD("query_path", "parameters", "result"), &NavigationServer3D::query_path); ClassDB::bind_method(D_METHOD("region_create"), &NavigationServer3D::region_create); + ClassDB::bind_method(D_METHOD("region_set_enabled", "region", "enabled"), &NavigationServer3D::region_set_enabled); + ClassDB::bind_method(D_METHOD("region_get_enabled", "region"), &NavigationServer3D::region_get_enabled); ClassDB::bind_method(D_METHOD("region_set_use_edge_connections", "region", "enabled"), &NavigationServer3D::region_set_use_edge_connections); ClassDB::bind_method(D_METHOD("region_get_use_edge_connections", "region"), &NavigationServer3D::region_get_use_edge_connections); ClassDB::bind_method(D_METHOD("region_set_enter_cost", "region", "enter_cost"), &NavigationServer3D::region_set_enter_cost); @@ -92,6 +94,8 @@ void NavigationServer3D::_bind_methods() { ClassDB::bind_method(D_METHOD("link_create"), &NavigationServer3D::link_create); ClassDB::bind_method(D_METHOD("link_set_map", "link", "map"), &NavigationServer3D::link_set_map); ClassDB::bind_method(D_METHOD("link_get_map", "link"), &NavigationServer3D::link_get_map); + ClassDB::bind_method(D_METHOD("link_set_enabled", "link", "enabled"), &NavigationServer3D::link_set_enabled); + ClassDB::bind_method(D_METHOD("link_get_enabled", "link"), &NavigationServer3D::link_get_enabled); ClassDB::bind_method(D_METHOD("link_set_bidirectional", "link", "bidirectional"), &NavigationServer3D::link_set_bidirectional); ClassDB::bind_method(D_METHOD("link_is_bidirectional", "link"), &NavigationServer3D::link_is_bidirectional); ClassDB::bind_method(D_METHOD("link_set_navigation_layers", "link", "navigation_layers"), &NavigationServer3D::link_set_navigation_layers); @@ -185,13 +189,14 @@ NavigationServer3D::NavigationServer3D() { ERR_FAIL_COND(singleton != nullptr); singleton = this; - GLOBAL_DEF_BASIC("navigation/2d/default_cell_size", 1); + GLOBAL_DEF_BASIC("navigation/2d/default_cell_size", 1.0); GLOBAL_DEF("navigation/2d/use_edge_connections", true); - GLOBAL_DEF_BASIC("navigation/2d/default_edge_connection_margin", 1); - GLOBAL_DEF_BASIC("navigation/2d/default_link_connection_radius", 4); + GLOBAL_DEF_BASIC("navigation/2d/default_edge_connection_margin", 1.0); + GLOBAL_DEF_BASIC("navigation/2d/default_link_connection_radius", 4.0); GLOBAL_DEF_BASIC("navigation/3d/default_cell_size", 0.25); GLOBAL_DEF_BASIC("navigation/3d/default_cell_height", 0.25); + GLOBAL_DEF("navigation/3d/default_up", Vector3(0, 1, 0)); GLOBAL_DEF("navigation/3d/use_edge_connections", true); GLOBAL_DEF_BASIC("navigation/3d/default_edge_connection_margin", 0.25); GLOBAL_DEF_BASIC("navigation/3d/default_link_connection_radius", 1.0); diff --git a/servers/navigation_server_3d.h b/servers/navigation_server_3d.h index 0764690b81..391730e18f 100644 --- a/servers/navigation_server_3d.h +++ b/servers/navigation_server_3d.h @@ -117,6 +117,9 @@ public: /// Creates a new region. virtual RID region_create() = 0; + virtual void region_set_enabled(RID p_region, bool p_enabled) = 0; + virtual bool region_get_enabled(RID p_region) const = 0; + virtual void region_set_use_edge_connections(RID p_region, bool p_enabled) = 0; virtual bool region_get_use_edge_connections(RID p_region) const = 0; @@ -165,6 +168,9 @@ public: virtual void link_set_map(RID p_link, RID p_map) = 0; virtual RID link_get_map(RID p_link) const = 0; + virtual void link_set_enabled(RID p_link, bool p_enabled) = 0; + virtual bool link_get_enabled(RID p_link) const = 0; + /// Set whether this link travels in both directions. virtual void link_set_bidirectional(RID p_link, bool p_bidirectional) = 0; virtual bool link_is_bidirectional(RID p_link) const = 0; diff --git a/servers/navigation_server_3d_dummy.h b/servers/navigation_server_3d_dummy.h index 4a2e9f7439..b2d452f67a 100644 --- a/servers/navigation_server_3d_dummy.h +++ b/servers/navigation_server_3d_dummy.h @@ -64,6 +64,8 @@ public: TypedArray<RID> map_get_obstacles(RID p_map) const override { return TypedArray<RID>(); } void map_force_update(RID p_map) override {} RID region_create() override { return RID(); } + void region_set_enabled(RID p_region, bool p_enabled) override {} + bool region_get_enabled(RID p_region) const override { return false; } void region_set_use_edge_connections(RID p_region, bool p_enabled) override {} bool region_get_use_edge_connections(RID p_region) const override { return false; } void region_set_enter_cost(RID p_region, real_t p_enter_cost) override {} @@ -88,6 +90,8 @@ public: RID link_create() override { return RID(); } void link_set_map(RID p_link, RID p_map) override {} RID link_get_map(RID p_link) const override { return RID(); } + void link_set_enabled(RID p_link, bool p_enabled) override {} + bool link_get_enabled(RID p_link) const override { return false; } void link_set_bidirectional(RID p_link, bool p_bidirectional) override {} bool link_is_bidirectional(RID p_link) const override { return false; } void link_set_navigation_layers(RID p_link, uint32_t p_navigation_layers) override {} diff --git a/servers/rendering/dummy/storage/texture_storage.h b/servers/rendering/dummy/storage/texture_storage.h index 768b1ba702..71a1801de9 100644 --- a/servers/rendering/dummy/storage/texture_storage.h +++ b/servers/rendering/dummy/storage/texture_storage.h @@ -117,6 +117,8 @@ public: virtual void texture_set_path(RID p_texture, const String &p_path) override{}; virtual String texture_get_path(RID p_texture) const override { return String(); }; + virtual Image::Format texture_get_format(RID p_texture) const override { return Image::FORMAT_MAX; } + virtual void texture_set_detect_3d_callback(RID p_texture, RS::TextureDetectCallback p_callback, void *p_userdata) override{}; virtual void texture_set_detect_normal_callback(RID p_texture, RS::TextureDetectCallback p_callback, void *p_userdata) override{}; virtual void texture_set_detect_roughness_callback(RID p_texture, RS::TextureDetectRoughnessCallback p_callback, void *p_userdata) override{}; @@ -127,6 +129,7 @@ public: virtual Size2 texture_size_with_proxy(RID p_proxy) override { return Size2(); }; + virtual void texture_rd_initialize(RID p_texture, const RID &p_rd_texture, const RS::TextureLayeredType p_layer_type = RS::TEXTURE_LAYERED_2D_ARRAY) override{}; virtual RID texture_get_rd_texture(RID p_texture, bool p_srgb = false) const override { return RID(); }; virtual uint64_t texture_get_native_handle(RID p_texture, bool p_srgb = false) const override { return 0; }; diff --git a/servers/rendering/renderer_rd/effects/copy_effects.cpp b/servers/rendering/renderer_rd/effects/copy_effects.cpp index 86484c982a..eba1c145e3 100644 --- a/servers/rendering/renderer_rd/effects/copy_effects.cpp +++ b/servers/rendering/renderer_rd/effects/copy_effects.cpp @@ -962,7 +962,7 @@ void CopyEffects::set_color_raster(RID p_dest_texture, const Color &p_color, con RD::get_singleton()->draw_list_end(); } -void CopyEffects::copy_cubemap_to_dp(RID p_source_rd_texture, RID p_dst_framebuffer, const Rect2 &p_rect, const Vector2 &p_dst_size, float p_z_near, float p_z_far, bool p_dp_flip) { +void CopyEffects::copy_cubemap_to_dp(RID p_source_rd_texture, RID p_dst_framebuffer, const Rect2 &p_rect, const Vector2 &p_dst_size, float p_z_near, float p_z_far, bool p_dp_flip, BitField<RD::BarrierMask> p_post_barrier) { UniformSetCacheRD *uniform_set_cache = UniformSetCacheRD::get_singleton(); ERR_FAIL_NULL(uniform_set_cache); MaterialStorage *material_storage = MaterialStorage::get_singleton(); @@ -994,7 +994,7 @@ void CopyEffects::copy_cubemap_to_dp(RID p_source_rd_texture, RID p_dst_framebuf RD::get_singleton()->draw_list_set_push_constant(draw_list, &push_constant, sizeof(CopyToDPPushConstant)); RD::get_singleton()->draw_list_draw(draw_list, true); - RD::get_singleton()->draw_list_end(RD::BARRIER_MASK_RASTER | RD::BARRIER_MASK_TRANSFER); + RD::get_singleton()->draw_list_end(p_post_barrier); } void CopyEffects::cubemap_downsample(RID p_source_cubemap, RID p_dest_cubemap, const Size2i &p_size) { diff --git a/servers/rendering/renderer_rd/effects/copy_effects.h b/servers/rendering/renderer_rd/effects/copy_effects.h index 3cd26d0f7e..470ac1acee 100644 --- a/servers/rendering/renderer_rd/effects/copy_effects.h +++ b/servers/rendering/renderer_rd/effects/copy_effects.h @@ -341,7 +341,7 @@ public: void set_color(RID p_dest_texture, const Color &p_color, const Rect2i &p_region, bool p_8bit_dst = false); void set_color_raster(RID p_dest_texture, const Color &p_color, const Rect2i &p_region); - void copy_cubemap_to_dp(RID p_source_rd_texture, RID p_dst_framebuffer, const Rect2 &p_rect, const Vector2 &p_dst_size, float p_z_near, float p_z_far, bool p_dp_flip); + void copy_cubemap_to_dp(RID p_source_rd_texture, RID p_dst_framebuffer, const Rect2 &p_rect, const Vector2 &p_dst_size, float p_z_near, float p_z_far, bool p_dp_flip, BitField<RD::BarrierMask> p_post_barrier = RD::BARRIER_MASK_RASTER | RD::BARRIER_MASK_TRANSFER); void cubemap_downsample(RID p_source_cubemap, RID p_dest_cubemap, const Size2i &p_size); void cubemap_downsample_raster(RID p_source_cubemap, RID p_dest_framebuffer, uint32_t p_face_id, const Size2i &p_size); void cubemap_filter(RID p_source_cubemap, Vector<RID> p_dest_cubemap, bool p_use_array); diff --git a/servers/rendering/renderer_rd/environment/sky.cpp b/servers/rendering/renderer_rd/environment/sky.cpp index 390ce40e65..ebf3c5f619 100644 --- a/servers/rendering/renderer_rd/environment/sky.cpp +++ b/servers/rendering/renderer_rd/environment/sky.cpp @@ -1021,25 +1021,27 @@ void SkyRD::setup_sky(RID p_env, Ref<RenderSceneBuffersRD> p_render_buffers, con material = nullptr; } } + } - if (!material) { - sky_material = sky_shader.default_material; - material = static_cast<SkyMaterialData *>(material_storage->material_get_data(sky_material, RendererRD::MaterialStorage::SHADER_TYPE_SKY)); - } + if (!material) { + sky_material = sky_shader.default_material; + material = static_cast<SkyMaterialData *>(material_storage->material_get_data(sky_material, RendererRD::MaterialStorage::SHADER_TYPE_SKY)); + } - ERR_FAIL_COND(!material); + ERR_FAIL_COND(!material); - shader_data = material->shader_data; + shader_data = material->shader_data; - ERR_FAIL_COND(!shader_data); + ERR_FAIL_COND(!shader_data); - material->set_as_used(); + material->set_as_used(); - // Save our screen size, our buffers will already have been cleared + if (sky) { + // Save our screen size; our buffers will already have been cleared. sky->screen_size.x = p_screen_size.x < 4 ? 4 : p_screen_size.x; sky->screen_size.y = p_screen_size.y < 4 ? 4 : p_screen_size.y; - // Trigger updating radiance buffers + // Trigger updating radiance buffers. if (sky->radiance.is_null()) { invalidate_sky(sky); update_dirty_skys(); @@ -1065,107 +1067,109 @@ void SkyRD::setup_sky(RID p_env, Ref<RenderSceneBuffersRD> p_render_buffers, con sky->prev_position = p_cam_transform.origin; sky->reflection.dirty = true; } + } - sky_scene_state.ubo.directional_light_count = 0; - if (shader_data->uses_light) { - // Run through the list of lights in the scene and pick out the Directional Lights. - // This can't be done in RenderSceneRenderRD::_setup lights because that needs to be called - // after the depth prepass, but this runs before the depth prepass - for (int i = 0; i < (int)p_lights.size(); i++) { - if (!light_storage->owns_light_instance(p_lights[i])) { - continue; + sky_scene_state.ubo.directional_light_count = 0; + if (shader_data->uses_light) { + // Run through the list of lights in the scene and pick out the Directional Lights. + // This can't be done in RenderSceneRenderRD::_setup lights because that needs to be called + // after the depth prepass, but this runs before the depth prepass. + for (int i = 0; i < (int)p_lights.size(); i++) { + if (!light_storage->owns_light_instance(p_lights[i])) { + continue; + } + RID base = light_storage->light_instance_get_base_light(p_lights[i]); + + ERR_CONTINUE(base.is_null()); + + RS::LightType type = light_storage->light_get_type(base); + if (type == RS::LIGHT_DIRECTIONAL && light_storage->light_directional_get_sky_mode(base) != RS::LIGHT_DIRECTIONAL_SKY_MODE_LIGHT_ONLY) { + SkyDirectionalLightData &sky_light_data = sky_scene_state.directional_lights[sky_scene_state.ubo.directional_light_count]; + Transform3D light_transform = light_storage->light_instance_get_base_transform(p_lights[i]); + Vector3 world_direction = light_transform.basis.xform(Vector3(0, 0, 1)).normalized(); + + sky_light_data.direction[0] = world_direction.x; + sky_light_data.direction[1] = world_direction.y; + sky_light_data.direction[2] = world_direction.z; + + float sign = light_storage->light_is_negative(base) ? -1 : 1; + sky_light_data.energy = sign * light_storage->light_get_param(base, RS::LIGHT_PARAM_ENERGY); + + if (p_scene_render->is_using_physical_light_units()) { + sky_light_data.energy *= light_storage->light_get_param(base, RS::LIGHT_PARAM_INTENSITY); + } + + if (p_camera_attributes.is_valid()) { + sky_light_data.energy *= RSG::camera_attributes->camera_attributes_get_exposure_normalization_factor(p_camera_attributes); } - RID base = light_storage->light_instance_get_base_light(p_lights[i]); - - ERR_CONTINUE(base.is_null()); - - RS::LightType type = light_storage->light_get_type(base); - if (type == RS::LIGHT_DIRECTIONAL && light_storage->light_directional_get_sky_mode(base) != RS::LIGHT_DIRECTIONAL_SKY_MODE_LIGHT_ONLY) { - SkyDirectionalLightData &sky_light_data = sky_scene_state.directional_lights[sky_scene_state.ubo.directional_light_count]; - Transform3D light_transform = light_storage->light_instance_get_base_transform(p_lights[i]); - Vector3 world_direction = light_transform.basis.xform(Vector3(0, 0, 1)).normalized(); - - sky_light_data.direction[0] = world_direction.x; - sky_light_data.direction[1] = world_direction.y; - sky_light_data.direction[2] = world_direction.z; - - float sign = light_storage->light_is_negative(base) ? -1 : 1; - sky_light_data.energy = sign * light_storage->light_get_param(base, RS::LIGHT_PARAM_ENERGY); - - if (p_scene_render->is_using_physical_light_units()) { - sky_light_data.energy *= light_storage->light_get_param(base, RS::LIGHT_PARAM_INTENSITY); - } - - if (p_camera_attributes.is_valid()) { - sky_light_data.energy *= RSG::camera_attributes->camera_attributes_get_exposure_normalization_factor(p_camera_attributes); - } - - Color linear_col = light_storage->light_get_color(base).srgb_to_linear(); - sky_light_data.color[0] = linear_col.r; - sky_light_data.color[1] = linear_col.g; - sky_light_data.color[2] = linear_col.b; - - sky_light_data.enabled = true; - - float angular_diameter = light_storage->light_get_param(base, RS::LIGHT_PARAM_SIZE); - if (angular_diameter > 0.0) { - // I know tan(0) is 0, but let's not risk it with numerical precision. - // technically this will keep expanding until reaching the sun, but all we care - // is expand until we reach the radius of the near plane (there can't be more occluders than that) - angular_diameter = Math::tan(Math::deg_to_rad(angular_diameter)); - } else { - angular_diameter = 0.0; - } - sky_light_data.size = angular_diameter; - sky_scene_state.ubo.directional_light_count++; - if (sky_scene_state.ubo.directional_light_count >= sky_scene_state.max_directional_lights) { - break; - } + + Color linear_col = light_storage->light_get_color(base).srgb_to_linear(); + sky_light_data.color[0] = linear_col.r; + sky_light_data.color[1] = linear_col.g; + sky_light_data.color[2] = linear_col.b; + + sky_light_data.enabled = true; + + float angular_diameter = light_storage->light_get_param(base, RS::LIGHT_PARAM_SIZE); + if (angular_diameter > 0.0) { + // I know tan(0) is 0, but let's not risk it with numerical precision. + // Technically this will keep expanding until reaching the sun, but all we care about + // is expanding until we reach the radius of the near plane. There can't be more occluders than that. + angular_diameter = Math::tan(Math::deg_to_rad(angular_diameter)); + } else { + angular_diameter = 0.0; } - } - // Check whether the directional_light_buffer changes - bool light_data_dirty = false; - - // Light buffer is dirty if we have fewer or more lights - // If we have fewer lights, make sure that old lights are disabled - if (sky_scene_state.ubo.directional_light_count != sky_scene_state.last_frame_directional_light_count) { - light_data_dirty = true; - for (uint32_t i = sky_scene_state.ubo.directional_light_count; i < sky_scene_state.max_directional_lights; i++) { - sky_scene_state.directional_lights[i].enabled = false; - sky_scene_state.last_frame_directional_lights[i].enabled = false; + sky_light_data.size = angular_diameter; + sky_scene_state.ubo.directional_light_count++; + if (sky_scene_state.ubo.directional_light_count >= sky_scene_state.max_directional_lights) { + break; } } + } + // Check whether the directional_light_buffer changes. + bool light_data_dirty = false; + + // Light buffer is dirty if we have fewer or more lights. + // If we have fewer lights, make sure that old lights are disabled. + if (sky_scene_state.ubo.directional_light_count != sky_scene_state.last_frame_directional_light_count) { + light_data_dirty = true; + for (uint32_t i = sky_scene_state.ubo.directional_light_count; i < sky_scene_state.max_directional_lights; i++) { + sky_scene_state.directional_lights[i].enabled = false; + sky_scene_state.last_frame_directional_lights[i].enabled = false; + } + } - if (!light_data_dirty) { - for (uint32_t i = 0; i < sky_scene_state.ubo.directional_light_count; i++) { - if (sky_scene_state.directional_lights[i].direction[0] != sky_scene_state.last_frame_directional_lights[i].direction[0] || - sky_scene_state.directional_lights[i].direction[1] != sky_scene_state.last_frame_directional_lights[i].direction[1] || - sky_scene_state.directional_lights[i].direction[2] != sky_scene_state.last_frame_directional_lights[i].direction[2] || - sky_scene_state.directional_lights[i].energy != sky_scene_state.last_frame_directional_lights[i].energy || - sky_scene_state.directional_lights[i].color[0] != sky_scene_state.last_frame_directional_lights[i].color[0] || - sky_scene_state.directional_lights[i].color[1] != sky_scene_state.last_frame_directional_lights[i].color[1] || - sky_scene_state.directional_lights[i].color[2] != sky_scene_state.last_frame_directional_lights[i].color[2] || - sky_scene_state.directional_lights[i].enabled != sky_scene_state.last_frame_directional_lights[i].enabled || - sky_scene_state.directional_lights[i].size != sky_scene_state.last_frame_directional_lights[i].size) { - light_data_dirty = true; - break; - } + if (!light_data_dirty) { + for (uint32_t i = 0; i < sky_scene_state.ubo.directional_light_count; i++) { + if (sky_scene_state.directional_lights[i].direction[0] != sky_scene_state.last_frame_directional_lights[i].direction[0] || + sky_scene_state.directional_lights[i].direction[1] != sky_scene_state.last_frame_directional_lights[i].direction[1] || + sky_scene_state.directional_lights[i].direction[2] != sky_scene_state.last_frame_directional_lights[i].direction[2] || + sky_scene_state.directional_lights[i].energy != sky_scene_state.last_frame_directional_lights[i].energy || + sky_scene_state.directional_lights[i].color[0] != sky_scene_state.last_frame_directional_lights[i].color[0] || + sky_scene_state.directional_lights[i].color[1] != sky_scene_state.last_frame_directional_lights[i].color[1] || + sky_scene_state.directional_lights[i].color[2] != sky_scene_state.last_frame_directional_lights[i].color[2] || + sky_scene_state.directional_lights[i].enabled != sky_scene_state.last_frame_directional_lights[i].enabled || + sky_scene_state.directional_lights[i].size != sky_scene_state.last_frame_directional_lights[i].size) { + light_data_dirty = true; + break; } } + } - if (light_data_dirty) { - RD::get_singleton()->buffer_update(sky_scene_state.directional_light_buffer, 0, sizeof(SkyDirectionalLightData) * sky_scene_state.max_directional_lights, sky_scene_state.directional_lights); + if (light_data_dirty) { + RD::get_singleton()->buffer_update(sky_scene_state.directional_light_buffer, 0, sizeof(SkyDirectionalLightData) * sky_scene_state.max_directional_lights, sky_scene_state.directional_lights); - SkyDirectionalLightData *temp = sky_scene_state.last_frame_directional_lights; - sky_scene_state.last_frame_directional_lights = sky_scene_state.directional_lights; - sky_scene_state.directional_lights = temp; - sky_scene_state.last_frame_directional_light_count = sky_scene_state.ubo.directional_light_count; + SkyDirectionalLightData *temp = sky_scene_state.last_frame_directional_lights; + sky_scene_state.last_frame_directional_lights = sky_scene_state.directional_lights; + sky_scene_state.directional_lights = temp; + sky_scene_state.last_frame_directional_light_count = sky_scene_state.ubo.directional_light_count; + if (sky) { sky->reflection.dirty = true; } } } - //setup fog variables + // Setup fog variables. sky_scene_state.ubo.volumetric_fog_enabled = false; if (p_render_buffers.is_valid()) { if (p_render_buffers->has_custom_data(RB_SCOPE_FOG)) { @@ -1179,7 +1183,7 @@ void SkyRD::setup_sky(RID p_env, Ref<RenderSceneBuffersRD> p_render_buffers, con sky_scene_state.ubo.volumetric_fog_inv_length = 1.0; } - float fog_detail_spread = fog->spread; //reverse lookup + float fog_detail_spread = fog->spread; // Reverse lookup. if (fog_detail_spread > 0.0) { sky_scene_state.ubo.volumetric_fog_detail_spread = 1.0 / fog_detail_spread; } else { @@ -1192,9 +1196,9 @@ void SkyRD::setup_sky(RID p_env, Ref<RenderSceneBuffersRD> p_render_buffers, con sky_scene_state.view_count = p_view_count; sky_scene_state.cam_transform = p_cam_transform; - sky_scene_state.cam_projection = p_cam_projection; // We only use this when rendering a single view + sky_scene_state.cam_projection = p_cam_projection; // We only use this when rendering a single view. - // Our info in our UBO is only used if we're rendering stereo + // Our info in our UBO is only used if we're rendering stereo. for (uint32_t i = 0; i < p_view_count; i++) { Projection view_inv_projection = p_view_projections[i].inverse(); if (p_view_count > 1) { @@ -1211,7 +1215,7 @@ void SkyRD::setup_sky(RID p_env, Ref<RenderSceneBuffersRD> p_render_buffers, con sky_scene_state.ubo.view_eye_offsets[i][3] = 0.0; } - sky_scene_state.ubo.z_far = p_view_projections[0].get_z_far(); // Should be the same for all projection + sky_scene_state.ubo.z_far = p_view_projections[0].get_z_far(); // Should be the same for all projection. sky_scene_state.ubo.fog_enabled = RendererSceneRenderRD::get_singleton()->environment_get_fog_enabled(p_env); sky_scene_state.ubo.fog_density = RendererSceneRenderRD::get_singleton()->environment_get_fog_density(p_env); sky_scene_state.ubo.fog_aerial_perspective = RendererSceneRenderRD::get_singleton()->environment_get_fog_aerial_perspective(p_env); 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 7a2dbe0dd7..6bef792922 100644 --- a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp +++ b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp @@ -944,25 +944,27 @@ void RenderForwardClustered::_fill_render_list(RenderListType p_render_list, con // LOD if (p_render_data->scene_data->screen_mesh_lod_threshold > 0.0 && mesh_storage->mesh_surface_has_lod(surf->surface)) { - // Get the LOD support points on the mesh AABB. - Vector3 lod_support_min = inst->transformed_aabb.get_support(p_render_data->scene_data->cam_transform.basis.get_column(Vector3::AXIS_Z)); - Vector3 lod_support_max = inst->transformed_aabb.get_support(-p_render_data->scene_data->cam_transform.basis.get_column(Vector3::AXIS_Z)); - - // Get the distances to those points on the AABB from the camera origin. - float distance_min = (float)p_render_data->scene_data->cam_transform.origin.distance_to(lod_support_min); - float distance_max = (float)p_render_data->scene_data->cam_transform.origin.distance_to(lod_support_max); - float distance = 0.0; - if (distance_min * distance_max < 0.0) { - //crossing plane - distance = 0.0; - } else if (distance_min >= 0.0) { - distance = distance_min; - } else if (distance_max <= 0.0) { - distance = -distance_max; + // Check if camera is NOT inside the mesh AABB. + if (!inst->transformed_aabb.has_point(p_render_data->scene_data->cam_transform.origin)) { + // Get the LOD support points on the mesh AABB. + Vector3 lod_support_min = inst->transformed_aabb.get_support(p_render_data->scene_data->cam_transform.basis.get_column(Vector3::AXIS_Z)); + Vector3 lod_support_max = inst->transformed_aabb.get_support(-p_render_data->scene_data->cam_transform.basis.get_column(Vector3::AXIS_Z)); + + // Get the distances to those points on the AABB from the camera origin. + float distance_min = (float)p_render_data->scene_data->cam_transform.origin.distance_to(lod_support_min); + float distance_max = (float)p_render_data->scene_data->cam_transform.origin.distance_to(lod_support_max); + + if (distance_min * distance_max < 0.0) { + //crossing plane + distance = 0.0; + } else if (distance_min >= 0.0) { + distance = distance_min; + } else if (distance_max <= 0.0) { + distance = -distance_max; + } } - if (p_render_data->scene_data->cam_orthogonal) { distance = 1.0; } 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 17e7388900..377aab1354 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 @@ -701,7 +701,7 @@ void SceneShaderForwardClustered::init(const String p_defines) { actions.render_mode_defines["cull_front"] = "#define DO_SIDE_CHECK\n"; actions.render_mode_defines["cull_disabled"] = "#define DO_SIDE_CHECK\n"; actions.render_mode_defines["particle_trails"] = "#define USE_PARTICLE_TRAILS\n"; - actions.render_mode_defines["depth_draw_opaque"] = "#define USE_OPAQUE_PREPASS\n"; + actions.render_mode_defines["depth_prepass_alpha"] = "#define USE_OPAQUE_PREPASS\n"; bool force_lambert = GLOBAL_GET("rendering/shading/overrides/force_lambert_over_burley"); 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 618348c688..c29204ed1e 100644 --- a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp +++ b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp @@ -1245,14 +1245,15 @@ void RenderForwardMobile::_render_shadow_pass(RID p_light, RID p_shadow_atlas, i _render_shadow_append(render_fb, p_instances, light_projection, light_transform, zfar, 0, 0, false, false, use_pancake, p_camera_plane, p_lod_distance_multiplier, p_screen_mesh_lod_threshold, Rect2(), false, true, true, true, p_render_info); if (finalize_cubemap) { _render_shadow_process(); - _render_shadow_end(); - //reblit + _render_shadow_end(RD::BARRIER_MASK_FRAGMENT); + + // reblit Rect2 atlas_rect_norm = atlas_rect; atlas_rect_norm.position /= float(atlas_size); atlas_rect_norm.size /= float(atlas_size); - copy_effects->copy_cubemap_to_dp(render_texture, atlas_fb, atlas_rect_norm, atlas_rect.size, light_projection.get_z_near(), light_projection.get_z_far(), false); + copy_effects->copy_cubemap_to_dp(render_texture, atlas_fb, atlas_rect_norm, atlas_rect.size, light_projection.get_z_near(), light_projection.get_z_far(), false, RD::BARRIER_MASK_NO_BARRIER); atlas_rect_norm.position += Vector2(dual_paraboloid_offset) * atlas_rect_norm.size; - copy_effects->copy_cubemap_to_dp(render_texture, atlas_fb, atlas_rect_norm, atlas_rect.size, light_projection.get_z_near(), light_projection.get_z_far(), true); + copy_effects->copy_cubemap_to_dp(render_texture, atlas_fb, atlas_rect_norm, atlas_rect.size, light_projection.get_z_near(), light_projection.get_z_far(), true, RD::BARRIER_MASK_NO_BARRIER); //restore transform so it can be properly used light_storage->light_instance_set_shadow_transform(p_light, Projection(), light_storage->light_instance_get_base_transform(p_light), zfar, 0, 0, 0); @@ -1362,7 +1363,7 @@ void RenderForwardMobile::_render_shadow_end(uint32_t p_barrier) { } if (p_barrier != RD::BARRIER_MASK_NO_BARRIER) { - RD::get_singleton()->barrier(RD::BARRIER_MASK_RASTER, p_barrier); + RD::get_singleton()->barrier(RD::BARRIER_MASK_FRAGMENT, p_barrier); } RD::get_singleton()->draw_command_end_label(); } 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 e432dc87d5..e4498ac533 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 @@ -589,7 +589,7 @@ void SceneShaderForwardMobile::init(const String p_defines) { actions.render_mode_defines["cull_front"] = "#define DO_SIDE_CHECK\n"; actions.render_mode_defines["cull_disabled"] = "#define DO_SIDE_CHECK\n"; actions.render_mode_defines["particle_trails"] = "#define USE_PARTICLE_TRAILS\n"; - actions.render_mode_defines["depth_draw_opaque"] = "#define USE_OPAQUE_PREPASS\n"; + actions.render_mode_defines["depth_prepass_alpha"] = "#define USE_OPAQUE_PREPASS\n"; bool force_lambert = GLOBAL_GET("rendering/shading/overrides/force_lambert_over_burley"); if (!force_lambert) { diff --git a/servers/rendering/renderer_rd/shaders/effects/screen_space_reflection.glsl b/servers/rendering/renderer_rd/shaders/effects/screen_space_reflection.glsl index 1626244b0a..1e01d94533 100644 --- a/servers/rendering/renderer_rd/shaders/effects/screen_space_reflection.glsl +++ b/servers/rendering/renderer_rd/shaders/effects/screen_space_reflection.glsl @@ -263,7 +263,9 @@ void main() { // Schlick term. float metallic = texelFetch(source_metallic, ssC << 1, 0).w; - float f0 = mix(0.04, 0.37, metallic); // The default value of R0 is 0.04 and the maximum value is considered to be Germanium with R0 value of 0.37 + // F0 is the reflectance of normally incident light (perpendicular to the surface). + // Dielectric materials have a widely accepted default value of 0.04. We assume that metals reflect all light, so their F0 is 1.0. + float f0 = mix(0.04, 1.0, metallic); float m = clamp(1.0 - dot(normal, -view_dir), 0.0, 1.0); float m2 = m * m; m = m2 * m2 * m; // pow(m,5) diff --git a/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered.glsl b/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered.glsl index 8a7008492e..9214a953aa 100644 --- a/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered.glsl +++ b/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered.glsl @@ -875,11 +875,15 @@ void fragment_shader(in SceneData scene_data) { alpha = compute_alpha_antialiasing_edge(alpha, alpha_texture_coordinate, alpha_antialiasing_edge); #endif // ALPHA_ANTIALIASING_EDGE_USED +#ifdef MODE_RENDER_DEPTH #ifdef USE_OPAQUE_PREPASS +#ifndef ALPHA_SCISSOR_USED if (alpha < scene_data.opaque_prepass_threshold) { discard; } +#endif // !ALPHA_SCISSOR_USED #endif // USE_OPAQUE_PREPASS +#endif // MODE_RENDER_DEPTH #endif // !USE_SHADOW_TO_OPACITY @@ -2038,8 +2042,8 @@ void fragment_shader(in SceneData scene_data) { if (alpha < alpha_scissor) { discard; } -#endif // ALPHA_SCISSOR_USED - +#else +#ifdef MODE_RENDER_DEPTH #ifdef USE_OPAQUE_PREPASS if (alpha < scene_data.opaque_prepass_threshold) { @@ -2047,6 +2051,8 @@ void fragment_shader(in SceneData scene_data) { } #endif // USE_OPAQUE_PREPASS +#endif // MODE_RENDER_DEPTH +#endif // ALPHA_SCISSOR_USED #endif // USE_SHADOW_TO_OPACITY diff --git a/servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile.glsl b/servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile.glsl index 5f6e4a8c28..b63eea1401 100644 --- a/servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile.glsl +++ b/servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile.glsl @@ -817,11 +817,15 @@ void main() { alpha = compute_alpha_antialiasing_edge(alpha, alpha_texture_coordinate, alpha_antialiasing_edge); #endif // ALPHA_ANTIALIASING_EDGE_USED +#ifdef MODE_RENDER_DEPTH #ifdef USE_OPAQUE_PREPASS +#ifndef ALPHA_SCISSOR_USED if (alpha < scene_data.opaque_prepass_threshold) { discard; } +#endif // !ALPHA_SCISSOR_USED #endif // USE_OPAQUE_PREPASS +#endif // MODE_RENDER_DEPTH #endif // !USE_SHADOW_TO_OPACITY @@ -1685,8 +1689,8 @@ void main() { if (alpha < alpha_scissor) { discard; } -#endif // ALPHA_SCISSOR_USED - +#else +#ifdef MODE_RENDER_DEPTH #ifdef USE_OPAQUE_PREPASS if (alpha < scene_data.opaque_prepass_threshold) { @@ -1694,6 +1698,8 @@ void main() { } #endif // USE_OPAQUE_PREPASS +#endif // MODE_RENDER_DEPTH +#endif // !ALPHA_SCISSOR_USED #endif // USE_SHADOW_TO_OPACITY diff --git a/servers/rendering/renderer_rd/storage_rd/texture_storage.cpp b/servers/rendering/renderer_rd/storage_rd/texture_storage.cpp index 3c4e792b37..d84f6e6850 100644 --- a/servers/rendering/renderer_rd/storage_rd/texture_storage.cpp +++ b/servers/rendering/renderer_rd/storage_rd/texture_storage.cpp @@ -1395,6 +1395,13 @@ String TextureStorage::texture_get_path(RID p_texture) const { return tex->path; } +Image::Format TextureStorage::texture_get_format(RID p_texture) const { + Texture *tex = texture_owner.get_or_null(p_texture); + ERR_FAIL_COND_V(!tex, Image::FORMAT_MAX); + + return tex->format; +} + void TextureStorage::texture_set_detect_3d_callback(RID p_texture, RS::TextureDetectCallback p_callback, void *p_userdata) { Texture *tex = texture_owner.get_or_null(p_texture); ERR_FAIL_COND(!tex); @@ -1429,6 +1436,79 @@ Size2 TextureStorage::texture_size_with_proxy(RID p_proxy) { return texture_2d_get_size(p_proxy); } +void TextureStorage::texture_rd_initialize(RID p_texture, const RID &p_rd_texture, const RS::TextureLayeredType p_layer_type) { + ERR_FAIL_COND(!RD::get_singleton()->texture_is_valid(p_rd_texture)); + + // TODO : investigate if we can support this, will need to be able to obtain the order and obtain the slice info + ERR_FAIL_COND_MSG(RD::get_singleton()->texture_is_shared(p_rd_texture), "Please create the texture object using the original texture"); + + RD::TextureFormat tf = RD::get_singleton()->texture_get_format(p_rd_texture); + ERR_FAIL_COND(!(tf.usage_bits & RD::TEXTURE_USAGE_SAMPLING_BIT)); + + TextureFromRDFormat imfmt; + _texture_format_from_rd(tf.format, imfmt); + ERR_FAIL_COND(imfmt.image_format == Image::FORMAT_MAX); + + Texture texture; + + switch (tf.texture_type) { + case RD::TEXTURE_TYPE_2D: { + ERR_FAIL_COND(tf.array_layers != 1); + texture.type = TextureStorage::TYPE_2D; + } break; + case RD::TEXTURE_TYPE_2D_ARRAY: { + // RenderingDevice doesn't distinguish between Array textures and Cube textures + // this condition covers TextureArrays, TextureCube, and TextureCubeArray. + ERR_FAIL_COND(tf.array_layers == 1); + texture.type = TextureStorage::TYPE_LAYERED; + texture.layered_type = p_layer_type; + } break; + case RD::TEXTURE_TYPE_3D: { + ERR_FAIL_COND(tf.array_layers != 1); + texture.type = TextureStorage::TYPE_3D; + } break; + default: { + ERR_FAIL_MSG("This RD texture can't be used as a render texture"); + } break; + } + + texture.width = tf.width; + texture.height = tf.height; + texture.depth = tf.depth; + texture.layers = tf.array_layers; + texture.mipmaps = tf.mipmaps; + texture.format = imfmt.image_format; + texture.validated_format = texture.format; // ?? + + RD::TextureView rd_view; + rd_view.format_override = imfmt.rd_format == tf.format ? RD::DATA_FORMAT_MAX : imfmt.rd_format; + rd_view.swizzle_r = imfmt.swizzle_r; + rd_view.swizzle_g = imfmt.swizzle_g; + rd_view.swizzle_b = imfmt.swizzle_b; + rd_view.swizzle_a = imfmt.swizzle_a; + + texture.rd_type = tf.texture_type; + texture.rd_view = rd_view; + texture.rd_format = imfmt.rd_format; + // We create a shared texture here even if our view matches, so we don't obtain ownership. + texture.rd_texture = RD::get_singleton()->texture_create_shared(rd_view, p_rd_texture); + if (imfmt.rd_format_srgb != RD::DATA_FORMAT_MAX) { + rd_view.format_override = imfmt.rd_format_srgb == tf.format ? RD::DATA_FORMAT_MAX : imfmt.rd_format; + texture.rd_format_srgb = imfmt.rd_format_srgb; + // We create a shared texture here even if our view matches, so we don't obtain ownership. + texture.rd_texture_srgb = RD::get_singleton()->texture_create_shared(rd_view, p_rd_texture); + } + + // TODO figure out what to do with slices + + texture.width_2d = texture.width; + texture.height_2d = texture.height; + texture.is_render_target = false; + texture.is_proxy = false; + + texture_owner.initialize_rid(p_texture, texture); +} + RID TextureStorage::texture_get_rd_texture(RID p_texture, bool p_srgb) const { if (p_texture.is_null()) { return RID(); @@ -1921,6 +2001,362 @@ Ref<Image> TextureStorage::_validate_texture_format(const Ref<Image> &p_image, T return image; } +void TextureStorage::_texture_format_from_rd(RD::DataFormat p_rd_format, TextureFromRDFormat &r_format) { + switch (p_rd_format) { + case RD::DATA_FORMAT_R8_UNORM: { + r_format.image_format = Image::FORMAT_L8; + r_format.rd_format = RD::DATA_FORMAT_R8_UNORM; + r_format.swizzle_r = RD::TEXTURE_SWIZZLE_R; + r_format.swizzle_g = RD::TEXTURE_SWIZZLE_R; + r_format.swizzle_b = RD::TEXTURE_SWIZZLE_R; + r_format.swizzle_a = RD::TEXTURE_SWIZZLE_ONE; + } break; //luminance + case RD::DATA_FORMAT_R8G8_UNORM: { + r_format.image_format = Image::FORMAT_LA8; + r_format.rd_format = RD::DATA_FORMAT_R8G8_UNORM; + r_format.swizzle_r = RD::TEXTURE_SWIZZLE_R; + r_format.swizzle_g = RD::TEXTURE_SWIZZLE_R; + r_format.swizzle_b = RD::TEXTURE_SWIZZLE_R; + r_format.swizzle_a = RD::TEXTURE_SWIZZLE_G; + } break; //luminance-alpha + /* already maps to L8/LA8 + case RD::DATA_FORMAT_R8_UNORM: { + r_format.image_format = Image::FORMAT_R8; + r_format.rd_format = RD::DATA_FORMAT_R8_UNORM; + r_format.swizzle_r = RD::TEXTURE_SWIZZLE_R; + r_format.swizzle_g = RD::TEXTURE_SWIZZLE_ZERO; + r_format.swizzle_b = RD::TEXTURE_SWIZZLE_ZERO; + r_format.swizzle_a = RD::TEXTURE_SWIZZLE_ONE; + } break; + case RD::DATA_FORMAT_R8G8_UNORM: { + r_format.image_format = Image::FORMAT_RG8; + r_format.rd_format = RD::DATA_FORMAT_R8G8_UNORM; + r_format.swizzle_r = RD::TEXTURE_SWIZZLE_R; + r_format.swizzle_g = RD::TEXTURE_SWIZZLE_G; + r_format.swizzle_b = RD::TEXTURE_SWIZZLE_ZERO; + r_format.swizzle_a = RD::TEXTURE_SWIZZLE_ONE; + } break; + */ + case RD::DATA_FORMAT_R8G8B8_UNORM: + case RD::DATA_FORMAT_R8G8B8_SRGB: { + r_format.image_format = Image::FORMAT_RGB8; + r_format.rd_format = RD::DATA_FORMAT_R8G8B8_UNORM; + r_format.rd_format_srgb = RD::DATA_FORMAT_R8G8B8_SRGB; + r_format.swizzle_r = RD::TEXTURE_SWIZZLE_R; + r_format.swizzle_g = RD::TEXTURE_SWIZZLE_G; + r_format.swizzle_b = RD::TEXTURE_SWIZZLE_B; + r_format.swizzle_a = RD::TEXTURE_SWIZZLE_ONE; + + } break; + case RD::DATA_FORMAT_R8G8B8A8_UNORM: + case RD::DATA_FORMAT_R8G8B8A8_SRGB: { + r_format.image_format = Image::FORMAT_RGBA8; + r_format.rd_format = RD::DATA_FORMAT_R8G8B8A8_UNORM; + r_format.rd_format_srgb = RD::DATA_FORMAT_R8G8B8A8_SRGB; + r_format.swizzle_r = RD::TEXTURE_SWIZZLE_R; + r_format.swizzle_g = RD::TEXTURE_SWIZZLE_G; + r_format.swizzle_b = RD::TEXTURE_SWIZZLE_B; + r_format.swizzle_a = RD::TEXTURE_SWIZZLE_A; + } break; + case RD::DATA_FORMAT_B4G4R4A4_UNORM_PACK16: { + r_format.image_format = Image::FORMAT_RGBA4444; + r_format.rd_format = RD::DATA_FORMAT_B4G4R4A4_UNORM_PACK16; + r_format.swizzle_r = RD::TEXTURE_SWIZZLE_B; //needs swizzle + r_format.swizzle_g = RD::TEXTURE_SWIZZLE_G; + r_format.swizzle_b = RD::TEXTURE_SWIZZLE_R; + r_format.swizzle_a = RD::TEXTURE_SWIZZLE_A; + } break; + case RD::DATA_FORMAT_B5G6R5_UNORM_PACK16: { + r_format.image_format = Image::FORMAT_RGB565; + r_format.rd_format = RD::DATA_FORMAT_B5G6R5_UNORM_PACK16; + r_format.swizzle_r = RD::TEXTURE_SWIZZLE_B; + r_format.swizzle_g = RD::TEXTURE_SWIZZLE_G; + r_format.swizzle_b = RD::TEXTURE_SWIZZLE_R; + r_format.swizzle_a = RD::TEXTURE_SWIZZLE_A; + } break; + case RD::DATA_FORMAT_R32_SFLOAT: { + r_format.image_format = Image::FORMAT_RF; + r_format.rd_format = RD::DATA_FORMAT_R32_SFLOAT; + r_format.swizzle_r = RD::TEXTURE_SWIZZLE_R; + r_format.swizzle_g = RD::TEXTURE_SWIZZLE_ZERO; + r_format.swizzle_b = RD::TEXTURE_SWIZZLE_ZERO; + r_format.swizzle_a = RD::TEXTURE_SWIZZLE_ONE; + } break; //float + case RD::DATA_FORMAT_R32G32_SFLOAT: { + r_format.image_format = Image::FORMAT_RGF; + r_format.rd_format = RD::DATA_FORMAT_R32G32_SFLOAT; + r_format.swizzle_r = RD::TEXTURE_SWIZZLE_R; + r_format.swizzle_g = RD::TEXTURE_SWIZZLE_G; + r_format.swizzle_b = RD::TEXTURE_SWIZZLE_ZERO; + r_format.swizzle_a = RD::TEXTURE_SWIZZLE_ONE; + } break; + case RD::DATA_FORMAT_R32G32B32_SFLOAT: { + r_format.image_format = Image::FORMAT_RGBF; + r_format.rd_format = RD::DATA_FORMAT_R32G32B32_SFLOAT; + r_format.swizzle_r = RD::TEXTURE_SWIZZLE_R; + r_format.swizzle_g = RD::TEXTURE_SWIZZLE_G; + r_format.swizzle_b = RD::TEXTURE_SWIZZLE_B; + r_format.swizzle_a = RD::TEXTURE_SWIZZLE_ONE; + } break; + case RD::DATA_FORMAT_R32G32B32A32_SFLOAT: { + r_format.image_format = Image::FORMAT_RGBF; + r_format.rd_format = RD::DATA_FORMAT_R32G32B32A32_SFLOAT; + r_format.swizzle_r = RD::TEXTURE_SWIZZLE_R; + r_format.swizzle_g = RD::TEXTURE_SWIZZLE_G; + r_format.swizzle_b = RD::TEXTURE_SWIZZLE_B; + r_format.swizzle_a = RD::TEXTURE_SWIZZLE_A; + + } break; + case RD::DATA_FORMAT_R16_SFLOAT: { + r_format.image_format = Image::FORMAT_RH; + r_format.rd_format = RD::DATA_FORMAT_R16_SFLOAT; + r_format.swizzle_r = RD::TEXTURE_SWIZZLE_R; + r_format.swizzle_g = RD::TEXTURE_SWIZZLE_ZERO; + r_format.swizzle_b = RD::TEXTURE_SWIZZLE_ZERO; + r_format.swizzle_a = RD::TEXTURE_SWIZZLE_ONE; + + } break; //half float + case RD::DATA_FORMAT_R16G16_SFLOAT: { + r_format.image_format = Image::FORMAT_RGH; + r_format.rd_format = RD::DATA_FORMAT_R16G16_SFLOAT; + r_format.swizzle_r = RD::TEXTURE_SWIZZLE_R; + r_format.swizzle_g = RD::TEXTURE_SWIZZLE_G; + r_format.swizzle_b = RD::TEXTURE_SWIZZLE_ZERO; + r_format.swizzle_a = RD::TEXTURE_SWIZZLE_ONE; + + } break; + case RD::DATA_FORMAT_R16G16B16_SFLOAT: { + r_format.image_format = Image::FORMAT_RGBH; + r_format.rd_format = RD::DATA_FORMAT_R16G16B16_SFLOAT; + r_format.swizzle_r = RD::TEXTURE_SWIZZLE_R; + r_format.swizzle_g = RD::TEXTURE_SWIZZLE_G; + r_format.swizzle_b = RD::TEXTURE_SWIZZLE_B; + r_format.swizzle_a = RD::TEXTURE_SWIZZLE_ONE; + } break; + case RD::DATA_FORMAT_R16G16B16A16_SFLOAT: { + r_format.image_format = Image::FORMAT_RGBAH; + r_format.rd_format = RD::DATA_FORMAT_R16G16B16A16_SFLOAT; + r_format.swizzle_r = RD::TEXTURE_SWIZZLE_R; + r_format.swizzle_g = RD::TEXTURE_SWIZZLE_G; + r_format.swizzle_b = RD::TEXTURE_SWIZZLE_B; + r_format.swizzle_a = RD::TEXTURE_SWIZZLE_A; + + } break; + case RD::DATA_FORMAT_E5B9G9R9_UFLOAT_PACK32: { + r_format.image_format = Image::FORMAT_RGBE9995; + r_format.rd_format = RD::DATA_FORMAT_E5B9G9R9_UFLOAT_PACK32; + // TODO: Need to make a function in Image to swap bits for this. + r_format.swizzle_r = RD::TEXTURE_SWIZZLE_IDENTITY; + r_format.swizzle_g = RD::TEXTURE_SWIZZLE_IDENTITY; + r_format.swizzle_b = RD::TEXTURE_SWIZZLE_IDENTITY; + r_format.swizzle_a = RD::TEXTURE_SWIZZLE_IDENTITY; + } break; + case RD::DATA_FORMAT_BC1_RGB_UNORM_BLOCK: + case RD::DATA_FORMAT_BC1_RGB_SRGB_BLOCK: { + r_format.image_format = Image::FORMAT_DXT1; + r_format.rd_format = RD::DATA_FORMAT_BC1_RGB_UNORM_BLOCK; + r_format.rd_format_srgb = RD::DATA_FORMAT_BC1_RGB_SRGB_BLOCK; + r_format.swizzle_r = RD::TEXTURE_SWIZZLE_R; + r_format.swizzle_g = RD::TEXTURE_SWIZZLE_G; + r_format.swizzle_b = RD::TEXTURE_SWIZZLE_B; + r_format.swizzle_a = RD::TEXTURE_SWIZZLE_ONE; + + } break; //s3tc bc1 + case RD::DATA_FORMAT_BC2_UNORM_BLOCK: + case RD::DATA_FORMAT_BC2_SRGB_BLOCK: { + r_format.image_format = Image::FORMAT_DXT3; + r_format.rd_format = RD::DATA_FORMAT_BC2_UNORM_BLOCK; + r_format.rd_format_srgb = RD::DATA_FORMAT_BC2_SRGB_BLOCK; + r_format.swizzle_r = RD::TEXTURE_SWIZZLE_R; + r_format.swizzle_g = RD::TEXTURE_SWIZZLE_G; + r_format.swizzle_b = RD::TEXTURE_SWIZZLE_B; + r_format.swizzle_a = RD::TEXTURE_SWIZZLE_A; + + } break; //bc2 + case RD::DATA_FORMAT_BC3_UNORM_BLOCK: + case RD::DATA_FORMAT_BC3_SRGB_BLOCK: { + r_format.image_format = Image::FORMAT_DXT5; + r_format.rd_format = RD::DATA_FORMAT_BC3_UNORM_BLOCK; + r_format.rd_format_srgb = RD::DATA_FORMAT_BC3_SRGB_BLOCK; + r_format.swizzle_r = RD::TEXTURE_SWIZZLE_R; + r_format.swizzle_g = RD::TEXTURE_SWIZZLE_G; + r_format.swizzle_b = RD::TEXTURE_SWIZZLE_B; + r_format.swizzle_a = RD::TEXTURE_SWIZZLE_A; + } break; //bc3 + case RD::DATA_FORMAT_BC4_UNORM_BLOCK: { + r_format.image_format = Image::FORMAT_RGTC_R; + r_format.rd_format = RD::DATA_FORMAT_BC4_UNORM_BLOCK; + r_format.swizzle_r = RD::TEXTURE_SWIZZLE_R; + r_format.swizzle_g = RD::TEXTURE_SWIZZLE_ZERO; + r_format.swizzle_b = RD::TEXTURE_SWIZZLE_ZERO; + r_format.swizzle_a = RD::TEXTURE_SWIZZLE_ONE; + + } break; + case RD::DATA_FORMAT_BC5_UNORM_BLOCK: { + r_format.image_format = Image::FORMAT_RGTC_RG; + r_format.rd_format = RD::DATA_FORMAT_BC5_UNORM_BLOCK; + r_format.swizzle_r = RD::TEXTURE_SWIZZLE_R; + r_format.swizzle_g = RD::TEXTURE_SWIZZLE_G; + r_format.swizzle_b = RD::TEXTURE_SWIZZLE_ZERO; + r_format.swizzle_a = RD::TEXTURE_SWIZZLE_ONE; + + } break; + case RD::DATA_FORMAT_BC7_UNORM_BLOCK: + case RD::DATA_FORMAT_BC7_SRGB_BLOCK: { + r_format.image_format = Image::FORMAT_BPTC_RGBA; + r_format.rd_format = RD::DATA_FORMAT_BC7_UNORM_BLOCK; + r_format.rd_format_srgb = RD::DATA_FORMAT_BC7_SRGB_BLOCK; + r_format.swizzle_r = RD::TEXTURE_SWIZZLE_R; + r_format.swizzle_g = RD::TEXTURE_SWIZZLE_G; + r_format.swizzle_b = RD::TEXTURE_SWIZZLE_B; + r_format.swizzle_a = RD::TEXTURE_SWIZZLE_A; + + } break; //btpc bc7 + case RD::DATA_FORMAT_BC6H_SFLOAT_BLOCK: { + r_format.image_format = Image::FORMAT_BPTC_RGBF; + r_format.rd_format = RD::DATA_FORMAT_BC6H_SFLOAT_BLOCK; + r_format.swizzle_r = RD::TEXTURE_SWIZZLE_R; + r_format.swizzle_g = RD::TEXTURE_SWIZZLE_G; + r_format.swizzle_b = RD::TEXTURE_SWIZZLE_B; + r_format.swizzle_a = RD::TEXTURE_SWIZZLE_ONE; + } break; //float bc6h + case RD::DATA_FORMAT_BC6H_UFLOAT_BLOCK: { + r_format.image_format = Image::FORMAT_BPTC_RGBFU; + r_format.rd_format = RD::DATA_FORMAT_BC6H_UFLOAT_BLOCK; + r_format.swizzle_r = RD::TEXTURE_SWIZZLE_R; + r_format.swizzle_g = RD::TEXTURE_SWIZZLE_G; + r_format.swizzle_b = RD::TEXTURE_SWIZZLE_B; + r_format.swizzle_a = RD::TEXTURE_SWIZZLE_ONE; + } break; //unsigned float bc6hu + case RD::DATA_FORMAT_EAC_R11_UNORM_BLOCK: { + r_format.image_format = Image::FORMAT_ETC2_R11; + r_format.rd_format = RD::DATA_FORMAT_EAC_R11_UNORM_BLOCK; + r_format.swizzle_r = RD::TEXTURE_SWIZZLE_R; + r_format.swizzle_g = RD::TEXTURE_SWIZZLE_ZERO; + r_format.swizzle_b = RD::TEXTURE_SWIZZLE_ZERO; + r_format.swizzle_a = RD::TEXTURE_SWIZZLE_ONE; + + } break; //etc2 + case RD::DATA_FORMAT_EAC_R11_SNORM_BLOCK: { + r_format.image_format = Image::FORMAT_ETC2_R11S; + r_format.rd_format = RD::DATA_FORMAT_EAC_R11_SNORM_BLOCK; + r_format.swizzle_r = RD::TEXTURE_SWIZZLE_R; + r_format.swizzle_g = RD::TEXTURE_SWIZZLE_ZERO; + r_format.swizzle_b = RD::TEXTURE_SWIZZLE_ZERO; + r_format.swizzle_a = RD::TEXTURE_SWIZZLE_ONE; + } break; //signed: {} break; NOT srgb. + case RD::DATA_FORMAT_EAC_R11G11_UNORM_BLOCK: { + r_format.image_format = Image::FORMAT_ETC2_RG11; + r_format.rd_format = RD::DATA_FORMAT_EAC_R11G11_UNORM_BLOCK; + r_format.swizzle_r = RD::TEXTURE_SWIZZLE_R; + r_format.swizzle_g = RD::TEXTURE_SWIZZLE_G; + r_format.swizzle_b = RD::TEXTURE_SWIZZLE_ZERO; + r_format.swizzle_a = RD::TEXTURE_SWIZZLE_ONE; + } break; + case RD::DATA_FORMAT_EAC_R11G11_SNORM_BLOCK: { + r_format.image_format = Image::FORMAT_ETC2_RG11S; + r_format.rd_format = RD::DATA_FORMAT_EAC_R11G11_SNORM_BLOCK; + r_format.swizzle_r = RD::TEXTURE_SWIZZLE_R; + r_format.swizzle_g = RD::TEXTURE_SWIZZLE_G; + r_format.swizzle_b = RD::TEXTURE_SWIZZLE_ZERO; + r_format.swizzle_a = RD::TEXTURE_SWIZZLE_ONE; + } break; + case RD::DATA_FORMAT_ETC2_R8G8B8_UNORM_BLOCK: + case RD::DATA_FORMAT_ETC2_R8G8B8_SRGB_BLOCK: { + r_format.image_format = Image::FORMAT_ETC2_RGB8; + r_format.rd_format = RD::DATA_FORMAT_ETC2_R8G8B8_UNORM_BLOCK; + r_format.rd_format_srgb = RD::DATA_FORMAT_ETC2_R8G8B8_SRGB_BLOCK; + r_format.swizzle_r = RD::TEXTURE_SWIZZLE_R; + r_format.swizzle_g = RD::TEXTURE_SWIZZLE_G; + r_format.swizzle_b = RD::TEXTURE_SWIZZLE_B; + r_format.swizzle_a = RD::TEXTURE_SWIZZLE_ONE; + + } break; + /* already maps to FORMAT_ETC2_RGBA8 + case RD::DATA_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK: + case RD::DATA_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK: { + r_format.image_format = Image::FORMAT_ETC2_RGBA8; + r_format.rd_format = RD::DATA_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK; + r_format.rd_format_srgb = RD::DATA_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK; + r_format.swizzle_r = RD::TEXTURE_SWIZZLE_R; + r_format.swizzle_g = RD::TEXTURE_SWIZZLE_G; + r_format.swizzle_b = RD::TEXTURE_SWIZZLE_B; + r_format.swizzle_a = RD::TEXTURE_SWIZZLE_A; + } break; + */ + case RD::DATA_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK: + case RD::DATA_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK: { + r_format.image_format = Image::FORMAT_ETC2_RGB8A1; + r_format.rd_format = RD::DATA_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK; + r_format.rd_format_srgb = RD::DATA_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK; + r_format.swizzle_r = RD::TEXTURE_SWIZZLE_R; + r_format.swizzle_g = RD::TEXTURE_SWIZZLE_G; + r_format.swizzle_b = RD::TEXTURE_SWIZZLE_B; + r_format.swizzle_a = RD::TEXTURE_SWIZZLE_A; + } break; + case RD::DATA_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK: + case RD::DATA_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK: { + r_format.image_format = Image::FORMAT_ETC2_RA_AS_RG; + r_format.rd_format = RD::DATA_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK; + r_format.rd_format_srgb = RD::DATA_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK; + r_format.swizzle_r = RD::TEXTURE_SWIZZLE_R; + r_format.swizzle_g = RD::TEXTURE_SWIZZLE_A; + r_format.swizzle_b = RD::TEXTURE_SWIZZLE_ZERO; + r_format.swizzle_a = RD::TEXTURE_SWIZZLE_ONE; + } break; + /* already maps to FORMAT_DXT5 + case RD::DATA_FORMAT_BC3_UNORM_BLOCK: + case RD::DATA_FORMAT_BC3_SRGB_BLOCK: { + r_format.image_format = Image::FORMAT_DXT5_RA_AS_RG; + r_format.rd_format = RD::DATA_FORMAT_BC3_UNORM_BLOCK; + r_format.rd_format_srgb = RD::DATA_FORMAT_BC3_SRGB_BLOCK; + r_format.swizzle_r = RD::TEXTURE_SWIZZLE_R; + r_format.swizzle_g = RD::TEXTURE_SWIZZLE_A; + r_format.swizzle_b = RD::TEXTURE_SWIZZLE_ZERO; + r_format.swizzle_a = RD::TEXTURE_SWIZZLE_ONE; + } break; + */ + case RD::DATA_FORMAT_ASTC_4x4_UNORM_BLOCK: { + // Q: Do we do as we do below, just create the sRGB variant? + r_format.image_format = Image::FORMAT_ASTC_4x4; + r_format.rd_format = RD::DATA_FORMAT_ASTC_4x4_UNORM_BLOCK; + r_format.swizzle_r = RD::TEXTURE_SWIZZLE_R; + r_format.swizzle_g = RD::TEXTURE_SWIZZLE_G; + r_format.swizzle_b = RD::TEXTURE_SWIZZLE_B; + r_format.swizzle_a = RD::TEXTURE_SWIZZLE_A; + } break; + case RD::DATA_FORMAT_ASTC_4x4_SRGB_BLOCK: { + r_format.image_format = Image::FORMAT_ASTC_4x4_HDR; + r_format.rd_format = RD::DATA_FORMAT_ASTC_4x4_UNORM_BLOCK; + r_format.rd_format_srgb = RD::DATA_FORMAT_ASTC_4x4_SRGB_BLOCK; + r_format.swizzle_r = RD::TEXTURE_SWIZZLE_R; + r_format.swizzle_g = RD::TEXTURE_SWIZZLE_G; + r_format.swizzle_b = RD::TEXTURE_SWIZZLE_B; + r_format.swizzle_a = RD::TEXTURE_SWIZZLE_A; + + } break; // astc 4x4 + case RD::DATA_FORMAT_ASTC_8x8_UNORM_BLOCK: { + // Q: Do we do as we do below, just create the sRGB variant? + r_format.image_format = Image::FORMAT_ASTC_8x8; + r_format.rd_format = RD::DATA_FORMAT_ASTC_8x8_UNORM_BLOCK; + } break; + case RD::DATA_FORMAT_ASTC_8x8_SRGB_BLOCK: { + r_format.image_format = Image::FORMAT_ASTC_8x8_HDR; + r_format.rd_format = RD::DATA_FORMAT_ASTC_8x8_UNORM_BLOCK; + r_format.rd_format_srgb = RD::DATA_FORMAT_ASTC_8x8_SRGB_BLOCK; + r_format.swizzle_r = RD::TEXTURE_SWIZZLE_R; + r_format.swizzle_g = RD::TEXTURE_SWIZZLE_G; + r_format.swizzle_b = RD::TEXTURE_SWIZZLE_B; + r_format.swizzle_a = RD::TEXTURE_SWIZZLE_A; + + } break; // astc 8x8 + + default: { + ERR_FAIL_MSG("Unsupported image format"); + } + } +} + /* DECAL API */ RID TextureStorage::decal_atlas_get_texture() const { diff --git a/servers/rendering/renderer_rd/storage_rd/texture_storage.h b/servers/rendering/renderer_rd/storage_rd/texture_storage.h index 8f021f3179..6df6faa40a 100644 --- a/servers/rendering/renderer_rd/storage_rd/texture_storage.h +++ b/servers/rendering/renderer_rd/storage_rd/texture_storage.h @@ -196,6 +196,27 @@ private: Ref<Image> _validate_texture_format(const Ref<Image> &p_image, TextureToRDFormat &r_format); void _texture_2d_update(RID p_texture, const Ref<Image> &p_image, int p_layer = 0, bool p_immediate = false); + struct TextureFromRDFormat { + Image::Format image_format; + RD::DataFormat rd_format; + RD::DataFormat rd_format_srgb; + RD::TextureSwizzle swizzle_r; + RD::TextureSwizzle swizzle_g; + RD::TextureSwizzle swizzle_b; + RD::TextureSwizzle swizzle_a; + TextureFromRDFormat() { + image_format = Image::FORMAT_MAX; + rd_format = RD::DATA_FORMAT_MAX; + rd_format_srgb = RD::DATA_FORMAT_MAX; + swizzle_r = RD::TEXTURE_SWIZZLE_R; + swizzle_g = RD::TEXTURE_SWIZZLE_G; + swizzle_b = RD::TEXTURE_SWIZZLE_B; + swizzle_a = RD::TEXTURE_SWIZZLE_A; + } + }; + + void _texture_format_from_rd(RD::DataFormat p_rd_format, TextureFromRDFormat &r_format); + /* DECAL API */ struct DecalAtlas { @@ -488,6 +509,8 @@ public: virtual void texture_set_path(RID p_texture, const String &p_path) override; virtual String texture_get_path(RID p_texture) const override; + virtual Image::Format texture_get_format(RID p_texture) const override; + virtual void texture_set_detect_3d_callback(RID p_texture, RS::TextureDetectCallback p_callback, void *p_userdata) override; virtual void texture_set_detect_normal_callback(RID p_texture, RS::TextureDetectCallback p_callback, void *p_userdata) override; virtual void texture_set_detect_roughness_callback(RID p_texture, RS::TextureDetectRoughnessCallback p_callback, void *p_userdata) override; @@ -498,6 +521,7 @@ public: virtual Size2 texture_size_with_proxy(RID p_proxy) override; + virtual void texture_rd_initialize(RID p_texture, const RID &p_rd_texture, const RS::TextureLayeredType p_layer_type = RS::TEXTURE_LAYERED_2D_ARRAY) override; virtual RID texture_get_rd_texture(RID p_texture, bool p_srgb = false) const override; virtual uint64_t texture_get_native_handle(RID p_texture, bool p_srgb = false) const override; diff --git a/servers/rendering/renderer_scene_cull.cpp b/servers/rendering/renderer_scene_cull.cpp index c5eabba326..44b15ee91d 100644 --- a/servers/rendering/renderer_scene_cull.cpp +++ b/servers/rendering/renderer_scene_cull.cpp @@ -3994,11 +3994,12 @@ void RendererSceneCull::_update_dirty_instance(Instance *p_instance) { } void RendererSceneCull::update_dirty_instances() { - RSG::utilities->update_dirty_resources(); - while (_instance_update_list.first()) { _update_dirty_instance(_instance_update_list.first()->self()); } + + // Update dirty resources after dirty instances as instance updates may affect resources. + RSG::utilities->update_dirty_resources(); } void RendererSceneCull::update() { diff --git a/servers/rendering/rendering_device.cpp b/servers/rendering/rendering_device.cpp index d87c09a857..7bfa23bfa5 100644 --- a/servers/rendering/rendering_device.cpp +++ b/servers/rendering/rendering_device.cpp @@ -114,6 +114,14 @@ RID RenderingDevice::_texture_create_shared_from_slice(const Ref<RDTextureView> return texture_create_shared_from_slice(p_view->base, p_with_texture, p_layer, p_mipmap, p_mipmaps, p_slice_type); } +Ref<RDTextureFormat> RenderingDevice::_texture_get_format(RID p_rd_texture) { + Ref<RDTextureFormat> rtf; + rtf.instantiate(); + rtf->base = texture_get_format(p_rd_texture); + + return rtf; +} + RenderingDevice::FramebufferFormatID RenderingDevice::_framebuffer_format_create(const TypedArray<RDAttachmentFormat> &p_attachments, uint32_t p_view_count) { Vector<AttachmentFormat> attachments; attachments.resize(p_attachments.size()); @@ -720,6 +728,7 @@ void RenderingDevice::_bind_methods() { ClassDB::bind_method(D_METHOD("texture_clear", "texture", "color", "base_mipmap", "mipmap_count", "base_layer", "layer_count", "post_barrier"), &RenderingDevice::texture_clear, DEFVAL(BARRIER_MASK_ALL_BARRIERS)); ClassDB::bind_method(D_METHOD("texture_resolve_multisample", "from_texture", "to_texture", "post_barrier"), &RenderingDevice::texture_resolve_multisample, DEFVAL(BARRIER_MASK_ALL_BARRIERS)); + ClassDB::bind_method(D_METHOD("texture_get_format", "texture"), &RenderingDevice::_texture_get_format); ClassDB::bind_method(D_METHOD("texture_get_native_handle", "texture"), &RenderingDevice::texture_get_native_handle); ClassDB::bind_method(D_METHOD("framebuffer_format_create", "attachments", "view_count"), &RenderingDevice::_framebuffer_format_create, DEFVAL(1)); @@ -1073,9 +1082,11 @@ void RenderingDevice::_bind_methods() { BIND_ENUM_CONSTANT(DATA_FORMAT_G16_B16_R16_3PLANE_444_UNORM); BIND_ENUM_CONSTANT(DATA_FORMAT_MAX); - BIND_BITFIELD_FLAG(BARRIER_MASK_RASTER); + BIND_BITFIELD_FLAG(BARRIER_MASK_VERTEX); + BIND_BITFIELD_FLAG(BARRIER_MASK_FRAGMENT); BIND_BITFIELD_FLAG(BARRIER_MASK_COMPUTE); BIND_BITFIELD_FLAG(BARRIER_MASK_TRANSFER); + BIND_BITFIELD_FLAG(BARRIER_MASK_RASTER); BIND_BITFIELD_FLAG(BARRIER_MASK_ALL_BARRIERS); BIND_BITFIELD_FLAG(BARRIER_MASK_NO_BARRIER); diff --git a/servers/rendering/rendering_device.h b/servers/rendering/rendering_device.h index dd2f8d55aa..5edeb109e2 100644 --- a/servers/rendering/rendering_device.h +++ b/servers/rendering/rendering_device.h @@ -393,11 +393,14 @@ public: /*****************/ enum BarrierMask { - BARRIER_MASK_RASTER = 1, - BARRIER_MASK_COMPUTE = 2, - BARRIER_MASK_TRANSFER = 4, + BARRIER_MASK_VERTEX = 1, + BARRIER_MASK_FRAGMENT = 2, + BARRIER_MASK_COMPUTE = 4, + BARRIER_MASK_TRANSFER = 8, + + BARRIER_MASK_RASTER = BARRIER_MASK_VERTEX | BARRIER_MASK_FRAGMENT, // 3, BARRIER_MASK_ALL_BARRIERS = BARRIER_MASK_RASTER | BARRIER_MASK_COMPUTE | BARRIER_MASK_TRANSFER, // 7 - BARRIER_MASK_NO_BARRIER = 8, + BARRIER_MASK_NO_BARRIER = 16, }; /*****************/ @@ -538,6 +541,7 @@ public: virtual bool texture_is_format_supported_for_usage(DataFormat p_format, BitField<RenderingDevice::TextureUsageBits> p_usage) const = 0; virtual bool texture_is_shared(RID p_texture) = 0; virtual bool texture_is_valid(RID p_texture) = 0; + virtual TextureFormat texture_get_format(RID p_texture) = 0; virtual Size2i texture_size(RID p_texture) = 0; virtual uint64_t texture_get_native_handle(RID p_texture) = 0; @@ -1313,6 +1317,7 @@ protected: RID _texture_create(const Ref<RDTextureFormat> &p_format, const Ref<RDTextureView> &p_view, const TypedArray<PackedByteArray> &p_data = Array()); RID _texture_create_shared(const Ref<RDTextureView> &p_view, RID p_with_texture); RID _texture_create_shared_from_slice(const Ref<RDTextureView> &p_view, RID p_with_texture, uint32_t p_layer, uint32_t p_mipmap, uint32_t p_mipmaps = 1, TextureSliceType p_slice_type = TEXTURE_SLICE_2D); + Ref<RDTextureFormat> _texture_get_format(RID p_rd_texture); FramebufferFormatID _framebuffer_format_create(const TypedArray<RDAttachmentFormat> &p_attachments, uint32_t p_view_count); FramebufferFormatID _framebuffer_format_create_multipass(const TypedArray<RDAttachmentFormat> &p_attachments, const TypedArray<RDFramebufferPass> &p_passes, uint32_t p_view_count); diff --git a/servers/rendering/rendering_server_default.cpp b/servers/rendering/rendering_server_default.cpp index 65bdb94c9f..ff6bf3a6ba 100644 --- a/servers/rendering/rendering_server_default.cpp +++ b/servers/rendering/rendering_server_default.cpp @@ -258,22 +258,10 @@ uint64_t RenderingServerDefault::get_rendering_info(RenderingInfo p_info) { return RSG::utilities->get_rendering_info(p_info); } -String RenderingServerDefault::get_video_adapter_name() const { - return RSG::utilities->get_video_adapter_name(); -} - -String RenderingServerDefault::get_video_adapter_vendor() const { - return RSG::utilities->get_video_adapter_vendor(); -} - RenderingDevice::DeviceType RenderingServerDefault::get_video_adapter_type() const { return RSG::utilities->get_video_adapter_type(); } -String RenderingServerDefault::get_video_adapter_api_version() const { - return RSG::utilities->get_video_adapter_api_version(); -} - void RenderingServerDefault::set_frame_profiling_enabled(bool p_enable) { RSG::utilities->capturing_timestamps = p_enable; } diff --git a/servers/rendering/rendering_server_default.h b/servers/rendering/rendering_server_default.h index 797fe73ba0..c79d3a64cf 100644 --- a/servers/rendering/rendering_server_default.h +++ b/servers/rendering/rendering_server_default.h @@ -210,9 +210,13 @@ public: FUNC2(texture_set_path, RID, const String &) FUNC1RC(String, texture_get_path, RID) + + FUNC1RC(Image::Format, texture_get_format, RID) + FUNC1(texture_debug_usage, List<TextureInfo> *) FUNC2(texture_set_force_redraw_if_visible, RID, bool) + FUNCRIDTEX2(texture_rd, const RID &, const RS::TextureLayeredType) FUNC2RC(RID, texture_get_rd_texture, RID, bool) FUNC2RC(uint64_t, texture_get_native_handle, RID, bool) @@ -946,9 +950,26 @@ public: #undef server_name #undef ServerName + /* STATUS INFORMATION */ +#define ServerName RendererUtilities +#define server_name RSG::utilities + FUNC0RC(String, get_video_adapter_name) + FUNC0RC(String, get_video_adapter_vendor) + FUNC0RC(String, get_video_adapter_api_version) +#undef server_name +#undef ServerName #undef WRITE_ACTION #undef SYNC_DEBUG + virtual uint64_t get_rendering_info(RenderingInfo p_info) override; + virtual RenderingDevice::DeviceType get_video_adapter_type() const override; + + virtual void set_frame_profiling_enabled(bool p_enable) override; + virtual Vector<FrameProfileArea> get_frame_profile() override; + virtual uint64_t get_frame_profile_frame() override; + + virtual RID get_test_cube() override; + /* FREE */ virtual void free(RID p_rid) override { @@ -970,20 +991,6 @@ public: virtual void init() override; virtual void finish() override; - /* STATUS INFORMATION */ - - virtual uint64_t get_rendering_info(RenderingInfo p_info) override; - virtual String get_video_adapter_name() const override; - virtual String get_video_adapter_vendor() const override; - virtual RenderingDevice::DeviceType get_video_adapter_type() const override; - virtual String get_video_adapter_api_version() const override; - - virtual void set_frame_profiling_enabled(bool p_enable) override; - virtual Vector<FrameProfileArea> get_frame_profile() override; - virtual uint64_t get_frame_profile_frame() override; - - virtual RID get_test_cube() override; - /* TESTING */ virtual double get_frame_setup_time_cpu() const override; diff --git a/servers/rendering/shader_language.cpp b/servers/rendering/shader_language.cpp index ff4f670c58..e9421a435e 100644 --- a/servers/rendering/shader_language.cpp +++ b/servers/rendering/shader_language.cpp @@ -3388,12 +3388,14 @@ bool ShaderLanguage::_validate_function_call(BlockNode *p_block, const FunctionI } int last_arg_count = 0; + bool exists = false; String arg_list = ""; for (int i = 0; i < shader->functions.size(); i++) { if (name != shader->functions[i].name) { continue; } + exists = true; if (!shader->functions[i].callable) { _set_error(vformat(RTR("Function '%s' can't be called from source code."), String(name))); @@ -3494,10 +3496,12 @@ bool ShaderLanguage::_validate_function_call(BlockNode *p_block, const FunctionI } } - if (last_arg_count > args.size()) { - _set_error(vformat(RTR("Too few arguments for \"%s(%s)\" call. Expected at least %d but received %d."), String(name), arg_list, last_arg_count, args.size())); - } else if (last_arg_count < args.size()) { - _set_error(vformat(RTR("Too many arguments for \"%s(%s)\" call. Expected at most %d but received %d."), String(name), arg_list, last_arg_count, args.size())); + if (exists) { + if (last_arg_count > args.size()) { + _set_error(vformat(RTR("Too few arguments for \"%s(%s)\" call. Expected at least %d but received %d."), String(name), arg_list, last_arg_count, args.size())); + } else if (last_arg_count < args.size()) { + _set_error(vformat(RTR("Too many arguments for \"%s(%s)\" call. Expected at most %d but received %d."), String(name), arg_list, last_arg_count, args.size())); + } } return false; @@ -10549,19 +10553,23 @@ Error ShaderLanguage::complete(const String &p_code, const ShaderCompileInfo &p_ } } else if ((int(completion_base) > int(TYPE_MAT4) && int(completion_base) < int(TYPE_STRUCT))) { Vector<String> options; + if (current_uniform_filter == FILTER_DEFAULT) { + options.push_back("filter_linear"); + options.push_back("filter_linear_mipmap"); + options.push_back("filter_linear_mipmap_anisotropic"); + options.push_back("filter_nearest"); + options.push_back("filter_nearest_mipmap"); + options.push_back("filter_nearest_mipmap_anisotropic"); + } + if (current_uniform_repeat == REPEAT_DEFAULT) { + options.push_back("repeat_enable"); + options.push_back("repeat_disable"); + } if (completion_base_array) { if (current_uniform_hint == ShaderNode::Uniform::HINT_NONE) { options.push_back("source_color"); } } else { - if (current_uniform_filter == FILTER_DEFAULT) { - options.push_back("filter_linear"); - options.push_back("filter_linear_mipmap"); - options.push_back("filter_linear_mipmap_anisotropic"); - options.push_back("filter_nearest"); - options.push_back("filter_nearest_mipmap"); - options.push_back("filter_nearest_mipmap_anisotropic"); - } if (current_uniform_hint == ShaderNode::Uniform::HINT_NONE) { options.push_back("hint_anisotropy"); options.push_back("hint_default_black"); @@ -10579,10 +10587,6 @@ Error ShaderLanguage::complete(const String &p_code, const ShaderCompileInfo &p_ options.push_back("hint_depth_texture"); options.push_back("source_color"); } - if (current_uniform_repeat == REPEAT_DEFAULT) { - options.push_back("repeat_enable"); - options.push_back("repeat_disable"); - } } for (int i = 0; i < options.size(); i++) { diff --git a/servers/rendering/storage/texture_storage.h b/servers/rendering/storage/texture_storage.h index 93b32bd372..c3a257595c 100644 --- a/servers/rendering/storage/texture_storage.h +++ b/servers/rendering/storage/texture_storage.h @@ -90,6 +90,8 @@ public: virtual void texture_set_path(RID p_texture, const String &p_path) = 0; virtual String texture_get_path(RID p_texture) const = 0; + virtual Image::Format texture_get_format(RID p_texture) const = 0; + virtual void texture_set_detect_3d_callback(RID p_texture, RS::TextureDetectCallback p_callback, void *p_userdata) = 0; virtual void texture_set_detect_normal_callback(RID p_texture, RS::TextureDetectCallback p_callback, void *p_userdata) = 0; virtual void texture_set_detect_roughness_callback(RID p_texture, RS::TextureDetectRoughnessCallback p_callback, void *p_userdata) = 0; @@ -100,6 +102,7 @@ public: virtual Size2 texture_size_with_proxy(RID p_proxy) = 0; + virtual void texture_rd_initialize(RID p_texture, const RID &p_rd_texture, const RS::TextureLayeredType p_layer_type = RS::TEXTURE_LAYERED_2D_ARRAY) = 0; virtual RID texture_get_rd_texture(RID p_texture, bool p_srgb = false) const = 0; virtual uint64_t texture_get_native_handle(RID p_texture, bool p_srgb = false) const = 0; diff --git a/servers/rendering_server.cpp b/servers/rendering_server.cpp index 4c6b765157..4a95dc1963 100644 --- a/servers/rendering_server.cpp +++ b/servers/rendering_server.cpp @@ -1697,7 +1697,10 @@ void RenderingServer::_bind_methods() { ClassDB::bind_method(D_METHOD("texture_set_path", "texture", "path"), &RenderingServer::texture_set_path); ClassDB::bind_method(D_METHOD("texture_get_path", "texture"), &RenderingServer::texture_get_path); + ClassDB::bind_method(D_METHOD("texture_get_format", "texture"), &RenderingServer::texture_get_format); + ClassDB::bind_method(D_METHOD("texture_set_force_redraw_if_visible", "texture", "enable"), &RenderingServer::texture_set_force_redraw_if_visible); + ClassDB::bind_method(D_METHOD("texture_rd_create", "rd_texture", "layer_type"), &RenderingServer::texture_rd_create, DEFVAL(RenderingServer::TEXTURE_LAYERED_2D_ARRAY)); ClassDB::bind_method(D_METHOD("texture_get_rd_texture", "texture", "srgb"), &RenderingServer::texture_get_rd_texture, DEFVAL(false)); ClassDB::bind_method(D_METHOD("texture_get_native_handle", "texture", "srgb"), &RenderingServer::texture_get_native_handle, DEFVAL(false)); diff --git a/servers/rendering_server.h b/servers/rendering_server.h index deac2a59f9..9ea55c31b4 100644 --- a/servers/rendering_server.h +++ b/servers/rendering_server.h @@ -127,6 +127,8 @@ public: virtual void texture_set_path(RID p_texture, const String &p_path) = 0; virtual String texture_get_path(RID p_texture) const = 0; + virtual Image::Format texture_get_format(RID p_texture) const = 0; + typedef void (*TextureDetectCallback)(void *); virtual void texture_set_detect_3d_callback(RID p_texture, TextureDetectCallback p_callback, void *p_userdata) = 0; @@ -158,6 +160,7 @@ public: virtual void texture_set_force_redraw_if_visible(RID p_texture, bool p_enable) = 0; + virtual RID texture_rd_create(const RID &p_rd_texture, const RenderingServer::TextureLayeredType p_layer_type = RenderingServer::TEXTURE_LAYERED_2D_ARRAY) = 0; virtual RID texture_get_rd_texture(RID p_texture, bool p_srgb = false) const = 0; virtual uint64_t texture_get_native_handle(RID p_texture, bool p_srgb = false) const = 0; diff --git a/servers/text_server.cpp b/servers/text_server.cpp index 329344d4c4..07ad14f120 100644 --- a/servers/text_server.cpp +++ b/servers/text_server.cpp @@ -1539,7 +1539,7 @@ void TextServer::shaped_text_draw_outline(const RID &p_shaped, const RID &p_canv if (rtl && ellipsis_pos >= 0) { for (int i = ellipsis_gl_size - 1; i >= 0; i--) { for (int j = 0; j < ellipsis_glyphs[i].repeat; j++) { - font_draw_glyph(ellipsis_glyphs[i].font_rid, p_canvas, ellipsis_glyphs[i].font_size, ofs + Vector2(ellipsis_glyphs[i].x_off, ellipsis_glyphs[i].y_off), ellipsis_glyphs[i].index, p_color); + font_draw_glyph_outline(ellipsis_glyphs[i].font_rid, p_canvas, ellipsis_glyphs[i].font_size, p_outline_size, ofs + Vector2(ellipsis_glyphs[i].x_off, ellipsis_glyphs[i].y_off), ellipsis_glyphs[i].index, p_color); if (orientation == ORIENTATION_HORIZONTAL) { ofs.x += ellipsis_glyphs[i].advance; } else { @@ -1602,7 +1602,7 @@ void TextServer::shaped_text_draw_outline(const RID &p_shaped, const RID &p_canv if (!rtl && ellipsis_pos >= 0) { for (int i = 0; i < ellipsis_gl_size; i++) { for (int j = 0; j < ellipsis_glyphs[i].repeat; j++) { - font_draw_glyph(ellipsis_glyphs[i].font_rid, p_canvas, ellipsis_glyphs[i].font_size, ofs + Vector2(ellipsis_glyphs[i].x_off, ellipsis_glyphs[i].y_off), ellipsis_glyphs[i].index, p_color); + font_draw_glyph_outline(ellipsis_glyphs[i].font_rid, p_canvas, ellipsis_glyphs[i].font_size, p_outline_size, ofs + Vector2(ellipsis_glyphs[i].x_off, ellipsis_glyphs[i].y_off), ellipsis_glyphs[i].index, p_color); if (orientation == ORIENTATION_HORIZONTAL) { ofs.x += ellipsis_glyphs[i].advance; } else { diff --git a/tests/core/input/test_input_event.h b/tests/core/input/test_input_event.h new file mode 100644 index 0000000000..6b4b80486c --- /dev/null +++ b/tests/core/input/test_input_event.h @@ -0,0 +1,115 @@ +/**************************************************************************/ +/* test_input_event.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef TEST_INPUT_EVENT_H +#define TEST_INPUT_EVENT_H + +#include "core/input/input_event.h" +#include "core/math/rect2.h" +#include "core/os/memory.h" +#include "core/variant/array.h" + +#include "tests/test_macros.h" + +namespace TestInputEvent { +TEST_CASE("[InputEvent] Signal is emitted when device is changed") { + Ref<InputEventKey> input_event; + input_event.instantiate(); + + SIGNAL_WATCH(*input_event, SNAME("changed")); + Array args1; + Array empty_args; + empty_args.push_back(args1); + + input_event->set_device(1); + + SIGNAL_CHECK("changed", empty_args); + CHECK(input_event->get_device() == 1); + + SIGNAL_UNWATCH(*input_event, SNAME("changed")); +} + +TEST_CASE("[InputEvent] Test accumulate") { + Ref<InputEventMouseMotion> iemm1, iemm2; + Ref<InputEventKey> iek; + + iemm1.instantiate(), iemm2.instantiate(); + iek.instantiate(); + + iemm1->set_button_mask(MouseButtonMask::LEFT); + + CHECK_FALSE(iemm1->accumulate(iemm2)); + + iemm2->set_button_mask(MouseButtonMask::LEFT); + + CHECK(iemm1->accumulate(iemm2)); + + CHECK_FALSE(iemm1->accumulate(iek)); + CHECK_FALSE(iemm2->accumulate(iek)); +} + +TEST_CASE("[InputEvent][SceneTree] Test methods that interact with the InputMap") { + const String mock_action = "mock_action"; + Ref<InputEventJoypadMotion> iejm; + iejm.instantiate(); + + InputMap::get_singleton()->add_action(mock_action, 0.5); + InputMap::get_singleton()->action_add_event(mock_action, iejm); + + CHECK(iejm->is_action_type()); + CHECK(iejm->is_action(mock_action)); + + CHECK(iejm->is_action_released(mock_action)); + CHECK(Math::is_equal_approx(iejm->get_action_strength(mock_action), 0.0f)); + + iejm->set_axis_value(0.8f); + // Since deadzone is 0.5, action_strength grows linearly from 0.5 to 1.0. + CHECK(Math::is_equal_approx(iejm->get_action_strength(mock_action), 0.6f)); + CHECK(Math::is_equal_approx(iejm->get_action_raw_strength(mock_action), 0.8f)); + CHECK(iejm->is_action_pressed(mock_action)); + + InputMap::get_singleton()->erase_action(mock_action); +} + +TEST_CASE("[InputEvent] Test xformed_by") { + Ref<InputEventMouseMotion> iemm1; + iemm1.instantiate(); + + iemm1->set_position(Vector2(0.0f, 0.0f)); + Transform2D transform; + transform = transform.translated(Vector2(2.0f, 3.0f)); + + Ref<InputEventMouseMotion> iemm2 = iemm1->xformed_by(transform); + + CHECK(iemm2->get_position().is_equal_approx(Vector2(2.0f, 3.0f))); +} +} // namespace TestInputEvent + +#endif // TEST_INPUT_EVENT_H diff --git a/tests/core/io/test_resource.h b/tests/core/io/test_resource.h index 20d76c8894..8fc2a2f040 100644 --- a/tests/core/io/test_resource.h +++ b/tests/core/io/test_resource.h @@ -109,6 +109,58 @@ TEST_CASE("[Resource] Saving and loading") { loaded_child_resource_text->get_name() == "I'm a child resource", "The loaded child resource name should be equal to the expected value."); } + +TEST_CASE("[Resource] Breaking circular references on save") { + Ref<Resource> resource_a = memnew(Resource); + resource_a->set_name("A"); + Ref<Resource> resource_b = memnew(Resource); + resource_b->set_name("B"); + Ref<Resource> resource_c = memnew(Resource); + resource_c->set_name("C"); + resource_a->set_meta("next", resource_b); + resource_b->set_meta("next", resource_c); + resource_c->set_meta("next", resource_b); + + const String save_path_binary = OS::get_singleton()->get_cache_path().path_join("resource.res"); + const String save_path_text = OS::get_singleton()->get_cache_path().path_join("resource.tres"); + ResourceSaver::save(resource_a, save_path_binary); + ResourceSaver::save(resource_a, save_path_text); + + const Ref<Resource> &loaded_resource_a_binary = ResourceLoader::load(save_path_binary); + CHECK_MESSAGE( + loaded_resource_a_binary->get_name() == "A", + "The loaded resource name should be equal to the expected value."); + const Ref<Resource> &loaded_resource_b_binary = loaded_resource_a_binary->get_meta("next"); + CHECK_MESSAGE( + loaded_resource_b_binary->get_name() == "B", + "The loaded child resource name should be equal to the expected value."); + const Ref<Resource> &loaded_resource_c_binary = loaded_resource_b_binary->get_meta("next"); + CHECK_MESSAGE( + loaded_resource_c_binary->get_name() == "C", + "The loaded child resource name should be equal to the expected value."); + CHECK_MESSAGE( + !loaded_resource_c_binary->get_meta("next"), + "The loaded child resource circular reference should be NULL."); + + const Ref<Resource> &loaded_resource_a_text = ResourceLoader::load(save_path_text); + CHECK_MESSAGE( + loaded_resource_a_text->get_name() == "A", + "The loaded resource name should be equal to the expected value."); + const Ref<Resource> &loaded_resource_b_text = loaded_resource_a_text->get_meta("next"); + CHECK_MESSAGE( + loaded_resource_b_text->get_name() == "B", + "The loaded child resource name should be equal to the expected value."); + const Ref<Resource> &loaded_resource_c_text = loaded_resource_b_text->get_meta("next"); + CHECK_MESSAGE( + loaded_resource_c_text->get_name() == "C", + "The loaded child resource name should be equal to the expected value."); + CHECK_MESSAGE( + !loaded_resource_c_text->get_meta("next"), + "The loaded child resource circular reference should be NULL."); + + // Break circular reference to avoid memory leak + resource_c->remove_meta("next"); +} } // namespace TestResource #endif // TEST_RESOURCE_H diff --git a/tests/core/object/test_object.h b/tests/core/object/test_object.h index 98f9b3da65..8ab6221a1c 100644 --- a/tests/core/object/test_object.h +++ b/tests/core/object/test_object.h @@ -399,6 +399,29 @@ TEST_CASE("[Object] Signals") { SIGNAL_CHECK("my_custom_signal", empty_signal_args); SIGNAL_UNWATCH(&object, "my_custom_signal"); } + + SUBCASE("Connecting and then disconnecting many signals should not leave anything behind") { + List<Object::Connection> signal_connections; + Object targets[100]; + + for (int i = 0; i < 10; i++) { + ERR_PRINT_OFF; + for (Object &target : targets) { + object.connect("my_custom_signal", callable_mp(&target, &Object::notify_property_list_changed)); + } + ERR_PRINT_ON; + signal_connections.clear(); + object.get_all_signal_connections(&signal_connections); + CHECK(signal_connections.size() == 100); + } + + for (Object &target : targets) { + object.disconnect("my_custom_signal", callable_mp(&target, &Object::notify_property_list_changed)); + } + signal_connections.clear(); + object.get_all_signal_connections(&signal_connections); + CHECK(signal_connections.size() == 0); + } } } // namespace TestObject diff --git a/tests/core/threads/test_worker_thread_pool.h b/tests/core/threads/test_worker_thread_pool.h index e9a762b57b..ef0b475715 100644 --- a/tests/core/threads/test_worker_thread_pool.h +++ b/tests/core/threads/test_worker_thread_pool.h @@ -106,6 +106,32 @@ TEST_CASE("[WorkerThreadPool] Process elements using group tasks") { } } +TEST_CASE("[WorkerThreadPool] Parallel foreach") { + const int count_max = 256; + + for (int midpoint = 0; midpoint < count_max; midpoint++) { + LocalVector<int> c; + c.resize(count_max); + + for_range(0, count_max, true, String(), [&](int i) { + c[i] = 1; + }); + c.sort(); + CHECK(c[0] == 1); + CHECK(c[0] == c[count_max - 1]); + + for_range(0, midpoint, false, String(), [&](int i) { + c[i]++; + }); + for_range(midpoint, count_max, true, String(), [&](int i) { + c[i]++; + }); + c.sort(); + CHECK(c[0] == 2); + CHECK(c[0] == c[count_max - 1]); + } +} + } // namespace TestWorkerThreadPool #endif // TEST_WORKER_THREAD_POOL_H diff --git a/tests/core/variant/test_array.h b/tests/core/variant/test_array.h index ccb02ed5fa..228d77b3b5 100644 --- a/tests/core/variant/test_array.h +++ b/tests/core/variant/test_array.h @@ -304,13 +304,31 @@ TEST_CASE("[Array] slice()") { CHECK(slice8[1] == Variant(3)); CHECK(slice8[2] == Variant(1)); + Array slice9 = array.slice(10, 0, -2); + CHECK(slice9.size() == 3); + CHECK(slice9[0] == Variant(5)); + CHECK(slice9[1] == Variant(3)); + CHECK(slice9[2] == Variant(1)); + + Array slice10 = array.slice(2, -10, -1); + CHECK(slice10.size() == 3); + CHECK(slice10[0] == Variant(2)); + CHECK(slice10[1] == Variant(1)); + CHECK(slice10[2] == Variant(0)); + ERR_PRINT_OFF; - Array slice9 = array.slice(4, 1); - CHECK(slice9.size() == 0); + Array slice11 = array.slice(4, 1); + CHECK(slice11.size() == 0); - Array slice10 = array.slice(3, -4); - CHECK(slice10.size() == 0); + Array slice12 = array.slice(3, -4); + CHECK(slice12.size() == 0); ERR_PRINT_ON; + + Array slice13 = Array().slice(1); + CHECK(slice13.size() == 0); + + Array slice14 = array.slice(6); + CHECK(slice14.size() == 0); } TEST_CASE("[Array] Duplicate array") { diff --git a/tests/scene/test_theme.h b/tests/scene/test_theme.h index 69d29e5ca5..ad1ce1fd50 100644 --- a/tests/scene/test_theme.h +++ b/tests/scene/test_theme.h @@ -31,6 +31,8 @@ #ifndef TEST_THEME_H #define TEST_THEME_H +#include "scene/resources/image_texture.h" +#include "scene/resources/style_box_flat.h" #include "scene/resources/theme.h" #include "tests/test_tools.h" diff --git a/tests/servers/test_navigation_server_3d.h b/tests/servers/test_navigation_server_3d.h index ffd5b83231..8ea69ec67c 100644 --- a/tests/servers/test_navigation_server_3d.h +++ b/tests/servers/test_navigation_server_3d.h @@ -31,11 +31,38 @@ #ifndef TEST_NAVIGATION_SERVER_3D_H #define TEST_NAVIGATION_SERVER_3D_H +#include "scene/3d/mesh_instance_3d.h" +#include "scene/resources/primitive_meshes.h" #include "servers/navigation_server_3d.h" #include "tests/test_macros.h" namespace TestNavigationServer3D { + +// TODO: Find a more generic way to create `Callable` mocks. +class CallableMock : public Object { + GDCLASS(CallableMock, Object); + +public: + void function1(Variant arg0) { + function1_calls++; + function1_latest_arg0 = arg0; + } + + unsigned function1_calls{ 0 }; + Variant function1_latest_arg0{}; +}; + +static inline Array build_array() { + return Array(); +} +template <typename... Targs> +static inline Array build_array(Variant item, Targs... Fargs) { + Array a = build_array(Fargs...); + a.push_front(item); + return a; +} + TEST_SUITE("[Navigation]") { TEST_CASE("[NavigationServer3D] Server should be empty when initialized") { NavigationServer3D *navigation_server = NavigationServer3D::get_singleton(); @@ -56,7 +83,6 @@ TEST_SUITE("[Navigation]") { TEST_CASE("[NavigationServer3D] Server should manage agent properly") { NavigationServer3D *navigation_server = NavigationServer3D::get_singleton(); - CHECK_EQ(navigation_server->get_maps().size(), 0); RID agent = navigation_server->agent_create(); CHECK(agent.is_valid()); @@ -69,6 +95,7 @@ TEST_SUITE("[Navigation]") { bool initial_use_3d_avoidance = navigation_server->agent_get_use_3d_avoidance(agent); navigation_server->agent_set_use_3d_avoidance(agent, !initial_use_3d_avoidance); navigation_server->process(0.0); // Give server some cycles to commit. + CHECK_EQ(navigation_server->agent_get_use_3d_avoidance(agent), !initial_use_3d_avoidance); // TODO: Add remaining setters/getters once the missing getters are added. } @@ -83,20 +110,40 @@ TEST_SUITE("[Navigation]") { navigation_server->agent_set_map(agent, RID()); navigation_server->free(map); navigation_server->process(0.0); // Give server some cycles to commit. + CHECK_EQ(navigation_server->get_process_info(NavigationServer3D::INFO_AGENT_COUNT), 0); } navigation_server->free(agent); - - SUBCASE("'ProcessInfo' should not report removed agent") { - CHECK_EQ(navigation_server->get_process_info(NavigationServer3D::INFO_AGENT_COUNT), 0); - } } TEST_CASE("[NavigationServer3D] Server should manage map properly") { NavigationServer3D *navigation_server = NavigationServer3D::get_singleton(); - CHECK_EQ(navigation_server->get_maps().size(), 0); - RID map = navigation_server->map_create(); + RID map; + CHECK_FALSE(map.is_valid()); + + SUBCASE("Queries against invalid map should return empty or invalid values") { + CHECK_EQ(navigation_server->map_get_closest_point(map, Vector3(7, 7, 7)), Vector3()); + CHECK_EQ(navigation_server->map_get_closest_point_normal(map, Vector3(7, 7, 7)), Vector3()); + CHECK_FALSE(navigation_server->map_get_closest_point_owner(map, Vector3(7, 7, 7)).is_valid()); + CHECK_EQ(navigation_server->map_get_closest_point_to_segment(map, Vector3(7, 7, 7), Vector3(8, 8, 8), true), Vector3()); + CHECK_EQ(navigation_server->map_get_closest_point_to_segment(map, Vector3(7, 7, 7), Vector3(8, 8, 8), false), Vector3()); + CHECK_EQ(navigation_server->map_get_path(map, Vector3(7, 7, 7), Vector3(8, 8, 8), true).size(), 0); + CHECK_EQ(navigation_server->map_get_path(map, Vector3(7, 7, 7), Vector3(8, 8, 8), false).size(), 0); + + Ref<NavigationPathQueryParameters3D> query_parameters = memnew(NavigationPathQueryParameters3D); + query_parameters->set_map(map); + query_parameters->set_start_position(Vector3(7, 7, 7)); + query_parameters->set_target_position(Vector3(8, 8, 8)); + Ref<NavigationPathQueryResult3D> query_result = memnew(NavigationPathQueryResult3D); + navigation_server->query_path(query_parameters, query_result); + CHECK_EQ(query_result->get_path().size(), 0); + CHECK_EQ(query_result->get_path_types().size(), 0); + CHECK_EQ(query_result->get_path_rids().size(), 0); + CHECK_EQ(query_result->get_path_owner_ids().size(), 0); + } + + map = navigation_server->map_create(); CHECK(map.is_valid()); CHECK_EQ(navigation_server->get_maps().size(), 1); @@ -112,6 +159,7 @@ TEST_SUITE("[Navigation]") { bool initial_use_edge_connections = navigation_server->map_get_use_edge_connections(map); navigation_server->map_set_use_edge_connections(map, !initial_use_edge_connections); navigation_server->process(0.0); // Give server some cycles to commit. + CHECK_EQ(navigation_server->map_get_cell_size(map), doctest::Approx(0.55)); CHECK_EQ(navigation_server->map_get_edge_connection_margin(map), doctest::Approx(0.66)); CHECK_EQ(navigation_server->map_get_link_connection_radius(map), doctest::Approx(0.77)); @@ -140,10 +188,429 @@ TEST_SUITE("[Navigation]") { CHECK_EQ(navigation_server->map_get_agents(map).size(), 0); } + SUBCASE("Number of links should be reported properly") { + RID link = navigation_server->link_create(); + CHECK(link.is_valid()); + navigation_server->link_set_map(link, map); + navigation_server->process(0.0); // Give server some cycles to commit. + CHECK_EQ(navigation_server->map_get_links(map).size(), 1); + navigation_server->free(link); + navigation_server->process(0.0); // Give server some cycles to commit. + CHECK_EQ(navigation_server->map_get_links(map).size(), 0); + } + + SUBCASE("Number of obstacles should be reported properly") { + RID obstacle = navigation_server->obstacle_create(); + CHECK(obstacle.is_valid()); + navigation_server->obstacle_set_map(obstacle, map); + navigation_server->process(0.0); // Give server some cycles to commit. + CHECK_EQ(navigation_server->map_get_obstacles(map).size(), 1); + navigation_server->free(obstacle); + navigation_server->process(0.0); // Give server some cycles to commit. + CHECK_EQ(navigation_server->map_get_obstacles(map).size(), 0); + } + + SUBCASE("Number of regions should be reported properly") { + RID region = navigation_server->region_create(); + CHECK(region.is_valid()); + navigation_server->region_set_map(region, map); + navigation_server->process(0.0); // Give server some cycles to commit. + CHECK_EQ(navigation_server->map_get_regions(map).size(), 1); + navigation_server->free(region); + navigation_server->process(0.0); // Give server some cycles to commit. + CHECK_EQ(navigation_server->map_get_regions(map).size(), 0); + } + + SUBCASE("Queries against empty map should return empty or invalid values") { + navigation_server->map_set_active(map, true); + navigation_server->process(0.0); // Give server some cycles to commit. + + CHECK_EQ(navigation_server->map_get_closest_point(map, Vector3(7, 7, 7)), Vector3()); + CHECK_EQ(navigation_server->map_get_closest_point_normal(map, Vector3(7, 7, 7)), Vector3()); + CHECK_FALSE(navigation_server->map_get_closest_point_owner(map, Vector3(7, 7, 7)).is_valid()); + CHECK_EQ(navigation_server->map_get_closest_point_to_segment(map, Vector3(7, 7, 7), Vector3(8, 8, 8), true), Vector3()); + CHECK_EQ(navigation_server->map_get_closest_point_to_segment(map, Vector3(7, 7, 7), Vector3(8, 8, 8), false), Vector3()); + CHECK_EQ(navigation_server->map_get_path(map, Vector3(7, 7, 7), Vector3(8, 8, 8), true).size(), 0); + CHECK_EQ(navigation_server->map_get_path(map, Vector3(7, 7, 7), Vector3(8, 8, 8), false).size(), 0); + + Ref<NavigationPathQueryParameters3D> query_parameters = memnew(NavigationPathQueryParameters3D); + query_parameters->set_map(map); + query_parameters->set_start_position(Vector3(7, 7, 7)); + query_parameters->set_target_position(Vector3(8, 8, 8)); + Ref<NavigationPathQueryResult3D> query_result = memnew(NavigationPathQueryResult3D); + navigation_server->query_path(query_parameters, query_result); + CHECK_EQ(query_result->get_path().size(), 0); + CHECK_EQ(query_result->get_path_types().size(), 0); + CHECK_EQ(query_result->get_path_rids().size(), 0); + CHECK_EQ(query_result->get_path_owner_ids().size(), 0); + + navigation_server->map_set_active(map, false); + navigation_server->process(0.0); // Give server some cycles to commit. + } + navigation_server->free(map); navigation_server->process(0.0); // Give server some cycles to actually remove map. CHECK_EQ(navigation_server->get_maps().size(), 0); } + + TEST_CASE("[NavigationServer3D] Server should manage link properly") { + NavigationServer3D *navigation_server = NavigationServer3D::get_singleton(); + + RID link = navigation_server->link_create(); + CHECK(link.is_valid()); + + SUBCASE("'ProcessInfo' should not report dangling link") { + CHECK_EQ(navigation_server->get_process_info(NavigationServer3D::INFO_LINK_COUNT), 0); + } + + SUBCASE("Setters/getters should work") { + bool initial_bidirectional = navigation_server->link_is_bidirectional(link); + navigation_server->link_set_bidirectional(link, !initial_bidirectional); + navigation_server->link_set_end_position(link, Vector3(7, 7, 7)); + navigation_server->link_set_enter_cost(link, 0.55); + navigation_server->link_set_navigation_layers(link, 6); + navigation_server->link_set_owner_id(link, ObjectID((int64_t)7)); + navigation_server->link_set_start_position(link, Vector3(8, 8, 8)); + navigation_server->link_set_travel_cost(link, 0.66); + navigation_server->process(0.0); // Give server some cycles to commit. + + CHECK_EQ(navigation_server->link_is_bidirectional(link), !initial_bidirectional); + CHECK_EQ(navigation_server->link_get_end_position(link), Vector3(7, 7, 7)); + CHECK_EQ(navigation_server->link_get_enter_cost(link), doctest::Approx(0.55)); + CHECK_EQ(navigation_server->link_get_navigation_layers(link), 6); + CHECK_EQ(navigation_server->link_get_owner_id(link), ObjectID((int64_t)7)); + CHECK_EQ(navigation_server->link_get_start_position(link), Vector3(8, 8, 8)); + CHECK_EQ(navigation_server->link_get_travel_cost(link), doctest::Approx(0.66)); + } + + SUBCASE("'ProcessInfo' should report link with active map") { + RID map = navigation_server->map_create(); + CHECK(map.is_valid()); + navigation_server->map_set_active(map, true); + navigation_server->link_set_map(link, map); + navigation_server->process(0.0); // Give server some cycles to commit. + CHECK_EQ(navigation_server->get_process_info(NavigationServer3D::INFO_LINK_COUNT), 1); + navigation_server->link_set_map(link, RID()); + navigation_server->free(map); + navigation_server->process(0.0); // Give server some cycles to commit. + CHECK_EQ(navigation_server->get_process_info(NavigationServer3D::INFO_LINK_COUNT), 0); + } + + navigation_server->free(link); + } + + TEST_CASE("[NavigationServer3D] Server should manage obstacles properly") { + NavigationServer3D *navigation_server = NavigationServer3D::get_singleton(); + + RID obstacle = navigation_server->obstacle_create(); + CHECK(obstacle.is_valid()); + + // TODO: Add tests for setters/getters once getters are added. + + navigation_server->free(obstacle); + } + + TEST_CASE("[NavigationServer3D] Server should manage regions properly") { + NavigationServer3D *navigation_server = NavigationServer3D::get_singleton(); + + RID region = navigation_server->region_create(); + CHECK(region.is_valid()); + + SUBCASE("'ProcessInfo' should not report dangling region") { + CHECK_EQ(navigation_server->get_process_info(NavigationServer3D::INFO_REGION_COUNT), 0); + } + + SUBCASE("Setters/getters should work") { + bool initial_use_edge_connections = navigation_server->region_get_use_edge_connections(region); + navigation_server->region_set_enter_cost(region, 0.55); + navigation_server->region_set_navigation_layers(region, 5); + navigation_server->region_set_owner_id(region, ObjectID((int64_t)7)); + navigation_server->region_set_travel_cost(region, 0.66); + navigation_server->region_set_use_edge_connections(region, !initial_use_edge_connections); + navigation_server->process(0.0); // Give server some cycles to commit. + + CHECK_EQ(navigation_server->region_get_enter_cost(region), doctest::Approx(0.55)); + CHECK_EQ(navigation_server->region_get_navigation_layers(region), 5); + CHECK_EQ(navigation_server->region_get_owner_id(region), ObjectID((int64_t)7)); + CHECK_EQ(navigation_server->region_get_travel_cost(region), doctest::Approx(0.66)); + CHECK_EQ(navigation_server->region_get_use_edge_connections(region), !initial_use_edge_connections); + } + + SUBCASE("'ProcessInfo' should report region with active map") { + RID map = navigation_server->map_create(); + CHECK(map.is_valid()); + navigation_server->map_set_active(map, true); + navigation_server->region_set_map(region, map); + navigation_server->process(0.0); // Give server some cycles to commit. + CHECK_EQ(navigation_server->get_process_info(NavigationServer3D::INFO_REGION_COUNT), 1); + navigation_server->region_set_map(region, RID()); + navigation_server->free(map); + navigation_server->process(0.0); // Give server some cycles to commit. + CHECK_EQ(navigation_server->get_process_info(NavigationServer3D::INFO_REGION_COUNT), 0); + } + + SUBCASE("Queries against empty region should return empty or invalid values") { + CHECK_EQ(navigation_server->region_get_connections_count(region), 0); + CHECK_EQ(navigation_server->region_get_connection_pathway_end(region, 55), Vector3()); + CHECK_EQ(navigation_server->region_get_connection_pathway_start(region, 55), Vector3()); + } + + navigation_server->free(region); + } + + // This test case does not check precise values on purpose - to not be too sensitivte. + TEST_CASE("[NavigationServer3D] Server should move agent properly") { + NavigationServer3D *navigation_server = NavigationServer3D::get_singleton(); + + RID map = navigation_server->map_create(); + RID agent = navigation_server->agent_create(); + + navigation_server->map_set_active(map, true); + navigation_server->agent_set_map(agent, map); + navigation_server->agent_set_avoidance_enabled(agent, true); + navigation_server->agent_set_velocity(agent, Vector3(1, 0, 1)); + CallableMock agent_avoidance_callback_mock; + navigation_server->agent_set_avoidance_callback(agent, callable_mp(&agent_avoidance_callback_mock, &CallableMock::function1)); + CHECK_EQ(agent_avoidance_callback_mock.function1_calls, 0); + navigation_server->process(0.0); // Give server some cycles to commit. + CHECK_EQ(agent_avoidance_callback_mock.function1_calls, 1); + CHECK_NE(agent_avoidance_callback_mock.function1_latest_arg0, Vector3(0, 0, 0)); + + navigation_server->free(agent); + navigation_server->free(map); + } + + // This test case does not check precise values on purpose - to not be too sensitivte. + TEST_CASE("[NavigationServer3D] Server should make agents avoid each other when avoidance enabled") { + NavigationServer3D *navigation_server = NavigationServer3D::get_singleton(); + + RID map = navigation_server->map_create(); + RID agent_1 = navigation_server->agent_create(); + RID agent_2 = navigation_server->agent_create(); + + navigation_server->map_set_active(map, true); + + navigation_server->agent_set_map(agent_1, map); + navigation_server->agent_set_avoidance_enabled(agent_1, true); + navigation_server->agent_set_position(agent_1, Vector3(0, 0, 0)); + navigation_server->agent_set_radius(agent_1, 1); + navigation_server->agent_set_velocity(agent_1, Vector3(1, 0, 0)); + CallableMock agent_1_avoidance_callback_mock; + navigation_server->agent_set_avoidance_callback(agent_1, callable_mp(&agent_1_avoidance_callback_mock, &CallableMock::function1)); + + navigation_server->agent_set_map(agent_2, map); + navigation_server->agent_set_avoidance_enabled(agent_2, true); + navigation_server->agent_set_position(agent_2, Vector3(2.5, 0, 0.5)); + navigation_server->agent_set_radius(agent_2, 1); + navigation_server->agent_set_velocity(agent_2, Vector3(-1, 0, 0)); + CallableMock agent_2_avoidance_callback_mock; + navigation_server->agent_set_avoidance_callback(agent_2, callable_mp(&agent_2_avoidance_callback_mock, &CallableMock::function1)); + + CHECK_EQ(agent_1_avoidance_callback_mock.function1_calls, 0); + CHECK_EQ(agent_2_avoidance_callback_mock.function1_calls, 0); + navigation_server->process(0.0); // Give server some cycles to commit. + CHECK_EQ(agent_1_avoidance_callback_mock.function1_calls, 1); + CHECK_EQ(agent_2_avoidance_callback_mock.function1_calls, 1); + Vector3 agent_1_safe_velocity = agent_1_avoidance_callback_mock.function1_latest_arg0; + Vector3 agent_2_safe_velocity = agent_2_avoidance_callback_mock.function1_latest_arg0; + CHECK_MESSAGE(agent_1_safe_velocity.x > 0, "agent 1 should move a bit along desired velocity (+X)"); + CHECK_MESSAGE(agent_2_safe_velocity.x < 0, "agent 2 should move a bit along desired velocity (-X)"); + CHECK_MESSAGE(agent_1_safe_velocity.z < 0, "agent 1 should move a bit to the side so that it avoids agent 2"); + CHECK_MESSAGE(agent_2_safe_velocity.z > 0, "agent 2 should move a bit to the side so that it avoids agent 1"); + + navigation_server->free(agent_2); + navigation_server->free(agent_1); + navigation_server->free(map); + } + + // This test case uses only public APIs on purpose - other test cases use simplified baking. + // FIXME: Remove once deprecated `region_bake_navigation_mesh()` is removed. + TEST_CASE("[NavigationServer3D][SceneTree][DEPRECATED] Server should be able to bake map correctly") { + NavigationServer3D *navigation_server = NavigationServer3D::get_singleton(); + + // Prepare scene tree with simple mesh to serve as an input geometry. + Node3D *node_3d = memnew(Node3D); + SceneTree::get_singleton()->get_root()->add_child(node_3d); + Ref<PlaneMesh> plane_mesh = memnew(PlaneMesh); + plane_mesh->set_size(Size2(10.0, 10.0)); + MeshInstance3D *mesh_instance = memnew(MeshInstance3D); + mesh_instance->set_mesh(plane_mesh); + node_3d->add_child(mesh_instance); + + // Prepare anything necessary to bake navigation mesh. + RID map = navigation_server->map_create(); + RID region = navigation_server->region_create(); + Ref<NavigationMesh> navigation_mesh = memnew(NavigationMesh); + navigation_server->map_set_active(map, true); + navigation_server->region_set_map(region, map); + navigation_server->region_set_navigation_mesh(region, navigation_mesh); + navigation_server->process(0.0); // Give server some cycles to commit. + + CHECK_EQ(navigation_mesh->get_polygon_count(), 0); + CHECK_EQ(navigation_mesh->get_vertices().size(), 0); + + navigation_server->region_bake_navigation_mesh(navigation_mesh, node_3d); + // FIXME: The above line should trigger the update (line below) under the hood. + navigation_server->region_set_navigation_mesh(region, navigation_mesh); // Force update. + CHECK_EQ(navigation_mesh->get_polygon_count(), 2); + CHECK_EQ(navigation_mesh->get_vertices().size(), 4); + + SUBCASE("Map should emit signal and take newly baked navigation mesh into account") { + SIGNAL_WATCH(navigation_server, "map_changed"); + SIGNAL_CHECK_FALSE("map_changed"); + navigation_server->process(0.0); // Give server some cycles to commit. + SIGNAL_CHECK("map_changed", build_array(build_array(map))); + SIGNAL_UNWATCH(navigation_server, "map_changed"); + CHECK_NE(navigation_server->map_get_closest_point(map, Vector3(0, 0, 0)), Vector3(0, 0, 0)); + } + + navigation_server->free(region); + navigation_server->free(map); + navigation_server->process(0.0); // Give server some cycles to commit. + memdelete(mesh_instance); + memdelete(node_3d); + } + + // This test case uses only public APIs on purpose - other test cases use simplified baking. + TEST_CASE("[NavigationServer3D][SceneTree] Server should be able to bake map correctly") { + NavigationServer3D *navigation_server = NavigationServer3D::get_singleton(); + + // Prepare scene tree with simple mesh to serve as an input geometry. + Node3D *node_3d = memnew(Node3D); + SceneTree::get_singleton()->get_root()->add_child(node_3d); + Ref<PlaneMesh> plane_mesh = memnew(PlaneMesh); + plane_mesh->set_size(Size2(10.0, 10.0)); + MeshInstance3D *mesh_instance = memnew(MeshInstance3D); + mesh_instance->set_mesh(plane_mesh); + node_3d->add_child(mesh_instance); + + // Prepare anything necessary to bake navigation mesh. + RID map = navigation_server->map_create(); + RID region = navigation_server->region_create(); + Ref<NavigationMesh> navigation_mesh = memnew(NavigationMesh); + navigation_server->map_set_active(map, true); + navigation_server->region_set_map(region, map); + navigation_server->region_set_navigation_mesh(region, navigation_mesh); + navigation_server->process(0.0); // Give server some cycles to commit. + + CHECK_EQ(navigation_mesh->get_polygon_count(), 0); + CHECK_EQ(navigation_mesh->get_vertices().size(), 0); + + Ref<NavigationMeshSourceGeometryData3D> source_geometry = memnew(NavigationMeshSourceGeometryData3D); + navigation_server->parse_source_geometry_data(navigation_mesh, source_geometry, node_3d); + navigation_server->bake_from_source_geometry_data(navigation_mesh, source_geometry, Callable()); + // FIXME: The above line should trigger the update (line below) under the hood. + navigation_server->region_set_navigation_mesh(region, navigation_mesh); // Force update. + CHECK_EQ(navigation_mesh->get_polygon_count(), 2); + CHECK_EQ(navigation_mesh->get_vertices().size(), 4); + + SUBCASE("Map should emit signal and take newly baked navigation mesh into account") { + SIGNAL_WATCH(navigation_server, "map_changed"); + SIGNAL_CHECK_FALSE("map_changed"); + navigation_server->process(0.0); // Give server some cycles to commit. + SIGNAL_CHECK("map_changed", build_array(build_array(map))); + SIGNAL_UNWATCH(navigation_server, "map_changed"); + CHECK_NE(navigation_server->map_get_closest_point(map, Vector3(0, 0, 0)), Vector3(0, 0, 0)); + } + + navigation_server->free(region); + navigation_server->free(map); + navigation_server->process(0.0); // Give server some cycles to commit. + memdelete(mesh_instance); + memdelete(node_3d); + } + + // This test case does not check precise values on purpose - to not be too sensitivte. + TEST_CASE("[NavigationServer3D] Server should respond to queries against valid map properly") { + NavigationServer3D *navigation_server = NavigationServer3D::get_singleton(); + Ref<NavigationMesh> navigation_mesh = memnew(NavigationMesh); + Ref<NavigationMeshSourceGeometryData3D> source_geometry = memnew(NavigationMeshSourceGeometryData3D); + + Array arr; + arr.resize(RS::ARRAY_MAX); + BoxMesh::create_mesh_array(arr, Vector3(10.0, 0.001, 10.0)); + source_geometry->add_mesh_array(arr, Transform3D()); + navigation_server->bake_from_source_geometry_data(navigation_mesh, source_geometry, Callable()); + CHECK_NE(navigation_mesh->get_polygon_count(), 0); + CHECK_NE(navigation_mesh->get_vertices().size(), 0); + + RID map = navigation_server->map_create(); + RID region = navigation_server->region_create(); + navigation_server->map_set_active(map, true); + navigation_server->region_set_map(region, map); + navigation_server->region_set_navigation_mesh(region, navigation_mesh); + navigation_server->process(0.0); // Give server some cycles to commit. + + SUBCASE("Simple queries should return non-default values") { + CHECK_NE(navigation_server->map_get_closest_point(map, Vector3(0, 0, 0)), Vector3(0, 0, 0)); + CHECK_NE(navigation_server->map_get_closest_point_normal(map, Vector3(0, 0, 0)), Vector3()); + CHECK(navigation_server->map_get_closest_point_owner(map, Vector3(0, 0, 0)).is_valid()); + // TODO: Test map_get_closest_point_to_segment() with p_use_collision=true as well. + CHECK_NE(navigation_server->map_get_closest_point_to_segment(map, Vector3(0, 0, 0), Vector3(1, 1, 1), false), Vector3()); + CHECK_NE(navigation_server->map_get_path(map, Vector3(0, 0, 0), Vector3(10, 0, 10), true).size(), 0); + CHECK_NE(navigation_server->map_get_path(map, Vector3(0, 0, 0), Vector3(10, 0, 10), false).size(), 0); + } + + SUBCASE("Elaborate query with 'CORRIDORFUNNEL' post-processing should yield non-empty result") { + Ref<NavigationPathQueryParameters3D> query_parameters = memnew(NavigationPathQueryParameters3D); + query_parameters->set_map(map); + query_parameters->set_start_position(Vector3(0, 0, 0)); + query_parameters->set_target_position(Vector3(10, 0, 10)); + query_parameters->set_path_postprocessing(NavigationPathQueryParameters3D::PATH_POSTPROCESSING_CORRIDORFUNNEL); + Ref<NavigationPathQueryResult3D> query_result = memnew(NavigationPathQueryResult3D); + navigation_server->query_path(query_parameters, query_result); + CHECK_NE(query_result->get_path().size(), 0); + CHECK_NE(query_result->get_path_types().size(), 0); + CHECK_NE(query_result->get_path_rids().size(), 0); + CHECK_NE(query_result->get_path_owner_ids().size(), 0); + } + + SUBCASE("Elaborate query with 'EDGECENTERED' post-processing should yield non-empty result") { + Ref<NavigationPathQueryParameters3D> query_parameters = memnew(NavigationPathQueryParameters3D); + query_parameters->set_map(map); + query_parameters->set_start_position(Vector3(10, 0, 10)); + query_parameters->set_target_position(Vector3(0, 0, 0)); + query_parameters->set_path_postprocessing(NavigationPathQueryParameters3D::PATH_POSTPROCESSING_EDGECENTERED); + Ref<NavigationPathQueryResult3D> query_result = memnew(NavigationPathQueryResult3D); + navigation_server->query_path(query_parameters, query_result); + CHECK_NE(query_result->get_path().size(), 0); + CHECK_NE(query_result->get_path_types().size(), 0); + CHECK_NE(query_result->get_path_rids().size(), 0); + CHECK_NE(query_result->get_path_owner_ids().size(), 0); + } + + SUBCASE("Elaborate query with non-matching navigation layer mask should yield empty result") { + Ref<NavigationPathQueryParameters3D> query_parameters = memnew(NavigationPathQueryParameters3D); + query_parameters->set_map(map); + query_parameters->set_start_position(Vector3(10, 0, 10)); + query_parameters->set_target_position(Vector3(0, 0, 0)); + query_parameters->set_navigation_layers(2); + Ref<NavigationPathQueryResult3D> query_result = memnew(NavigationPathQueryResult3D); + navigation_server->query_path(query_parameters, query_result); + CHECK_EQ(query_result->get_path().size(), 0); + CHECK_EQ(query_result->get_path_types().size(), 0); + CHECK_EQ(query_result->get_path_rids().size(), 0); + CHECK_EQ(query_result->get_path_owner_ids().size(), 0); + } + + SUBCASE("Elaborate query without metadata flags should yield path only") { + Ref<NavigationPathQueryParameters3D> query_parameters = memnew(NavigationPathQueryParameters3D); + query_parameters->set_map(map); + query_parameters->set_start_position(Vector3(10, 0, 10)); + query_parameters->set_target_position(Vector3(0, 0, 0)); + query_parameters->set_metadata_flags(0); + Ref<NavigationPathQueryResult3D> query_result = memnew(NavigationPathQueryResult3D); + navigation_server->query_path(query_parameters, query_result); + CHECK_NE(query_result->get_path().size(), 0); + CHECK_EQ(query_result->get_path_types().size(), 0); + CHECK_EQ(query_result->get_path_rids().size(), 0); + CHECK_EQ(query_result->get_path_owner_ids().size(), 0); + } + + navigation_server->free(region); + navigation_server->free(map); + navigation_server->process(0.0); // Give server some cycles to commit. + } } } //namespace TestNavigationServer3D diff --git a/tests/test_main.cpp b/tests/test_main.cpp index 2e99e6fdb6..96ee146e77 100644 --- a/tests/test_main.cpp +++ b/tests/test_main.cpp @@ -31,6 +31,7 @@ #include "test_main.h" #include "tests/core/config/test_project_settings.h" +#include "tests/core/input/test_input_event.h" #include "tests/core/input/test_input_event_key.h" #include "tests/core/input/test_input_event_mouse.h" #include "tests/core/input/test_shortcut.h" diff --git a/thirdparty/README.md b/thirdparty/README.md index 071dea330c..a918acbe77 100644 --- a/thirdparty/README.md +++ b/thirdparty/README.md @@ -115,7 +115,7 @@ commits. ## enet - Upstream: http://enet.bespin.org -- Version: 1.3.17 (e0e7045b7e056b454b5093cb34df49dc4cee0bee, 2020) +- Version: git (ea4607a90dbfbcf4da2669ea998585253d8e70b1, 2023) - License: MIT Files extracted from upstream source: diff --git a/thirdparty/enet/enet/enet.h b/thirdparty/enet/enet/enet.h index 77f8004b80..5232f8a869 100644 --- a/thirdparty/enet/enet/enet.h +++ b/thirdparty/enet/enet/enet.h @@ -68,7 +68,8 @@ typedef enum _ENetSocketOption ENET_SOCKOPT_RCVTIMEO = 6, ENET_SOCKOPT_SNDTIMEO = 7, ENET_SOCKOPT_ERROR = 8, - ENET_SOCKOPT_NODELAY = 9 + ENET_SOCKOPT_NODELAY = 9, + ENET_SOCKOPT_TTL = 10 } ENetSocketOption; typedef enum _ENetSocketShutdown @@ -179,7 +180,7 @@ typedef struct _ENetOutgoingCommand enet_uint16 unreliableSequenceNumber; enet_uint32 sentTime; enet_uint32 roundTripTimeout; - enet_uint32 roundTripTimeoutLimit; + enet_uint32 queueTime; enet_uint32 fragmentOffset; enet_uint16 fragmentLength; enet_uint16 sendAttempts; @@ -222,7 +223,7 @@ enum ENET_HOST_RECEIVE_BUFFER_SIZE = 256 * 1024, ENET_HOST_SEND_BUFFER_SIZE = 256 * 1024, ENET_HOST_BANDWIDTH_THROTTLE_INTERVAL = 1000, - ENET_HOST_DEFAULT_MTU = 1400, + ENET_HOST_DEFAULT_MTU = 1392, ENET_HOST_DEFAULT_MAXIMUM_PACKET_SIZE = 32 * 1024 * 1024, ENET_HOST_DEFAULT_MAXIMUM_WAITING_DATA = 32 * 1024 * 1024, @@ -262,7 +263,8 @@ typedef struct _ENetChannel typedef enum _ENetPeerFlag { - ENET_PEER_FLAG_NEEDS_DISPATCH = (1 << 0) + ENET_PEER_FLAG_NEEDS_DISPATCH = (1 << 0), + ENET_PEER_FLAG_CONTINUE_SENDING = (1 << 1) } ENetPeerFlag; /** @@ -322,7 +324,7 @@ typedef struct _ENetPeer enet_uint16 outgoingReliableSequenceNumber; ENetList acknowledgements; ENetList sentReliableCommands; - ENetList sentUnreliableCommands; + ENetList outgoingSendReliableCommands; ENetList outgoingCommands; ENetList dispatchedCommands; enet_uint16 flags; @@ -385,7 +387,7 @@ typedef struct _ENetHost size_t channelLimit; /**< maximum number of channels allowed for connected peers */ enet_uint32 serviceTime; ENetList dispatchQueue; - int continueSending; + enet_uint32 totalQueued; size_t packetSize; enet_uint16 headerFlags; ENetProtocol commands [ENET_PROTOCOL_MAXIMUM_PACKET_COMMANDS]; @@ -585,6 +587,7 @@ ENET_API void enet_host_channel_limit (ENetHost *, size_t); ENET_API void enet_host_bandwidth_limit (ENetHost *, enet_uint32, enet_uint32); extern void enet_host_bandwidth_throttle (ENetHost *); extern enet_uint32 enet_host_random_seed (void); +extern enet_uint32 enet_host_random (ENetHost *); ENET_API int enet_peer_send (ENetPeer *, enet_uint8, ENetPacket *); ENET_API ENetPacket * enet_peer_receive (ENetPeer *, enet_uint8 * channelID); @@ -598,6 +601,7 @@ ENET_API void enet_peer_disconnect_later (ENetPeer *, enet_uint32 ENET_API void enet_peer_throttle_configure (ENetPeer *, enet_uint32, enet_uint32, enet_uint32); extern int enet_peer_throttle (ENetPeer *, enet_uint32); extern void enet_peer_reset_queues (ENetPeer *); +extern int enet_peer_has_outgoing_commands (ENetPeer *); extern void enet_peer_setup_outgoing_command (ENetPeer *, ENetOutgoingCommand *); extern ENetOutgoingCommand * enet_peer_queue_outgoing_command (ENetPeer *, const ENetProtocol *, ENetPacket *, enet_uint32, enet_uint16); extern ENetIncomingCommand * enet_peer_queue_incoming_command (ENetPeer *, const ENetProtocol *, const void *, size_t, enet_uint32, enet_uint32); diff --git a/thirdparty/enet/godot.cpp b/thirdparty/enet/godot.cpp index 2cbfe59fc6..9ce126a475 100644 --- a/thirdparty/enet/godot.cpp +++ b/thirdparty/enet/godot.cpp @@ -535,6 +535,10 @@ int enet_socket_receive(ENetSocket socket, ENetAddress *address, ENetBuffer *buf if (err == ERR_BUSY) { return 0; } + if (err == ERR_OUT_OF_MEMORY) { + // A packet above the ENET_PROTOCOL_MAXIMUM_MTU was received. + return -2; + } if (err != OK) { return -1; diff --git a/thirdparty/enet/host.c b/thirdparty/enet/host.c index 21ab27e247..adb3533cf1 100644 --- a/thirdparty/enet/host.c +++ b/thirdparty/enet/host.c @@ -96,6 +96,7 @@ enet_host_create (const ENetAddress * address, size_t peerCount, size_t channelL host -> totalSentPackets = 0; host -> totalReceivedData = 0; host -> totalReceivedPackets = 0; + host -> totalQueued = 0; host -> connectedPeers = 0; host -> bandwidthLimitedPeers = 0; @@ -123,8 +124,8 @@ enet_host_create (const ENetAddress * address, size_t peerCount, size_t channelL enet_list_clear (& currentPeer -> acknowledgements); enet_list_clear (& currentPeer -> sentReliableCommands); - enet_list_clear (& currentPeer -> sentUnreliableCommands); enet_list_clear (& currentPeer -> outgoingCommands); + enet_list_clear (& currentPeer -> outgoingSendReliableCommands); enet_list_clear (& currentPeer -> dispatchedCommands); enet_peer_reset (currentPeer); @@ -160,6 +161,16 @@ enet_host_destroy (ENetHost * host) enet_free (host); } +enet_uint32 +enet_host_random (ENetHost * host) +{ + /* Mulberry32 by Tommy Ettinger */ + enet_uint32 n = (host -> randomSeed += 0x6D2B79F5U); + n = (n ^ (n >> 15)) * (n | 1U); + n ^= n + (n ^ (n >> 7)) * (n | 61U); + return n ^ (n >> 14); +} + /** Initiates a connection to a foreign host. @param host host seeking the connection @param address destination for the connection @@ -199,7 +210,8 @@ enet_host_connect (ENetHost * host, const ENetAddress * address, size_t channelC currentPeer -> channelCount = channelCount; currentPeer -> state = ENET_PEER_STATE_CONNECTING; currentPeer -> address = * address; - currentPeer -> connectID = ++ host -> randomSeed; + currentPeer -> connectID = enet_host_random (host); + currentPeer -> mtu = host -> mtu; if (host -> outgoingBandwidth == 0) currentPeer -> windowSize = ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE; diff --git a/thirdparty/enet/packet.c b/thirdparty/enet/packet.c index 5fa78b28ae..d51c640404 100644 --- a/thirdparty/enet/packet.c +++ b/thirdparty/enet/packet.c @@ -98,53 +98,46 @@ enet_packet_resize (ENetPacket * packet, size_t dataLength) return 0; } -static int initializedCRC32 = 0; -static enet_uint32 crcTable [256]; - -static enet_uint32 -reflect_crc (int val, int bits) +static const enet_uint32 crcTable [256] = { - int result = 0, bit; - - for (bit = 0; bit < bits; bit ++) - { - if(val & 1) result |= 1 << (bits - 1 - bit); - val >>= 1; - } - - return result; -} - -static void -initialize_crc32 (void) -{ - int byte; - - for (byte = 0; byte < 256; ++ byte) - { - enet_uint32 crc = reflect_crc (byte, 8) << 24; - int offset; + 0, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3, + 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, + 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, + 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, + 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, + 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, + 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F, + 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, + 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433, + 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01, + 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, + 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, + 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, + 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, + 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, + 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD, + 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, + 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, + 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, + 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, + 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, + 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79, + 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, + 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, + 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x5005713, + 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0xBDBDF21, + 0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, + 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, + 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, + 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, + 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF, + 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D +}; - for(offset = 0; offset < 8; ++ offset) - { - if (crc & 0x80000000) - crc = (crc << 1) ^ 0x04c11db7; - else - crc <<= 1; - } - - crcTable [byte] = reflect_crc (crc, 32); - } - - initializedCRC32 = 1; -} - enet_uint32 enet_crc32 (const ENetBuffer * buffers, size_t bufferCount) { enet_uint32 crc = 0xFFFFFFFF; - - if (! initializedCRC32) initialize_crc32 (); while (bufferCount -- > 0) { @@ -153,7 +146,7 @@ enet_crc32 (const ENetBuffer * buffers, size_t bufferCount) while (data < dataEnd) { - crc = (crc >> 8) ^ crcTable [(crc & 0xFF) ^ *data++]; + crc = (crc >> 8) ^ crcTable [(crc & 0xFF) ^ *data++]; } ++ buffers; diff --git a/thirdparty/enet/peer.c b/thirdparty/enet/peer.c index 9370ef4be1..a7ac012079 100644 --- a/thirdparty/enet/peer.c +++ b/thirdparty/enet/peer.c @@ -90,6 +90,13 @@ enet_peer_throttle (ENetPeer * peer, enet_uint32 rtt) } /** Queues a packet to be sent. + + On success, ENet will assume ownership of the packet, and so enet_packet_destroy + should not be called on it thereafter. On failure, the caller still must destroy + the packet on its own as ENet has not queued the packet. The caller can also + check the packet's referenceCount field after sending to check if ENet queued + the packet and thus incremented the referenceCount. + @param peer destination for the packet @param channelID channel on which to send @param packet packet to send @@ -99,7 +106,7 @@ enet_peer_throttle (ENetPeer * peer, enet_uint32 rtt) int enet_peer_send (ENetPeer * peer, enet_uint8 channelID, ENetPacket * packet) { - ENetChannel * channel = & peer -> channels [channelID]; + ENetChannel * channel; ENetProtocol command; size_t fragmentLength; @@ -108,6 +115,7 @@ enet_peer_send (ENetPeer * peer, enet_uint8 channelID, ENetPacket * packet) packet -> dataLength > peer -> host -> maximumPacketSize) return -1; + channel = & peer -> channels [channelID]; fragmentLength = peer -> mtu - sizeof (ENetProtocolHeader) - sizeof (ENetProtocolSendFragment); if (peer -> host -> checksum != NULL) fragmentLength -= sizeof(enet_uint32); @@ -320,8 +328,8 @@ enet_peer_reset_queues (ENetPeer * peer) enet_free (enet_list_remove (enet_list_begin (& peer -> acknowledgements))); enet_peer_reset_outgoing_commands (& peer -> sentReliableCommands); - enet_peer_reset_outgoing_commands (& peer -> sentUnreliableCommands); enet_peer_reset_outgoing_commands (& peer -> outgoingCommands); + enet_peer_reset_outgoing_commands (& peer -> outgoingSendReliableCommands); enet_peer_reset_incoming_commands (& peer -> dispatchedCommands); if (peer -> channels != NULL && peer -> channelCount > 0) @@ -563,6 +571,17 @@ enet_peer_disconnect (ENetPeer * peer, enet_uint32 data) } } +int +enet_peer_has_outgoing_commands (ENetPeer * peer) +{ + if (enet_list_empty (& peer -> outgoingCommands) && + enet_list_empty (& peer -> outgoingSendReliableCommands) && + enet_list_empty (& peer -> sentReliableCommands)) + return 0; + + return 1; +} + /** Request a disconnection from a peer, but only after all queued outgoing packets are sent. @param peer peer to request a disconnection @param data data describing the disconnection @@ -573,8 +592,7 @@ void enet_peer_disconnect_later (ENetPeer * peer, enet_uint32 data) { if ((peer -> state == ENET_PEER_STATE_CONNECTED || peer -> state == ENET_PEER_STATE_DISCONNECT_LATER) && - ! (enet_list_empty (& peer -> outgoingCommands) && - enet_list_empty (& peer -> sentReliableCommands))) + enet_peer_has_outgoing_commands (peer)) { peer -> state = ENET_PEER_STATE_DISCONNECT_LATER; peer -> eventData = data; @@ -618,8 +636,6 @@ enet_peer_queue_acknowledgement (ENetPeer * peer, const ENetProtocol * command, void enet_peer_setup_outgoing_command (ENetPeer * peer, ENetOutgoingCommand * outgoingCommand) { - ENetChannel * channel = & peer -> channels [outgoingCommand -> command.header.channelID]; - peer -> outgoingDataTotal += enet_protocol_command_size (outgoingCommand -> command.header.command) + outgoingCommand -> fragmentLength; if (outgoingCommand -> command.header.channelID == 0xFF) @@ -630,36 +646,40 @@ enet_peer_setup_outgoing_command (ENetPeer * peer, ENetOutgoingCommand * outgoin outgoingCommand -> unreliableSequenceNumber = 0; } else - if (outgoingCommand -> command.header.command & ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE) { - ++ channel -> outgoingReliableSequenceNumber; - channel -> outgoingUnreliableSequenceNumber = 0; + ENetChannel * channel = & peer -> channels [outgoingCommand -> command.header.channelID]; - outgoingCommand -> reliableSequenceNumber = channel -> outgoingReliableSequenceNumber; - outgoingCommand -> unreliableSequenceNumber = 0; - } - else - if (outgoingCommand -> command.header.command & ENET_PROTOCOL_COMMAND_FLAG_UNSEQUENCED) - { - ++ peer -> outgoingUnsequencedGroup; + if (outgoingCommand -> command.header.command & ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE) + { + ++ channel -> outgoingReliableSequenceNumber; + channel -> outgoingUnreliableSequenceNumber = 0; - outgoingCommand -> reliableSequenceNumber = 0; - outgoingCommand -> unreliableSequenceNumber = 0; - } - else - { - if (outgoingCommand -> fragmentOffset == 0) - ++ channel -> outgoingUnreliableSequenceNumber; - - outgoingCommand -> reliableSequenceNumber = channel -> outgoingReliableSequenceNumber; - outgoingCommand -> unreliableSequenceNumber = channel -> outgoingUnreliableSequenceNumber; + outgoingCommand -> reliableSequenceNumber = channel -> outgoingReliableSequenceNumber; + outgoingCommand -> unreliableSequenceNumber = 0; + } + else + if (outgoingCommand -> command.header.command & ENET_PROTOCOL_COMMAND_FLAG_UNSEQUENCED) + { + ++ peer -> outgoingUnsequencedGroup; + + outgoingCommand -> reliableSequenceNumber = 0; + outgoingCommand -> unreliableSequenceNumber = 0; + } + else + { + if (outgoingCommand -> fragmentOffset == 0) + ++ channel -> outgoingUnreliableSequenceNumber; + + outgoingCommand -> reliableSequenceNumber = channel -> outgoingReliableSequenceNumber; + outgoingCommand -> unreliableSequenceNumber = channel -> outgoingUnreliableSequenceNumber; + } } - + outgoingCommand -> sendAttempts = 0; outgoingCommand -> sentTime = 0; outgoingCommand -> roundTripTimeout = 0; - outgoingCommand -> roundTripTimeoutLimit = 0; outgoingCommand -> command.header.reliableSequenceNumber = ENET_HOST_TO_NET_16 (outgoingCommand -> reliableSequenceNumber); + outgoingCommand -> queueTime = ++ peer -> host -> totalQueued; switch (outgoingCommand -> command.header.command & ENET_PROTOCOL_COMMAND_MASK) { @@ -670,12 +690,16 @@ enet_peer_setup_outgoing_command (ENetPeer * peer, ENetOutgoingCommand * outgoin case ENET_PROTOCOL_COMMAND_SEND_UNSEQUENCED: outgoingCommand -> command.sendUnsequenced.unsequencedGroup = ENET_HOST_TO_NET_16 (peer -> outgoingUnsequencedGroup); break; - + default: break; } - enet_list_insert (enet_list_end (& peer -> outgoingCommands), outgoingCommand); + if ((outgoingCommand -> command.header.command & ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE) != 0 && + outgoingCommand -> packet != NULL) + enet_list_insert (enet_list_end (& peer -> outgoingSendReliableCommands), outgoingCommand); + else + enet_list_insert (enet_list_end (& peer -> outgoingCommands), outgoingCommand); } ENetOutgoingCommand * diff --git a/thirdparty/enet/protocol.c b/thirdparty/enet/protocol.c index d7fe80f117..af307af7e5 100644 --- a/thirdparty/enet/protocol.c +++ b/thirdparty/enet/protocol.c @@ -9,7 +9,7 @@ #include "enet/time.h" #include "enet/enet.h" -static size_t commandSizes [ENET_PROTOCOL_COMMAND_COUNT] = +static const size_t commandSizes [ENET_PROTOCOL_COMMAND_COUNT] = { 0, sizeof (ENetProtocolAcknowledge), @@ -159,16 +159,16 @@ enet_protocol_notify_disconnect (ENetHost * host, ENetPeer * peer, ENetEvent * e } static void -enet_protocol_remove_sent_unreliable_commands (ENetPeer * peer) +enet_protocol_remove_sent_unreliable_commands (ENetPeer * peer, ENetList * sentUnreliableCommands) { ENetOutgoingCommand * outgoingCommand; - if (enet_list_empty (& peer -> sentUnreliableCommands)) + if (enet_list_empty (sentUnreliableCommands)) return; do { - outgoingCommand = (ENetOutgoingCommand *) enet_list_front (& peer -> sentUnreliableCommands); + outgoingCommand = (ENetOutgoingCommand *) enet_list_front (sentUnreliableCommands); enet_list_remove (& outgoingCommand -> outgoingCommandList); @@ -185,14 +185,38 @@ enet_protocol_remove_sent_unreliable_commands (ENetPeer * peer) } enet_free (outgoingCommand); - } while (! enet_list_empty (& peer -> sentUnreliableCommands)); + } while (! enet_list_empty (sentUnreliableCommands)); if (peer -> state == ENET_PEER_STATE_DISCONNECT_LATER && - enet_list_empty (& peer -> outgoingCommands) && - enet_list_empty (& peer -> sentReliableCommands)) + ! enet_peer_has_outgoing_commands (peer)) enet_peer_disconnect (peer, peer -> eventData); } +static ENetOutgoingCommand * +enet_protocol_find_sent_reliable_command (ENetList * list, enet_uint16 reliableSequenceNumber, enet_uint8 channelID) +{ + ENetListIterator currentCommand; + + for (currentCommand = enet_list_begin (list); + currentCommand != enet_list_end (list); + currentCommand = enet_list_next (currentCommand)) + { + ENetOutgoingCommand * outgoingCommand = (ENetOutgoingCommand *) currentCommand; + + if (! (outgoingCommand -> command.header.command & ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE)) + continue; + + if (outgoingCommand -> sendAttempts < 1) + break; + + if (outgoingCommand -> reliableSequenceNumber == reliableSequenceNumber && + outgoingCommand -> command.header.channelID == channelID) + return outgoingCommand; + } + + return NULL; +} + static ENetProtocolCommand enet_protocol_remove_sent_reliable_command (ENetPeer * peer, enet_uint16 reliableSequenceNumber, enet_uint8 channelID) { @@ -214,24 +238,9 @@ enet_protocol_remove_sent_reliable_command (ENetPeer * peer, enet_uint16 reliabl if (currentCommand == enet_list_end (& peer -> sentReliableCommands)) { - for (currentCommand = enet_list_begin (& peer -> outgoingCommands); - currentCommand != enet_list_end (& peer -> outgoingCommands); - currentCommand = enet_list_next (currentCommand)) - { - outgoingCommand = (ENetOutgoingCommand *) currentCommand; - - if (! (outgoingCommand -> command.header.command & ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE)) - continue; - - if (outgoingCommand -> sendAttempts < 1) return ENET_PROTOCOL_COMMAND_NONE; - - if (outgoingCommand -> reliableSequenceNumber == reliableSequenceNumber && - outgoingCommand -> command.header.channelID == channelID) - break; - } - - if (currentCommand == enet_list_end (& peer -> outgoingCommands)) - return ENET_PROTOCOL_COMMAND_NONE; + outgoingCommand = enet_protocol_find_sent_reliable_command (& peer -> outgoingCommands, reliableSequenceNumber, channelID); + if (outgoingCommand == NULL) + outgoingCommand = enet_protocol_find_sent_reliable_command (& peer -> outgoingSendReliableCommands, reliableSequenceNumber, channelID); wasSent = 0; } @@ -331,6 +340,7 @@ enet_protocol_handle_connect (ENetHost * host, ENetProtocolHeader * header, ENet peer -> state = ENET_PEER_STATE_ACKNOWLEDGING_CONNECT; peer -> connectID = command -> connect.connectID; peer -> address = host -> receivedAddress; + peer -> mtu = host -> mtu; peer -> outgoingPeerID = ENET_NET_TO_HOST_16 (command -> connect.outgoingPeerID); peer -> incomingBandwidth = ENET_NET_TO_HOST_32 (command -> connect.incomingBandwidth); peer -> outgoingBandwidth = ENET_NET_TO_HOST_32 (command -> connect.outgoingBandwidth); @@ -375,7 +385,8 @@ enet_protocol_handle_connect (ENetHost * host, ENetProtocolHeader * header, ENet if (mtu > ENET_PROTOCOL_MAXIMUM_MTU) mtu = ENET_PROTOCOL_MAXIMUM_MTU; - peer -> mtu = mtu; + if (mtu < peer -> mtu) + peer -> mtu = mtu; if (host -> outgoingBandwidth == 0 && peer -> incomingBandwidth == 0) @@ -542,7 +553,8 @@ enet_protocol_handle_send_fragment (ENetHost * host, ENetPeer * peer, const ENet fragmentLength = ENET_NET_TO_HOST_16 (command -> sendFragment.dataLength); * currentData += fragmentLength; - if (fragmentLength > host -> maximumPacketSize || + if (fragmentLength <= 0 || + fragmentLength > host -> maximumPacketSize || * currentData < host -> receivedData || * currentData > & host -> receivedData [host -> receivedDataLength]) return -1; @@ -566,6 +578,7 @@ enet_protocol_handle_send_fragment (ENetHost * host, ENetPeer * peer, const ENet if (fragmentCount > ENET_PROTOCOL_MAXIMUM_FRAGMENT_COUNT || fragmentNumber >= fragmentCount || totalLength > host -> maximumPacketSize || + totalLength < fragmentCount || fragmentOffset >= totalLength || fragmentLength > totalLength - fragmentOffset) return -1; @@ -921,8 +934,7 @@ enet_protocol_handle_acknowledge (ENetHost * host, ENetEvent * event, ENetPeer * break; case ENET_PEER_STATE_DISCONNECT_LATER: - if (enet_list_empty (& peer -> outgoingCommands) && - enet_list_empty (& peer -> sentReliableCommands)) + if (! enet_peer_has_outgoing_commands (peer)) enet_peer_disconnect (peer, peer -> eventData); break; @@ -1230,6 +1242,9 @@ enet_protocol_receive_incoming_commands (ENetHost * host, ENetEvent * event) & buffer, 1); + if (receivedLength == -2) + continue; + if (receivedLength < 0) return -1; @@ -1293,7 +1308,7 @@ enet_protocol_send_acknowledgements (ENetHost * host, ENetPeer * peer) buffer >= & host -> buffers [sizeof (host -> buffers) / sizeof (ENetBuffer)] || peer -> mtu - host -> packetSize < sizeof (ENetProtocolAcknowledge)) { - host -> continueSending = 1; + peer -> flags |= ENET_PEER_FLAG_CONTINUE_SENDING; break; } @@ -1333,10 +1348,11 @@ static int enet_protocol_check_timeouts (ENetHost * host, ENetPeer * peer, ENetEvent * event) { ENetOutgoingCommand * outgoingCommand; - ENetListIterator currentCommand, insertPosition; + ENetListIterator currentCommand, insertPosition, insertSendReliablePosition; currentCommand = enet_list_begin (& peer -> sentReliableCommands); insertPosition = enet_list_begin (& peer -> outgoingCommands); + insertSendReliablePosition = enet_list_begin (& peer -> outgoingSendReliableCommands); while (currentCommand != enet_list_end (& peer -> sentReliableCommands)) { @@ -1353,7 +1369,7 @@ enet_protocol_check_timeouts (ENetHost * host, ENetPeer * peer, ENetEvent * even if (peer -> earliestTimeout != 0 && (ENET_TIME_DIFFERENCE (host -> serviceTime, peer -> earliestTimeout) >= peer -> timeoutMaximum || - (outgoingCommand -> roundTripTimeout >= outgoingCommand -> roundTripTimeoutLimit && + ((1 << (outgoingCommand -> sendAttempts - 1)) >= peer -> timeoutLimit && ENET_TIME_DIFFERENCE (host -> serviceTime, peer -> earliestTimeout) >= peer -> timeoutMinimum))) { enet_protocol_notify_disconnect (host, peer, event); @@ -1361,14 +1377,18 @@ enet_protocol_check_timeouts (ENetHost * host, ENetPeer * peer, ENetEvent * even return 1; } - if (outgoingCommand -> packet != NULL) - peer -> reliableDataInTransit -= outgoingCommand -> fragmentLength; - ++ peer -> packetsLost; outgoingCommand -> roundTripTimeout *= 2; - enet_list_insert (insertPosition, enet_list_remove (& outgoingCommand -> outgoingCommandList)); + if (outgoingCommand -> packet != NULL) + { + peer -> reliableDataInTransit -= outgoingCommand -> fragmentLength; + + enet_list_insert (insertSendReliablePosition, enet_list_remove (& outgoingCommand -> outgoingCommandList)); + } + else + enet_list_insert (insertPosition, enet_list_remove (& outgoingCommand -> outgoingCommandList)); if (currentCommand == enet_list_begin (& peer -> sentReliableCommands) && ! enet_list_empty (& peer -> sentReliableCommands)) @@ -1383,22 +1403,41 @@ enet_protocol_check_timeouts (ENetHost * host, ENetPeer * peer, ENetEvent * even } static int -enet_protocol_check_outgoing_commands (ENetHost * host, ENetPeer * peer) +enet_protocol_check_outgoing_commands (ENetHost * host, ENetPeer * peer, ENetList * sentUnreliableCommands) { ENetProtocol * command = & host -> commands [host -> commandCount]; ENetBuffer * buffer = & host -> buffers [host -> bufferCount]; ENetOutgoingCommand * outgoingCommand; - ENetListIterator currentCommand; - ENetChannel *channel; - enet_uint16 reliableWindow; + ENetListIterator currentCommand, currentSendReliableCommand; + ENetChannel *channel = NULL; + enet_uint16 reliableWindow = 0; size_t commandSize; - int windowExceeded = 0, windowWrap = 0, canPing = 1; + int windowWrap = 0, canPing = 1; currentCommand = enet_list_begin (& peer -> outgoingCommands); - - while (currentCommand != enet_list_end (& peer -> outgoingCommands)) + currentSendReliableCommand = enet_list_begin (& peer -> outgoingSendReliableCommands); + + for (;;) { - outgoingCommand = (ENetOutgoingCommand *) currentCommand; + if (currentCommand != enet_list_end (& peer -> outgoingCommands)) + { + outgoingCommand = (ENetOutgoingCommand *) currentCommand; + + if (currentSendReliableCommand != enet_list_end (& peer -> outgoingSendReliableCommands) && + ENET_TIME_LESS (((ENetOutgoingCommand *) currentSendReliableCommand) -> queueTime, outgoingCommand -> queueTime)) + goto useSendReliableCommand; + + currentCommand = enet_list_next (currentCommand); + } + else + if (currentSendReliableCommand != enet_list_end (& peer -> outgoingSendReliableCommands)) + { + useSendReliableCommand: + outgoingCommand = (ENetOutgoingCommand *) currentSendReliableCommand; + currentSendReliableCommand = enet_list_next (currentSendReliableCommand); + } + else + break; if (outgoingCommand -> command.header.command & ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE) { @@ -1406,33 +1445,29 @@ enet_protocol_check_outgoing_commands (ENetHost * host, ENetPeer * peer) reliableWindow = outgoingCommand -> reliableSequenceNumber / ENET_PEER_RELIABLE_WINDOW_SIZE; if (channel != NULL) { - if (! windowWrap && - outgoingCommand -> sendAttempts < 1 && + if (windowWrap) + continue; + else + if (outgoingCommand -> sendAttempts < 1 && ! (outgoingCommand -> reliableSequenceNumber % ENET_PEER_RELIABLE_WINDOW_SIZE) && (channel -> reliableWindows [(reliableWindow + ENET_PEER_RELIABLE_WINDOWS - 1) % ENET_PEER_RELIABLE_WINDOWS] >= ENET_PEER_RELIABLE_WINDOW_SIZE || channel -> usedReliableWindows & ((((1 << (ENET_PEER_FREE_RELIABLE_WINDOWS + 2)) - 1) << reliableWindow) | (((1 << (ENET_PEER_FREE_RELIABLE_WINDOWS + 2)) - 1) >> (ENET_PEER_RELIABLE_WINDOWS - reliableWindow))))) - windowWrap = 1; - if (windowWrap) { - currentCommand = enet_list_next (currentCommand); - + windowWrap = 1; + currentSendReliableCommand = enet_list_end (& peer -> outgoingSendReliableCommands); + continue; } } - + if (outgoingCommand -> packet != NULL) { - if (! windowExceeded) - { - enet_uint32 windowSize = (peer -> packetThrottle * peer -> windowSize) / ENET_PEER_PACKET_THROTTLE_SCALE; - - if (peer -> reliableDataInTransit + outgoingCommand -> fragmentLength > ENET_MAX (windowSize, peer -> mtu)) - windowExceeded = 1; - } - if (windowExceeded) + enet_uint32 windowSize = (peer -> packetThrottle * peer -> windowSize) / ENET_PEER_PACKET_THROTTLE_SCALE; + + if (peer -> reliableDataInTransit + outgoingCommand -> fragmentLength > ENET_MAX (windowSize, peer -> mtu)) { - currentCommand = enet_list_next (currentCommand); + currentSendReliableCommand = enet_list_end (& peer -> outgoingSendReliableCommands); continue; } @@ -1448,13 +1483,11 @@ enet_protocol_check_outgoing_commands (ENetHost * host, ENetPeer * peer) (outgoingCommand -> packet != NULL && (enet_uint16) (peer -> mtu - host -> packetSize) < (enet_uint16) (commandSize + outgoingCommand -> fragmentLength))) { - host -> continueSending = 1; - + peer -> flags |= ENET_PEER_FLAG_CONTINUE_SENDING; + break; } - currentCommand = enet_list_next (currentCommand); - if (outgoingCommand -> command.header.command & ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE) { if (channel != NULL && outgoingCommand -> sendAttempts < 1) @@ -1466,10 +1499,7 @@ enet_protocol_check_outgoing_commands (ENetHost * host, ENetPeer * peer) ++ outgoingCommand -> sendAttempts; if (outgoingCommand -> roundTripTimeout == 0) - { - outgoingCommand -> roundTripTimeout = peer -> roundTripTime + 4 * peer -> roundTripTimeVariance; - outgoingCommand -> roundTripTimeoutLimit = peer -> timeoutLimit * outgoingCommand -> roundTripTimeout; - } + outgoingCommand -> roundTripTimeout = peer -> roundTripTime + 4 * peer -> roundTripTimeVariance; if (enet_list_empty (& peer -> sentReliableCommands)) peer -> nextTimeout = host -> serviceTime + outgoingCommand -> roundTripTimeout; @@ -1522,7 +1552,7 @@ enet_protocol_check_outgoing_commands (ENetHost * host, ENetPeer * peer) enet_list_remove (& outgoingCommand -> outgoingCommandList); if (outgoingCommand -> packet != NULL) - enet_list_insert (enet_list_end (& peer -> sentUnreliableCommands), outgoingCommand); + enet_list_insert (enet_list_end (sentUnreliableCommands), outgoingCommand); } buffer -> data = command; @@ -1555,9 +1585,8 @@ enet_protocol_check_outgoing_commands (ENetHost * host, ENetPeer * peer) host -> bufferCount = buffer - host -> buffers; if (peer -> state == ENET_PEER_STATE_DISCONNECT_LATER && - enet_list_empty (& peer -> outgoingCommands) && - enet_list_empty (& peer -> sentReliableCommands) && - enet_list_empty (& peer -> sentUnreliableCommands)) + ! enet_peer_has_outgoing_commands (peer) && + enet_list_empty (sentUnreliableCommands)) enet_peer_disconnect (peer, peer -> eventData); return canPing; @@ -1568,22 +1597,24 @@ enet_protocol_send_outgoing_commands (ENetHost * host, ENetEvent * event, int ch { enet_uint8 headerData [sizeof (ENetProtocolHeader) + sizeof (enet_uint32)]; ENetProtocolHeader * header = (ENetProtocolHeader *) headerData; - ENetPeer * currentPeer; - int sentLength; + int sentLength = 0; size_t shouldCompress = 0; - - host -> continueSending = 1; + ENetList sentUnreliableCommands; - while (host -> continueSending) - for (host -> continueSending = 0, - currentPeer = host -> peers; + enet_list_clear (& sentUnreliableCommands); + + for (int sendPass = 0, continueSending = 0; sendPass <= continueSending; ++ sendPass) + for (ENetPeer * currentPeer = host -> peers; currentPeer < & host -> peers [host -> peerCount]; ++ currentPeer) { if (currentPeer -> state == ENET_PEER_STATE_DISCONNECTED || - currentPeer -> state == ENET_PEER_STATE_ZOMBIE) + currentPeer -> state == ENET_PEER_STATE_ZOMBIE || + (sendPass > 0 && ! (currentPeer -> flags & ENET_PEER_FLAG_CONTINUE_SENDING))) continue; + currentPeer -> flags &= ~ ENET_PEER_FLAG_CONTINUE_SENDING; + host -> headerFlags = 0; host -> commandCount = 0; host -> bufferCount = 1; @@ -1600,21 +1631,22 @@ enet_protocol_send_outgoing_commands (ENetHost * host, ENetEvent * event, int ch if (event != NULL && event -> type != ENET_EVENT_TYPE_NONE) return 1; else - continue; + goto nextPeer; } - if ((enet_list_empty (& currentPeer -> outgoingCommands) || - enet_protocol_check_outgoing_commands (host, currentPeer)) && + if (((enet_list_empty (& currentPeer -> outgoingCommands) && + enet_list_empty (& currentPeer -> outgoingSendReliableCommands)) || + enet_protocol_check_outgoing_commands (host, currentPeer, & sentUnreliableCommands)) && enet_list_empty (& currentPeer -> sentReliableCommands) && ENET_TIME_DIFFERENCE (host -> serviceTime, currentPeer -> lastReceiveTime) >= currentPeer -> pingInterval && currentPeer -> mtu - host -> packetSize >= sizeof (ENetProtocolPing)) { enet_peer_ping (currentPeer); - enet_protocol_check_outgoing_commands (host, currentPeer); + enet_protocol_check_outgoing_commands (host, currentPeer, & sentUnreliableCommands); } if (host -> commandCount == 0) - continue; + goto nextPeer; if (currentPeer -> packetLossEpoch == 0) currentPeer -> packetLossEpoch = host -> serviceTime; @@ -1625,7 +1657,7 @@ enet_protocol_send_outgoing_commands (ENetHost * host, ENetEvent * event, int ch enet_uint32 packetLoss = currentPeer -> packetsLost * ENET_PEER_PACKET_LOSS_SCALE / currentPeer -> packetsSent; #ifdef ENET_DEBUG - printf ("peer %u: %f%%+-%f%% packet loss, %u+-%u ms round trip time, %f%% throttle, %u outgoing, %u/%u incoming\n", currentPeer -> incomingPeerID, currentPeer -> packetLoss / (float) ENET_PEER_PACKET_LOSS_SCALE, currentPeer -> packetLossVariance / (float) ENET_PEER_PACKET_LOSS_SCALE, currentPeer -> roundTripTime, currentPeer -> roundTripTimeVariance, currentPeer -> packetThrottle / (float) ENET_PEER_PACKET_THROTTLE_SCALE, enet_list_size (& currentPeer -> outgoingCommands), currentPeer -> channels != NULL ? enet_list_size (& currentPeer -> channels -> incomingReliableCommands) : 0, currentPeer -> channels != NULL ? enet_list_size (& currentPeer -> channels -> incomingUnreliableCommands) : 0); + printf ("peer %u: %f%%+-%f%% packet loss, %u+-%u ms round trip time, %f%% throttle, %u outgoing, %u/%u incoming\n", currentPeer -> incomingPeerID, currentPeer -> packetLoss / (float) ENET_PEER_PACKET_LOSS_SCALE, currentPeer -> packetLossVariance / (float) ENET_PEER_PACKET_LOSS_SCALE, currentPeer -> roundTripTime, currentPeer -> roundTripTimeVariance, currentPeer -> packetThrottle / (float) ENET_PEER_PACKET_THROTTLE_SCALE, enet_list_size (& currentPeer -> outgoingCommands) + enet_list_size (& currentPeer -> outgoingSendReliableCommands), currentPeer -> channels != NULL ? enet_list_size (& currentPeer -> channels -> incomingReliableCommands) : 0, currentPeer -> channels != NULL ? enet_list_size (& currentPeer -> channels -> incomingUnreliableCommands) : 0); #endif currentPeer -> packetLossVariance = (currentPeer -> packetLossVariance * 3 + ENET_DIFFERENCE (packetLoss, currentPeer -> packetLoss)) / 4; @@ -1687,13 +1719,17 @@ enet_protocol_send_outgoing_commands (ENetHost * host, ENetEvent * event, int ch sentLength = enet_socket_send (host -> socket, & currentPeer -> address, host -> buffers, host -> bufferCount); - enet_protocol_remove_sent_unreliable_commands (currentPeer); + enet_protocol_remove_sent_unreliable_commands (currentPeer, & sentUnreliableCommands); if (sentLength < 0) return -1; host -> totalSentData += sentLength; host -> totalSentPackets ++; + + nextPeer: + if (currentPeer -> flags & ENET_PEER_FLAG_CONTINUE_SENDING) + continueSending = sendPass + 1; } return 0; |
