diff options
118 files changed, 3289 insertions, 2083 deletions
diff --git a/.github/workflows/web_builds.yml b/.github/workflows/web_builds.yml index d314991fee..9524b5260b 100644 --- a/.github/workflows/web_builds.yml +++ b/.github/workflows/web_builds.yml @@ -17,7 +17,24 @@ concurrency: jobs: web-template: runs-on: "ubuntu-22.04" - name: Template (target=template_release) + name: ${{ matrix.name }} + strategy: + fail-fast: false + matrix: + include: + - name: Template w/ threads (target=template_release, threads=yes) + cache-name: web-template + target: template_release + sconsflags: threads=yes + tests: false + artifact: true + + - name: Template w/o threads (target=template_release, threads=no) + cache-name: web-nothreads-template + target: template_release + sconsflags: threads=no + tests: false + artifact: true steps: - uses: actions/checkout@v4 @@ -34,6 +51,8 @@ jobs: - name: Setup Godot build cache uses: ./.github/actions/godot-cache + with: + cache-name: ${{ matrix.cache-name }} continue-on-error: true - name: Setup python and scons @@ -42,10 +61,13 @@ jobs: - name: Compilation uses: ./.github/actions/godot-build with: - sconsflags: ${{ env.SCONSFLAGS }} + sconsflags: ${{ env.SCONSFLAGS }} ${{ matrix.sconsflags }} platform: web - target: template_release - tests: false + target: ${{ matrix.target }} + tests: ${{ matrix.tests }} - name: Upload artifact uses: ./.github/actions/upload-artifact + if: ${{ matrix.artifact }} + with: + name: ${{ matrix.cache-name }} diff --git a/SConstruct b/SConstruct index c7b9d5bc86..6a4dea2c09 100644 --- a/SConstruct +++ b/SConstruct @@ -183,6 +183,7 @@ opts.Add(BoolVariable("separate_debug_symbols", "Extract debugging symbols to a opts.Add(EnumVariable("lto", "Link-time optimization (production builds)", "none", ("none", "auto", "thin", "full"))) opts.Add(BoolVariable("production", "Set defaults to build Godot for use in production", False)) opts.Add(BoolVariable("generate_apk", "Generate an APK/AAB after building Android library by calling Gradle", False)) +opts.Add(BoolVariable("threads", "Enable threading support", True)) # Components opts.Add(BoolVariable("deprecated", "Enable compatibility code for deprecated and removed features", True)) @@ -832,6 +833,10 @@ if selected_platform in platform_list: suffix += ".double" suffix += "." + env["arch"] + + if not env["threads"]: + suffix += ".nothreads" + suffix += env.extra_suffix sys.path.remove(tmppath) @@ -972,6 +977,9 @@ if selected_platform in platform_list: env.Tool("compilation_db") env.Alias("compiledb", env.CompilationDatabase()) + if env["threads"]: + env.Append(CPPDEFINES=["THREADS_ENABLED"]) + Export("env") # Build subdirs, the build order is dependent on link order. diff --git a/core/debugger/remote_debugger.cpp b/core/debugger/remote_debugger.cpp index ce675d6b06..d3b0039e72 100644 --- a/core/debugger/remote_debugger.cpp +++ b/core/debugger/remote_debugger.cpp @@ -36,6 +36,7 @@ #include "core/debugger/engine_profiler.h" #include "core/debugger/script_debugger.h" #include "core/input/input.h" +#include "core/io/resource_loader.h" #include "core/object/script_language.h" #include "core/os/os.h" @@ -513,8 +514,9 @@ void RemoteDebugger::debug(bool p_can_continue, bool p_is_error_breakpoint) { _send_stack_vars(globals, globals_vals, 2); } else if (command == "reload_scripts") { + script_paths_to_reload = data; + } else if (command == "reload_all_scripts") { reload_all_scripts = true; - } else if (command == "breakpoint") { ERR_FAIL_COND(data.size() < 3); bool set = data[2]; @@ -589,19 +591,36 @@ void RemoteDebugger::poll_events(bool p_is_idle) { } // Reload scripts during idle poll only. - if (p_is_idle && reload_all_scripts) { - for (int i = 0; i < ScriptServer::get_language_count(); i++) { - ScriptServer::get_language(i)->reload_all_scripts(); + if (p_is_idle) { + if (reload_all_scripts) { + for (int i = 0; i < ScriptServer::get_language_count(); i++) { + ScriptServer::get_language(i)->reload_all_scripts(); + } + reload_all_scripts = false; + } else if (!script_paths_to_reload.is_empty()) { + Array scripts_to_reload; + for (int i = 0; i < script_paths_to_reload.size(); ++i) { + String path = script_paths_to_reload[i]; + Error err = OK; + Ref<Script> script = ResourceLoader::load(path, "", ResourceFormatLoader::CACHE_MODE_REUSE, &err); + ERR_CONTINUE_MSG(err != OK, vformat("Could not reload script '%s': %s", path, error_names[err])); + ERR_CONTINUE_MSG(script.is_null(), vformat("Could not reload script '%s': Not a script!", path, error_names[err])); + scripts_to_reload.push_back(script); + } + for (int i = 0; i < ScriptServer::get_language_count(); i++) { + ScriptServer::get_language(i)->reload_scripts(scripts_to_reload, true); + } } - reload_all_scripts = false; + script_paths_to_reload.clear(); } } Error RemoteDebugger::_core_capture(const String &p_cmd, const Array &p_data, bool &r_captured) { r_captured = true; if (p_cmd == "reload_scripts") { + script_paths_to_reload = p_data; + } else if (p_cmd == "reload_all_scripts") { reload_all_scripts = true; - } else if (p_cmd == "breakpoint") { ERR_FAIL_COND_V(p_data.size() < 3, ERR_INVALID_DATA); bool set = p_data[2]; diff --git a/core/debugger/remote_debugger.h b/core/debugger/remote_debugger.h index 7c399178c6..519a90e7cc 100644 --- a/core/debugger/remote_debugger.h +++ b/core/debugger/remote_debugger.h @@ -74,6 +74,7 @@ private: int warn_count = 0; int last_reset = 0; bool reload_all_scripts = false; + Array script_paths_to_reload; // Make handlers and send_message thread safe. Mutex mutex; diff --git a/core/input/input_builders.py b/core/input/input_builders.py index e98e2441e2..94c566493e 100644 --- a/core/input/input_builders.py +++ b/core/input/input_builders.py @@ -45,10 +45,10 @@ def make_default_controller_mappings(target, source, env): platform_mappings[current_platform][guid] = line platform_variables = { - "Linux": "#if LINUXBSD_ENABLED", + "Linux": "#ifdef LINUXBSD_ENABLED", "Windows": "#ifdef WINDOWS_ENABLED", "Mac OS X": "#ifdef MACOS_ENABLED", - "Android": "#if defined(__ANDROID__)", + "Android": "#ifdef ANDROID_ENABLED", "iOS": "#ifdef IOS_ENABLED", "Web": "#ifdef WEB_ENABLED", } diff --git a/core/object/script_language.h b/core/object/script_language.h index 66106bf139..bb714d5bc3 100644 --- a/core/object/script_language.h +++ b/core/object/script_language.h @@ -371,6 +371,7 @@ public: virtual Vector<StackInfo> debug_get_current_stack_info() { return Vector<StackInfo>(); } virtual void reload_all_scripts() = 0; + virtual void reload_scripts(const Array &p_scripts, bool p_soft_reload) = 0; virtual void reload_tool_script(const Ref<Script> &p_script, bool p_soft_reload) = 0; /* LOADER FUNCTIONS */ diff --git a/core/object/script_language_extension.h b/core/object/script_language_extension.h index 8b01667519..5b10739486 100644 --- a/core/object/script_language_extension.h +++ b/core/object/script_language_extension.h @@ -562,6 +562,7 @@ public: } EXBIND0(reload_all_scripts) + EXBIND2(reload_scripts, const Array &, bool) EXBIND2(reload_tool_script, const Ref<Script> &, bool) /* LOADER FUNCTIONS */ diff --git a/core/object/worker_thread_pool.cpp b/core/object/worker_thread_pool.cpp index 8e8a2ef06b..e2ab473b01 100644 --- a/core/object/worker_thread_pool.cpp +++ b/core/object/worker_thread_pool.cpp @@ -47,6 +47,7 @@ WorkerThreadPool *WorkerThreadPool::singleton = nullptr; thread_local CommandQueueMT *WorkerThreadPool::flushing_cmd_queue = nullptr; void WorkerThreadPool::_process_task(Task *p_task) { +#ifdef THREADS_ENABLED int pool_thread_index = thread_ids[Thread::get_caller_id()]; ThreadData &curr_thread = threads[pool_thread_index]; Task *prev_task = nullptr; // In case this is recursively called. @@ -69,6 +70,7 @@ void WorkerThreadPool::_process_task(Task *p_task) { curr_thread.current_task = p_task; task_mutex.unlock(); } +#endif if (p_task->group) { // Handling a group @@ -143,6 +145,7 @@ void WorkerThreadPool::_process_task(Task *p_task) { } } +#ifdef THREADS_ENABLED { curr_thread.current_task = prev_task; if (p_task->low_priority) { @@ -159,6 +162,7 @@ void WorkerThreadPool::_process_task(Task *p_task) { } set_current_thread_safe_for_nodes(safe_for_nodes_backup); +#endif } void WorkerThreadPool::_thread_function(void *p_user) { @@ -542,6 +546,7 @@ bool WorkerThreadPool::is_group_task_completed(GroupID p_group) const { } void WorkerThreadPool::wait_for_group_task_completion(GroupID p_group) { +#ifdef THREADS_ENABLED task_mutex.lock(); Group **groupp = groups.getptr(p_group); task_mutex.unlock(); @@ -574,6 +579,7 @@ void WorkerThreadPool::wait_for_group_task_completion(GroupID p_group) { task_mutex.lock(); // This mutex is needed when Physics 2D and/or 3D is selected to run on a separate thread. groups.erase(p_group); task_mutex.unlock(); +#endif } int WorkerThreadPool::get_thread_index() { diff --git a/core/os/condition_variable.h b/core/os/condition_variable.h index 6a49ced31b..2b6b272e18 100644 --- a/core/os/condition_variable.h +++ b/core/os/condition_variable.h @@ -33,6 +33,8 @@ #include "core/os/mutex.h" +#ifdef THREADS_ENABLED + #ifdef MINGW_ENABLED #define MINGW_STDTHREAD_REDUNDANCY_WARNING #include "thirdparty/mingw-std-threads/mingw.condition_variable.h" @@ -66,4 +68,16 @@ public: } }; +#else // No threads. + +class ConditionVariable { +public: + template <class BinaryMutexT> + void wait(const MutexLock<BinaryMutexT> &p_lock) const {} + void notify_one() const {} + void notify_all() const {} +}; + +#endif // THREADS_ENABLED + #endif // CONDITION_VARIABLE_H diff --git a/core/os/mutex.cpp b/core/os/mutex.cpp index 5d4e457c5f..9a8a2a2961 100644 --- a/core/os/mutex.cpp +++ b/core/os/mutex.cpp @@ -40,7 +40,11 @@ void _global_unlock() { _global_mutex.unlock(); } +#ifdef THREADS_ENABLED + template class MutexImpl<THREADING_NAMESPACE::recursive_mutex>; template class MutexImpl<THREADING_NAMESPACE::mutex>; template class MutexLock<MutexImpl<THREADING_NAMESPACE::recursive_mutex>>; template class MutexLock<MutexImpl<THREADING_NAMESPACE::mutex>>; + +#endif diff --git a/core/os/mutex.h b/core/os/mutex.h index 03af48ca7c..69f494d9cd 100644 --- a/core/os/mutex.h +++ b/core/os/mutex.h @@ -43,6 +43,8 @@ #define THREADING_NAMESPACE std #endif +#ifdef THREADS_ENABLED + template <class MutexT> class MutexLock; @@ -125,8 +127,8 @@ class MutexLock { THREADING_NAMESPACE::unique_lock<typename MutexT::StdMutexType> lock; public: - _ALWAYS_INLINE_ explicit MutexLock(const MutexT &p_mutex) : - lock(p_mutex.mutex){}; + explicit MutexLock(const MutexT &p_mutex) : + lock(p_mutex.mutex) {} }; // This specialization is needed so manual locking and MutexLock can be used @@ -155,4 +157,38 @@ extern template class MutexImpl<THREADING_NAMESPACE::mutex>; extern template class MutexLock<MutexImpl<THREADING_NAMESPACE::recursive_mutex>>; extern template class MutexLock<MutexImpl<THREADING_NAMESPACE::mutex>>; +#else // No threads. + +class MutexImpl { + mutable THREADING_NAMESPACE::mutex mutex; + +public: + void lock() const {} + void unlock() const {} + bool try_lock() const { return true; } +}; + +template <int Tag> +class SafeBinaryMutex : public MutexImpl { + static thread_local uint32_t count; +}; + +template <class MutexT> +class MutexLock { +public: + MutexLock(const MutexT &p_mutex) {} +}; + +template <int Tag> +class MutexLock<SafeBinaryMutex<Tag>> { +public: + MutexLock(const SafeBinaryMutex<Tag> &p_mutex) {} + ~MutexLock() {} +}; + +using Mutex = MutexImpl; +using BinaryMutex = MutexImpl; + +#endif // THREADS_ENABLED + #endif // MUTEX_H diff --git a/core/os/os.cpp b/core/os/os.cpp index 26ae286979..d5d9988cc1 100644 --- a/core/os/os.cpp +++ b/core/os/os.cpp @@ -504,6 +504,12 @@ bool OS::has_feature(const String &p_feature) { } #endif +#ifdef THREADS_ENABLED + if (p_feature == "threads") { + return true; + } +#endif + if (_check_internal_feature_support(p_feature)) { return true; } diff --git a/core/os/semaphore.h b/core/os/semaphore.h index b8ae35b86b..19ef1dedc0 100644 --- a/core/os/semaphore.h +++ b/core/os/semaphore.h @@ -31,6 +31,10 @@ #ifndef SEMAPHORE_H #define SEMAPHORE_H +#include <cstdint> + +#ifdef THREADS_ENABLED + #include "core/error/error_list.h" #include "core/typedefs.h" #ifdef DEBUG_ENABLED @@ -132,4 +136,17 @@ public: #endif }; +#else // No threads. + +class Semaphore { +public: + void post(uint32_t p_count = 1) const {} + void wait() const {} + bool try_wait() const { + return true; + } +}; + +#endif // THREADS_ENABLED + #endif // SEMAPHORE_H diff --git a/core/os/thread.cpp b/core/os/thread.cpp index 2ba90ba42c..afc74364f6 100644 --- a/core/os/thread.cpp +++ b/core/os/thread.cpp @@ -33,19 +33,22 @@ #include "thread.h" +#ifdef THREADS_ENABLED #include "core/object/script_language.h" #include "core/templates/safe_refcount.h" -Thread::PlatformFunctions Thread::platform_functions; - SafeNumeric<uint64_t> Thread::id_counter(1); // The first value after .increment() is 2, hence by default the main thread ID should be 1. thread_local Thread::ID Thread::caller_id = Thread::UNASSIGNED_ID; +#endif + +Thread::PlatformFunctions Thread::platform_functions; void Thread::_set_platform_functions(const PlatformFunctions &p_functions) { platform_functions = p_functions; } +#ifdef THREADS_ENABLED void Thread::callback(ID p_caller_id, const Settings &p_settings, Callback p_callback, void *p_userdata) { Thread::caller_id = p_caller_id; if (platform_functions.set_priority) { @@ -107,4 +110,6 @@ Thread::~Thread() { } } +#endif // THREADS_ENABLED + #endif // PLATFORM_THREAD_OVERRIDE diff --git a/core/os/thread.h b/core/os/thread.h index cc954678f9..a0ecc24c91 100644 --- a/core/os/thread.h +++ b/core/os/thread.h @@ -53,6 +53,8 @@ class String; +#ifdef THREADS_ENABLED + class Thread { public: typedef void (*Callback)(void *p_userdata); @@ -86,6 +88,8 @@ public: private: friend class Main; + static PlatformFunctions platform_functions; + ID id = UNASSIGNED_ID; static SafeNumeric<uint64_t> id_counter; static thread_local ID caller_id; @@ -93,8 +97,6 @@ private: static void callback(ID p_caller_id, const Settings &p_settings, Thread::Callback p_callback, void *p_userdata); - static PlatformFunctions platform_functions; - static void make_main_thread() { caller_id = MAIN_ID; } static void release_main_thread() { caller_id = UNASSIGNED_ID; } @@ -125,6 +127,64 @@ public: ~Thread(); }; +#else // No threads. + +class Thread { +public: + typedef void (*Callback)(void *p_userdata); + + typedef uint64_t ID; + + enum : ID { + UNASSIGNED_ID = 0, + MAIN_ID = 1 + }; + + enum Priority { + PRIORITY_LOW, + PRIORITY_NORMAL, + PRIORITY_HIGH + }; + + struct Settings { + Priority priority; + Settings() { priority = PRIORITY_NORMAL; } + }; + + struct PlatformFunctions { + Error (*set_name)(const String &) = nullptr; + void (*set_priority)(Thread::Priority) = nullptr; + void (*init)() = nullptr; + void (*wrapper)(Thread::Callback, void *) = nullptr; + void (*term)() = nullptr; + }; + +private: + friend class Main; + + static PlatformFunctions platform_functions; + + static void make_main_thread() {} + static void release_main_thread() {} + +public: + static void _set_platform_functions(const PlatformFunctions &p_functions); + + _FORCE_INLINE_ ID get_id() const { return 0; } + _FORCE_INLINE_ static ID get_caller_id() { return MAIN_ID; } + _FORCE_INLINE_ static ID get_main_id() { return MAIN_ID; } + + _FORCE_INLINE_ static bool is_main_thread() { return true; } + + static Error set_name(const String &p_name) { return ERR_UNAVAILABLE; } + + void start(Thread::Callback p_callback, void *p_user, const Settings &p_settings = Settings()) {} + bool is_started() const { return false; } + void wait_to_finish() {} +}; + +#endif // THREADS_ENABLED + #endif // THREAD_H #endif // PLATFORM_THREAD_OVERRIDE diff --git a/doc/classes/AABB.xml b/doc/classes/AABB.xml index c1c637d2d6..427d38d421 100644 --- a/doc/classes/AABB.xml +++ b/doc/classes/AABB.xml @@ -4,10 +4,10 @@ A 3D axis-aligned bounding box. </brief_description> <description> - [AABB] consists of a position, a size, and several utility functions. It is typically used for fast overlap tests. - It uses floating-point coordinates. The 2D counterpart to [AABB] is [Rect2]. - Negative values for [member size] are not supported and will not work for most methods. Use [method abs] to get an AABB with a positive size. - [b]Note:[/b] Unlike [Rect2], [AABB] does not have a variant that uses integer coordinates. + The [AABB] built-in [Variant] type represents an axis-aligned bounding box in a 3D space. It is defined by its [member position] and [member size], which are [Vector3]. It is frequently used for fast overlap tests (see [method intersects]). Although [AABB] itself is axis-aligned, it can be combined with [Transform3D] to represent a rotated or skewed bounding box. + It uses floating-point coordinates. The 2D counterpart to [AABB] is [Rect2]. There is no version of [AABB] that uses integer coordinates. + [b]Note:[/b] Negative values for [member size] are not supported. With negative size, most [AABB] methods do not work correctly. Use [method abs] to get an equivalent [AABB] with a non-negative size. + [b]Note:[/b] In a boolean context, a [AABB] evaluates to [code]false[/code] if both [member position] and [member size] are zero (equal to [constant Vector3.ZERO]). Otherwise, it always evaluates to [code]true[/code]. </description> <tutorials> <link title="Math documentation index">$DOCS_URL/tutorials/math/index.html</link> @@ -18,7 +18,7 @@ <constructor name="AABB"> <return type="AABB" /> <description> - Constructs a default-initialized [AABB] with default (zero) values of [member position] and [member size]. + Constructs an [AABB] with its [member position] and [member size] set to [constant Vector3.ZERO]. </description> </constructor> <constructor name="AABB"> @@ -33,7 +33,7 @@ <param index="0" name="position" type="Vector3" /> <param index="1" name="size" type="Vector3" /> <description> - Constructs an [AABB] from a position and size. + Constructs an [AABB] by [param position] and [param size]. </description> </constructor> </constructors> @@ -41,34 +41,78 @@ <method name="abs" qualifiers="const"> <return type="AABB" /> <description> - Returns an AABB with equivalent position and size, modified so that the most-negative corner is the origin and the size is positive. + Returns an [AABB] equivalent to this bounding box, with its width, height, and depth modified to be non-negative values. + [codeblocks] + [gdscript] + var box = AABB(Vector3(5, 0, 5), Vector3(-20, -10, -5)) + var absolute = box.abs() + print(absolute.position) # Prints (-15, -10, 0) + print(absolute.size) # Prints (20, 10, 5) + [/gdscript] + [csharp] + var box = new Aabb(new Vector3(5, 0, 5), new Vector3(-20, -10, -5)); + var absolute = box.Abs(); + GD.Print(absolute.Position); // Prints (-15, -10, 0) + GD.Print(absolute.Size); // Prints (20, 10, 5) + [/csharp] + [/codeblocks] + [b]Note:[/b] It's recommended to use this method when [member size] is negative, as most other methods in Godot assume that the [member size]'s components are greater than [code]0[/code]. </description> </method> <method name="encloses" qualifiers="const"> <return type="bool" /> <param index="0" name="with" type="AABB" /> <description> - Returns [code]true[/code] if this [AABB] completely encloses another one. + Returns [code]true[/code] if this bounding box [i]completely[/i] encloses the [param with] box. The edges of both boxes are included. + [codeblocks] + [gdscript] + var a = AABB(Vector3(0, 0, 0), Vector3(4, 4, 4)) + var b = AABB(Vector3(1, 1, 1), Vector3(3, 3, 3)) + var c = AABB(Vector3(2, 2, 2), Vector3(8, 8, 8)) + + print(a.encloses(a)) # Prints true + print(a.encloses(b)) # Prints true + print(a.encloses(c)) # Prints false + [/gdscript] + [csharp] + var a = new Aabb(new Vector3(0, 0, 0), new Vector3(4, 4, 4)); + var b = new Aabb(new Vector3(1, 1, 1), new Vector3(3, 3, 3)); + var c = new Aabb(new Vector3(2, 2, 2), new Vector3(8, 8, 8)); + + GD.Print(a.Encloses(a)); // Prints True + GD.Print(a.Encloses(b)); // Prints True + GD.Print(a.Encloses(c)); // Prints False + [/csharp] + [/codeblocks] </description> </method> <method name="expand" qualifiers="const"> <return type="AABB" /> <param index="0" name="to_point" type="Vector3" /> <description> - Returns a copy of this [AABB] expanded to include a given point. - [b]Example:[/b] + Returns a copy of this bounding box expanded to align the edges with the given [param to_point], if necessary. [codeblocks] [gdscript] - # position (-3, 2, 0), size (1, 1, 1) - var box = AABB(Vector3(-3, 2, 0), Vector3(1, 1, 1)) - # position (-3, -1, 0), size (3, 4, 2), so we fit both the original AABB and Vector3(0, -1, 2) - var box2 = box.expand(Vector3(0, -1, 2)) + var box = AABB(Vector3(0, 0, 0), Vector3(5, 2, 5)) + + box = box.expand(Vector3(10, 0, 0)) + print(box.position) # Prints (0, 0, 0) + print(box.size) # Prints (10, 2, 5) + + box = box.expand(Vector3(-5, 0, 5)) + print(box.position) # Prints (-5, 0, 0) + print(box.size) # Prints (15, 2, 5) [/gdscript] [csharp] - // position (-3, 2, 0), size (1, 1, 1) - var box = new Aabb(new Vector3(-3, 2, 0), new Vector3(1, 1, 1)); - // position (-3, -1, 0), size (3, 4, 2), so we fit both the original AABB and Vector3(0, -1, 2) - var box2 = box.Expand(new Vector3(0, -1, 2)); + var box = new Aabb(new Vector3(0, 0, 0), new Vector3(5, 2, 5)); + + box = box.Expand(new Vector3(10, 0, 0)); + GD.Print(box.Position); // Prints (0, 0, 0) + GD.Print(box.Size); // Prints (10, 2, 5) + + box = box.Expand(new Vector3(-5, 0, 5)); + GD.Print(box.Position); // Prints (-5, 0, 0) + GD.Print(box.Size); // Prints (15, 2, 5) [/csharp] [/codeblocks] </description> @@ -76,111 +120,188 @@ <method name="get_center" qualifiers="const"> <return type="Vector3" /> <description> - Returns the center of the [AABB], which is equal to [member position] + ([member size] / 2). + Returns the center point of the bounding box. This is the same as [code]position + (size / 2.0)[/code]. </description> </method> <method name="get_endpoint" qualifiers="const"> <return type="Vector3" /> <param index="0" name="idx" type="int" /> <description> - Gets the position of the 8 endpoints of the [AABB] in space. + Returns the position of one of the 8 vertices that compose this bounding box. With a [param idx] of [code]0[/code] this is the same as [member position], and a [param idx] of [code]7[/code] is the same as [member end]. </description> </method> <method name="get_longest_axis" qualifiers="const"> <return type="Vector3" /> <description> - Returns the normalized longest axis of the [AABB]. + Returns the longest normalized axis of this bounding box's [member size], as a [Vector3] ([constant Vector3.RIGHT], [constant Vector3.UP], or [constant Vector3.BACK]). + [codeblocks] + [gdscript] + var box = AABB(Vector3(0, 0, 0), Vector3(2, 4, 8)) + + print(box.get_longest_axis()) # Prints (0, 0, 1) + print(box.get_longest_axis_index()) # Prints 2 + print(box.get_longest_axis_size()) # Prints 8 + [/gdscript] + [csharp] + var box = new Aabb(new Vector3(0, 0, 0), new Vector3(2, 4, 8)); + + GD.Print(box.GetLongestAxis()); // Prints (0, 0, 1) + GD.Print(box.GetLongestAxisIndex()); // Prints 2 + GD.Print(box.GetLongestAxisSize()); // Prints 8 + [/csharp] + [/codeblocks] + See also [method get_longest_axis_index] and [method get_longest_axis_size]. </description> </method> <method name="get_longest_axis_index" qualifiers="const"> <return type="int" /> <description> - Returns the index of the longest axis of the [AABB] (according to [Vector3]'s [code]AXIS_*[/code] constants). + Returns the index to the longest axis of this bounding box's [member size] (see [constant Vector3.AXIS_X], [constant Vector3.AXIS_Y], and [constant Vector3.AXIS_Z]). + For an example, see [method get_longest_axis]. </description> </method> <method name="get_longest_axis_size" qualifiers="const"> <return type="float" /> <description> - Returns the scalar length of the longest axis of the [AABB]. + Returns the longest dimension of this bounding box's [member size]. + For an example, see [method get_longest_axis]. </description> </method> <method name="get_shortest_axis" qualifiers="const"> <return type="Vector3" /> <description> - Returns the normalized shortest axis of the [AABB]. + Returns the shortest normaalized axis of this bounding box's [member size], as a [Vector3] ([constant Vector3.RIGHT], [constant Vector3.UP], or [constant Vector3.BACK]). + [codeblocks] + [gdscript] + var box = AABB(Vector3(0, 0, 0), Vector3(2, 4, 8)) + + print(box.get_shortest_axis()) # Prints (1, 0, 0) + print(box.get_shortest_axis_index()) # Prints 0 + print(box.get_shortest_axis_size()) # Prints 2 + [/gdscript] + [csharp] + var box = new Aabb(new Vector3(0, 0, 0), new Vector3(2, 4, 8)); + + GD.Print(box.GetShortestAxis()); // Prints (1, 0, 0) + GD.Print(box.GetShortestAxisIndex()); // Prints 0 + GD.Print(box.GetShortestAxisSize()); // Prints 2 + [/csharp] + [/codeblocks] + See also [method get_shortest_axis_index] and [method get_shortest_axis_size]. </description> </method> <method name="get_shortest_axis_index" qualifiers="const"> <return type="int" /> <description> - Returns the index of the shortest axis of the [AABB] (according to [Vector3]::AXIS* enum). + Returns the index to the shortest axis of this bounding box's [member size] (see [constant Vector3.AXIS_X], [constant Vector3.AXIS_Y], and [constant Vector3.AXIS_Z]). + For an example, see [method get_shortest_axis]. </description> </method> <method name="get_shortest_axis_size" qualifiers="const"> <return type="float" /> <description> - Returns the scalar length of the shortest axis of the [AABB]. + Returns the shortest dimension of this bounding box's [member size]. + For an example, see [method get_shortest_axis]. </description> </method> <method name="get_support" qualifiers="const"> <return type="Vector3" /> <param index="0" name="dir" type="Vector3" /> <description> - Returns the vertex of the AABB that's the farthest in a given direction. This point is commonly known as the support point in collision detection algorithms. + Returns the vertex's position of this bounding box that's the farthest in the given direction. This point is commonly known as the support point in collision detection algorithms. </description> </method> <method name="get_volume" qualifiers="const"> <return type="float" /> <description> - Returns the volume of the [AABB]. + Returns the bounding box's volume. This is equivalent to [code]size.x * size.y * size.z[/code]. See also [method has_volume]. </description> </method> <method name="grow" qualifiers="const"> <return type="AABB" /> <param index="0" name="by" type="float" /> <description> - Returns a copy of the [AABB] grown a given number of units towards all the sides. + Returns a copy of this bounding box extended on all sides by the given amount [param by]. A negative amount shrinks the box instead. + [codeblocks] + [gdscript] + var a = AABB(Vector3(4, 4, 4), Vector3(8, 8, 8)).grow(4) + print(a.position) # Prints (0, 0, 0) + print(a.size) # Prints (16, 16, 16) + + var b = AABB(Vector3(0, 0, 0), Vector3(8, 4, 2)).grow(2) + print(b.position) # Prints (-2, -2, -2) + print(b.size) # Prints (12, 8, 6) + [/gdscript] + [csharp] + var a = new Aabb(new Vector3(4, 4, 4), new Vector3(8, 8, 8)).Grow(4); + GD.Print(a.Position); // Prints (0, 0, 0) + GD.Print(a.Size); // Prints (16, 16, 16) + + var b = new Aabb(new Vector3(0, 0, 0), new Vector3(8, 4, 2)).Grow(2); + GD.Print(b.Position); // Prints (-2, -2, -2) + GD.Print(b.Size); // Prints (12, 8, 6) + [/csharp] + [/codeblocks] </description> </method> <method name="has_point" qualifiers="const"> <return type="bool" /> <param index="0" name="point" type="Vector3" /> <description> - Returns [code]true[/code] if the [AABB] contains a point. Points on the faces of the AABB are considered included, though float-point precision errors may impact the accuracy of such checks. - [b]Note:[/b] This method is not reliable for [AABB] with a [i]negative size[/i]. Use [method abs] to get a positive sized equivalent [AABB] to check for contained points. + Returns [code]true[/code] if the bounding box contains the given [param point]. By convention, points exactly on the right, top, and front sides are [b]not[/b] included. + [b]Note:[/b] This method is not reliable for [AABB] with a [i]negative[/i] [member size]. Use [method abs] first to get a valid bounding box. </description> </method> <method name="has_surface" qualifiers="const"> <return type="bool" /> <description> - Returns [code]true[/code] if the [AABB] has a surface or a length, and [code]false[/code] if the [AABB] is empty (all components of [member size] are zero or negative). + Returns [code]true[/code] if this bounding box has a surface or a length, that is, at least one component of [member size] is greater than [code]0[/code]. Otherwise, returns [code]false[/code]. </description> </method> <method name="has_volume" qualifiers="const"> <return type="bool" /> <description> - Returns [code]true[/code] if the [AABB] has a volume, and [code]false[/code] if the [AABB] is flat, empty, or has a negative [member size]. + Returns [code]true[/code] if this bounding box's width, height, and depth are all positive. See also [method get_volume]. </description> </method> <method name="intersection" qualifiers="const"> <return type="AABB" /> <param index="0" name="with" type="AABB" /> <description> - Returns the intersection between two [AABB]. An empty AABB (size [code](0, 0, 0)[/code]) is returned on failure. + Returns the intersection between this bounding box and [param with]. If the boxes do not intersect, returns an empty [AABB]. If the boxes intersect at the edge, returns a flat [AABB] with no volume (see [method has_surface] and [method has_volume]). + [codeblocks] + [gdscript] + var box1 = AABB(Vector3(0, 0, 0), Vector3(5, 2, 8)) + var box2 = AABB(Vector3(2, 0, 2), Vector3(8, 4, 4)) + + var intersection = box1.intersection(box2) + print(intersection.position) # Prints (2, 0, 2) + print(intersection.size) # Prints (3, 2, 4) + [/gdscript] + [csharp] + var box1 = new Aabb(new Vector3(0, 0, 0), new Vector3(5, 2, 8)); + var box2 = new Aabb(new Vector3(2, 0, 2), new Vector3(8, 4, 4)); + + var intersection = box1.Intersection(box2); + GD.Print(intersection.Position); // Prints (2, 0, 2) + GD.Print(intersection.Size); // Prints (3, 2, 4) + [/csharp] + [/codeblocks] + [b]Note:[/b] If you only need to know whether two bounding boxes are intersecting, use [method intersects], instead. </description> </method> <method name="intersects" qualifiers="const"> <return type="bool" /> <param index="0" name="with" type="AABB" /> <description> - Returns [code]true[/code] if the [AABB] overlaps with another. + Returns [code]true[/code] if this bounding box overlaps with the box [param with]. The edges of both boxes are [i]always[/i] excluded. </description> </method> <method name="intersects_plane" qualifiers="const"> <return type="bool" /> <param index="0" name="plane" type="Plane" /> <description> - Returns [code]true[/code] if the [AABB] is on both sides of a plane. + Returns [code]true[/code] if this bounding box is on both sides of the given [param plane]. </description> </method> <method name="intersects_ray" qualifiers="const"> @@ -188,7 +309,8 @@ <param index="0" name="from" type="Vector3" /> <param index="1" name="dir" type="Vector3" /> <description> - Returns the point of intersection of the given ray with this [AABB] or [code]null[/code] if there is no intersection. Ray length is infinite. + Returns the first point where this bounding box and the given ray intersect, as a [Vector3]. If no intersection occurs, returns [code]null[/code]. + The ray begin at [param from], faces [param dir] and extends towards infinity. </description> </method> <method name="intersects_segment" qualifiers="const"> @@ -196,40 +318,41 @@ <param index="0" name="from" type="Vector3" /> <param index="1" name="to" type="Vector3" /> <description> - Returns the point of intersection between [param from] and [param to] with this [AABB] or [code]null[/code] if there is no intersection. + Returns the first point where this bounding box and the given segment intersect, as a [Vector3]. If no intersection occurs, returns [code]null[/code]. + The segment begins at [param from] and ends at [param to]. </description> </method> <method name="is_equal_approx" qualifiers="const"> <return type="bool" /> <param index="0" name="aabb" type="AABB" /> <description> - Returns [code]true[/code] if this [AABB] and [param aabb] are approximately equal, by calling [method @GlobalScope.is_equal_approx] on each component. + Returns [code]true[/code] if this bounding box and [param aabb] are approximately equal, by calling [method Vector2.is_equal_approx] on the [member position] and the [member size]. </description> </method> <method name="is_finite" qualifiers="const"> <return type="bool" /> <description> - Returns [code]true[/code] if this [AABB] is finite, by calling [method @GlobalScope.is_finite] on each component. + Returns [code]true[/code] if this bounding box's values are finite, by calling [method Vector2.is_finite] on the [member position] and the [member size]. </description> </method> <method name="merge" qualifiers="const"> <return type="AABB" /> <param index="0" name="with" type="AABB" /> <description> - Returns a larger [AABB] that contains both this [AABB] and [param with]. + Returns an [AABB] that encloses both this bounding box and [param with] around the edges. See also [method encloses]. </description> </method> </methods> <members> <member name="end" type="Vector3" setter="" getter="" default="Vector3(0, 0, 0)"> - Ending corner. This is calculated as [code]position + size[/code]. Setting this value will change the size. + The ending point. This is usually the corner on the top-right and forward of the bounding box, and is equivalent to [code]position + size[/code]. Setting this point affects the [member size]. </member> <member name="position" type="Vector3" setter="" getter="" default="Vector3(0, 0, 0)"> - Beginning corner. Typically has values lower than [member end]. + The origin point. This is usually the corner on the bottom-left and back of the bounding box. </member> <member name="size" type="Vector3" setter="" getter="" default="Vector3(0, 0, 0)"> - Size from [member position] to [member end]. Typically, all components are positive. - If the size is negative, you can use [method abs] to fix it. + The bounding box's width, height, and depth starting from [member position]. Setting this value also affects the [member end] point. + [b]Note:[/b] It's recommended setting the width, height, and depth to non-negative values. This is because most methods in Godot assume that the [member position] is the bottom-left-back corner, and the [member end] is the top-right-forward corner. To get an equivalent bounding box with non-negative size, use [method abs]. </member> </members> <operators> @@ -237,7 +360,7 @@ <return type="bool" /> <param index="0" name="right" type="AABB" /> <description> - Returns [code]true[/code] if the AABBs are not equal. + Returns [code]true[/code] if the [member position] or [member size] of both bounding boxes are not equal. [b]Note:[/b] Due to floating-point precision errors, consider using [method is_equal_approx] instead, which is more reliable. </description> </operator> @@ -254,7 +377,7 @@ <return type="bool" /> <param index="0" name="right" type="AABB" /> <description> - Returns [code]true[/code] if the AABBs are exactly equal. + Returns [code]true[/code] if both [member position] and [member size] of the bounding boxes are exactly equal, respectively. [b]Note:[/b] Due to floating-point precision errors, consider using [method is_equal_approx] instead, which is more reliable. </description> </operator> diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index acff58dadf..e0d41ab90a 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -2551,6 +2551,7 @@ [b]Note:[/b] This setting is only effective when using the Compatibility rendering method, not Forward+ and Mobile. </member> <member name="rendering/limits/spatial_indexer/threaded_cull_minimum_instances" type="int" setter="" getter="" default="1000"> + The minimum number of instances that must be present in a scene to enable culling computations on multiple threads. If a scene has fewer instances than this number, culling is done on a single thread. </member> <member name="rendering/limits/spatial_indexer/update_iterations_per_frame" type="int" setter="" getter="" default="10"> </member> diff --git a/doc/tools/make_rst.py b/doc/tools/make_rst.py index 2f6340e572..4435d52527 100755 --- a/doc/tools/make_rst.py +++ b/doc/tools/make_rst.py @@ -68,6 +68,8 @@ BASE_STRINGS = [ "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 enum. Please help us by :ref:`contributing one <doc_updating_the_class_reference>`!", + "There is currently no description for this constant. 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>`!", @@ -1102,6 +1104,14 @@ def make_rst_class(class_def: ClassDef, state: State, dry_run: bool, output_dir: if value.text is not None and value.text.strip() != "": f.write(f"{format_text_block(value.text.strip(), value, state)}") + else: + f.write(".. container:: contribute\n\n\t") + f.write( + translate( + "There is currently no description for this enum. Please help us by :ref:`contributing one <doc_updating_the_class_reference>`!" + ) + + "\n\n" + ) f.write("\n\n") @@ -1125,6 +1135,14 @@ def make_rst_class(class_def: ClassDef, state: State, dry_run: bool, output_dir: if constant.text is not None and constant.text.strip() != "": f.write(f"{format_text_block(constant.text.strip(), constant, state)}") + else: + f.write(".. container:: contribute\n\n\t") + f.write( + translate( + "There is currently no description for this constant. Please help us by :ref:`contributing one <doc_updating_the_class_reference>`!" + ) + + "\n\n" + ) f.write("\n\n") diff --git a/drivers/d3d12/rendering_device_driver_d3d12.cpp b/drivers/d3d12/rendering_device_driver_d3d12.cpp index 1d1dc6bec8..aed82b4ce8 100644 --- a/drivers/d3d12/rendering_device_driver_d3d12.cpp +++ b/drivers/d3d12/rendering_device_driver_d3d12.cpp @@ -5170,7 +5170,7 @@ void RenderingDeviceDriverD3D12::command_timestamp_write(CommandBufferID p_cmd_b TimestampQueryPoolInfo *tqp_info = (TimestampQueryPoolInfo *)p_pool_id.id; ID3D12Resource *results_buffer = tqp_info->results_buffer_allocation->GetResource(); cmd_buf_info->cmd_list->EndQuery(tqp_info->query_heap.Get(), D3D12_QUERY_TYPE_TIMESTAMP, p_index); - cmd_buf_info->cmd_list->ResolveQueryData(tqp_info->query_heap.Get(), D3D12_QUERY_TYPE_TIMESTAMP, p_index, tqp_info->query_count, results_buffer, p_index * sizeof(uint64_t)); + cmd_buf_info->cmd_list->ResolveQueryData(tqp_info->query_heap.Get(), D3D12_QUERY_TYPE_TIMESTAMP, p_index, 1, results_buffer, p_index * sizeof(uint64_t)); } void RenderingDeviceDriverD3D12::command_begin_label(CommandBufferID p_cmd_buffer, const char *p_label_name, const Color &p_color) { diff --git a/drivers/unix/os_unix.cpp b/drivers/unix/os_unix.cpp index 51ea9234d4..8126f74332 100644 --- a/drivers/unix/os_unix.cpp +++ b/drivers/unix/os_unix.cpp @@ -153,7 +153,9 @@ int OS_Unix::unix_initialize_audio(int p_audio_driver) { } void OS_Unix::initialize_core() { +#ifdef THREADS_ENABLED init_thread_posix(); +#endif FileAccess::make_default<FileAccessUnix>(FileAccess::ACCESS_RESOURCES); FileAccess::make_default<FileAccessUnix>(FileAccess::ACCESS_USERDATA); diff --git a/editor/connections_dialog.cpp b/editor/connections_dialog.cpp index a5af7ef134..0285692ab7 100644 --- a/editor/connections_dialog.cpp +++ b/editor/connections_dialog.cpp @@ -1202,7 +1202,22 @@ void ConnectionsDock::_slot_menu_about_to_popup() { slot_menu->set_item_disabled(slot_menu->get_item_index(SLOT_MENU_DISCONNECT), connection_is_inherited); } -void ConnectionsDock::_rmb_pressed(const Ref<InputEvent> &p_event) { +void ConnectionsDock::_tree_gui_input(const Ref<InputEvent> &p_event) { + // Handle Delete press. + if (ED_IS_SHORTCUT("connections_editor/disconnect", p_event)) { + TreeItem *item = tree->get_selected(); + if (item && _get_item_type(*item) == TREE_ITEM_TYPE_CONNECTION) { + Connection connection = item->get_metadata(0); + _disconnect(connection); + update_tree(); + + // Stop the Delete input from propagating elsewhere. + accept_event(); + return; + } + } + + // Handle RMB press. const Ref<InputEventMouseButton> &mb_event = p_event; if (mb_event.is_null() || !mb_event->is_pressed() || mb_event->get_button_index() != MouseButton::RIGHT) { return; @@ -1536,13 +1551,13 @@ ConnectionsDock::ConnectionsDock() { slot_menu->connect("about_to_popup", callable_mp(this, &ConnectionsDock::_slot_menu_about_to_popup)); slot_menu->add_item(TTR("Edit..."), SLOT_MENU_EDIT); slot_menu->add_item(TTR("Go to Method"), SLOT_MENU_GO_TO_METHOD); - slot_menu->add_item(TTR("Disconnect"), SLOT_MENU_DISCONNECT); + slot_menu->add_shortcut(ED_SHORTCUT("connections_editor/disconnect", TTR("Disconnect"), Key::KEY_DELETE), SLOT_MENU_DISCONNECT); add_child(slot_menu); connect_dialog->connect("connected", callable_mp(this, &ConnectionsDock::_make_or_edit_connection)); tree->connect("item_selected", callable_mp(this, &ConnectionsDock::_tree_item_selected)); tree->connect("item_activated", callable_mp(this, &ConnectionsDock::_tree_item_activated)); - tree->connect("gui_input", callable_mp(this, &ConnectionsDock::_rmb_pressed)); + tree->connect("gui_input", callable_mp(this, &ConnectionsDock::_tree_gui_input)); add_theme_constant_override("separation", 3 * EDSCALE); } diff --git a/editor/connections_dialog.h b/editor/connections_dialog.h index 2fd4778389..a99f0dd0fe 100644 --- a/editor/connections_dialog.h +++ b/editor/connections_dialog.h @@ -252,7 +252,7 @@ class ConnectionsDock : public VBoxContainer { void _signal_menu_about_to_popup(); void _handle_slot_menu_option(int p_option); void _slot_menu_about_to_popup(); - void _rmb_pressed(const Ref<InputEvent> &p_event); + void _tree_gui_input(const Ref<InputEvent> &p_event); void _close(); protected: diff --git a/editor/debugger/editor_debugger_node.cpp b/editor/debugger/editor_debugger_node.cpp index 372d558aab..6471bd449e 100644 --- a/editor/debugger/editor_debugger_node.cpp +++ b/editor/debugger/editor_debugger_node.cpp @@ -593,9 +593,15 @@ void EditorDebuggerNode::set_breakpoints(const String &p_path, Array p_lines) { } } -void EditorDebuggerNode::reload_scripts() { +void EditorDebuggerNode::reload_all_scripts() { _for_all(tabs, [&](ScriptEditorDebugger *dbg) { - dbg->reload_scripts(); + dbg->reload_all_scripts(); + }); +} + +void EditorDebuggerNode::reload_scripts(const Vector<String> &p_script_paths) { + _for_all(tabs, [&](ScriptEditorDebugger *dbg) { + dbg->reload_scripts(p_script_paths); }); } diff --git a/editor/debugger/editor_debugger_node.h b/editor/debugger/editor_debugger_node.h index 4338f144b8..d30f29c7c6 100644 --- a/editor/debugger/editor_debugger_node.h +++ b/editor/debugger/editor_debugger_node.h @@ -187,7 +187,8 @@ public: bool is_skip_breakpoints() const; void set_breakpoint(const String &p_path, int p_line, bool p_enabled); void set_breakpoints(const String &p_path, Array p_lines); - void reload_scripts(); + void reload_all_scripts(); + void reload_scripts(const Vector<String> &p_script_paths); // Remote inspector/edit. void request_remote_tree(); diff --git a/editor/debugger/script_editor_debugger.cpp b/editor/debugger/script_editor_debugger.cpp index 6cc3769976..3c863bdc19 100644 --- a/editor/debugger/script_editor_debugger.cpp +++ b/editor/debugger/script_editor_debugger.cpp @@ -1518,8 +1518,12 @@ void ScriptEditorDebugger::set_breakpoint(const String &p_path, int p_line, bool } } -void ScriptEditorDebugger::reload_scripts() { - _put_msg("reload_scripts", Array(), debugging_thread_id != Thread::UNASSIGNED_ID ? debugging_thread_id : Thread::MAIN_ID); +void ScriptEditorDebugger::reload_all_scripts() { + _put_msg("reload_all_scripts", Array(), debugging_thread_id != Thread::UNASSIGNED_ID ? debugging_thread_id : Thread::MAIN_ID); +} + +void ScriptEditorDebugger::reload_scripts(const Vector<String> &p_script_paths) { + _put_msg("reload_scripts", Variant(p_script_paths).operator Array(), debugging_thread_id != Thread::UNASSIGNED_ID ? debugging_thread_id : Thread::MAIN_ID); } bool ScriptEditorDebugger::is_skip_breakpoints() { diff --git a/editor/debugger/script_editor_debugger.h b/editor/debugger/script_editor_debugger.h index 79224061ff..589e82ef25 100644 --- a/editor/debugger/script_editor_debugger.h +++ b/editor/debugger/script_editor_debugger.h @@ -300,7 +300,8 @@ public: void update_live_edit_root(); - void reload_scripts(); + void reload_all_scripts(); + void reload_scripts(const Vector<String> &p_script_paths); bool is_skip_breakpoints(); diff --git a/editor/editor_file_system.cpp b/editor/editor_file_system.cpp index aab433ac27..9fbe7ba655 100644 --- a/editor/editor_file_system.cpp +++ b/editor/editor_file_system.cpp @@ -2354,7 +2354,11 @@ void EditorFileSystem::reimport_files(const Vector<String> &p_files) { reimport_files.sort(); +#ifdef THREADS_ENABLED bool use_multiple_threads = GLOBAL_GET("editor/import/use_multiple_threads"); +#else + bool use_multiple_threads = false; +#endif int from = 0; for (int i = 0; i < reimport_files.size(); i++) { @@ -2680,6 +2684,10 @@ void EditorFileSystem::remove_import_format_support_query(Ref<EditorFileSystemIm } EditorFileSystem::EditorFileSystem() { +#ifdef THREADS_ENABLED + use_threads = true; +#endif + ResourceLoader::import = _resource_import; reimport_on_missing_imported_files = GLOBAL_GET("editor/import/reimport_missing_imported_files"); singleton = this; diff --git a/editor/editor_file_system.h b/editor/editor_file_system.h index 2a34c06b0d..2f5cd88a55 100644 --- a/editor/editor_file_system.h +++ b/editor/editor_file_system.h @@ -165,7 +165,7 @@ class EditorFileSystem : public Node { EditorFileSystemDirectory::FileInfo *new_file = nullptr; }; - bool use_threads = true; + bool use_threads = false; Thread thread; static void _thread_func(void *_userdata); diff --git a/editor/editor_help_search.cpp b/editor/editor_help_search.cpp index 190e8c2445..229eb79e11 100644 --- a/editor/editor_help_search.cpp +++ b/editor/editor_help_search.cpp @@ -48,7 +48,7 @@ void EditorHelpSearch::_update_results() { search_flags |= SEARCH_SHOW_HIERARCHY; } - search = Ref<Runner>(memnew(Runner(results_tree, results_tree, term, search_flags))); + search = Ref<Runner>(memnew(Runner(results_tree, results_tree, &tree_cache, term, search_flags))); set_process(true); } @@ -96,6 +96,7 @@ void EditorHelpSearch::_notification(int p_what) { switch (p_what) { case NOTIFICATION_VISIBILITY_CHANGED: { if (!is_visible()) { + tree_cache.clear(); callable_mp(results_tree, &Tree::clear).call_deferred(); // Wait for the Tree's mouse event propagation. get_ok_button()->set_disabled(true); EditorSettings::get_singleton()->set_project_metadata("dialog_bounds", "search_help", Rect2(get_position(), get_size())); @@ -258,6 +259,13 @@ EditorHelpSearch::EditorHelpSearch() { vbox->add_child(results_tree, true); } +void EditorHelpSearch::TreeCache::clear() { + for (const KeyValue<String, TreeItem *> &E : item_cache) { + memdelete(E.value); + } + item_cache.clear(); +} + bool EditorHelpSearch::Runner::_is_class_disabled_by_feature_profile(const StringName &p_class) { Ref<EditorFeatureProfile> profile = EditorFeatureProfileManager::get_singleton()->get_current_profile(); if (profile.is_null()) { @@ -436,11 +444,42 @@ bool EditorHelpSearch::Runner::_phase_match_classes() { return iterator_stack.is_empty(); } +void EditorHelpSearch::Runner::_populate_cache() { + root_item = results_tree->get_root(); + + if (root_item) { + LocalVector<TreeItem *> stack; + + // Add children of root item to stack. + for (TreeItem *child = root_item->get_first_child(); child; child = child->get_next()) { + stack.push_back(child); + } + + // Traverse stack and cache items. + while (!stack.is_empty()) { + TreeItem *cur_item = stack[stack.size() - 1]; + stack.resize(stack.size() - 1); + + // Add to the cache. + tree_cache->item_cache.insert(cur_item->get_metadata(0).operator String(), cur_item); + + // Add any children to the stack. + for (TreeItem *child = cur_item->get_first_child(); child; child = child->get_next()) { + stack.push_back(child); + } + + // Remove from parent. + cur_item->get_parent()->remove_child(cur_item); + } + } else { + root_item = results_tree->create_item(); + } +} + bool EditorHelpSearch::Runner::_phase_class_items_init() { iterator_match = matches.begin(); - results_tree->clear(); - root_item = results_tree->create_item(); + _populate_cache(); class_items.clear(); return true; @@ -618,27 +657,54 @@ TreeItem *EditorHelpSearch::Runner::_create_class_hierarchy(const ClassMatch &p_ return class_item; } +bool EditorHelpSearch::Runner::_find_or_create_item(TreeItem *p_parent, const String &p_item_meta, TreeItem *&r_item) { + // Attempt to find in cache. + if (tree_cache->item_cache.has(p_item_meta)) { + r_item = tree_cache->item_cache[p_item_meta]; + + // Remove from cache. + tree_cache->item_cache.erase(p_item_meta); + + // Add to tree. + p_parent->add_child(r_item); + + return false; + } else { + // Otherwise create item. + r_item = results_tree->create_item(p_parent); + + return true; + } +} + TreeItem *EditorHelpSearch::Runner::_create_class_item(TreeItem *p_parent, const DocData::ClassDoc *p_doc, bool p_gray) { String tooltip = DTR(p_doc->brief_description.strip_edges()); - TreeItem *item = results_tree->create_item(p_parent); - item->set_icon(0, EditorNode::get_singleton()->get_class_icon(p_doc->name)); - item->set_text(0, p_doc->name); - item->set_text(1, TTR("Class")); - item->set_tooltip_text(0, tooltip); - item->set_tooltip_text(1, tooltip); - item->set_metadata(0, "class_name:" + p_doc->name); + const String item_meta = "class_name:" + p_doc->name; + + TreeItem *item = nullptr; + if (_find_or_create_item(p_parent, item_meta, item)) { + item->set_icon(0, EditorNode::get_singleton()->get_class_icon(p_doc->name)); + item->set_text(0, p_doc->name); + item->set_text(1, TTR("Class")); + item->set_tooltip_text(0, tooltip); + item->set_tooltip_text(1, tooltip); + item->set_metadata(0, item_meta); + if (p_doc->is_deprecated) { + Ref<Texture2D> error_icon = ui_service->get_editor_theme_icon(SNAME("StatusError")); + item->add_button(0, error_icon, 0, false, TTR("This class is marked as deprecated.")); + } else if (p_doc->is_experimental) { + Ref<Texture2D> warning_icon = ui_service->get_editor_theme_icon(SNAME("NodeWarning")); + item->add_button(0, warning_icon, 0, false, TTR("This class is marked as experimental.")); + } + } + if (p_gray) { item->set_custom_color(0, disabled_color); item->set_custom_color(1, disabled_color); - } - - if (p_doc->is_deprecated) { - Ref<Texture2D> error_icon = ui_service->get_editor_theme_icon("StatusError"); - item->add_button(0, error_icon, 0, false, TTR("This class is marked as deprecated.")); - } else if (p_doc->is_experimental) { - Ref<Texture2D> warning_icon = ui_service->get_editor_theme_icon("NodeWarning"); - item->add_button(0, warning_icon, 0, false, TTR("This class is marked as experimental.")); + } else { + item->clear_custom_color(0); + item->clear_custom_color(1); } _match_item(item, p_doc->name); @@ -679,30 +745,29 @@ TreeItem *EditorHelpSearch::Runner::_create_theme_property_item(TreeItem *p_pare } TreeItem *EditorHelpSearch::Runner::_create_member_item(TreeItem *p_parent, const String &p_class_name, const String &p_icon, const String &p_name, const String &p_text, const String &p_type, const String &p_metatype, const String &p_tooltip, bool is_deprecated, bool is_experimental) { - Ref<Texture2D> icon; - String text; - if (search_flags & SEARCH_SHOW_HIERARCHY) { - icon = ui_service->get_editor_theme_icon(p_icon); - text = p_text; - } else { - icon = ui_service->get_editor_theme_icon(p_icon); - text = p_class_name + "." + p_text; + const String item_meta = "class_" + p_metatype + ":" + p_class_name + ":" + p_name; + + TreeItem *item = nullptr; + if (_find_or_create_item(p_parent, item_meta, item)) { + item->set_icon(0, ui_service->get_editor_theme_icon(p_icon)); + item->set_text(1, TTRGET(p_type)); + item->set_tooltip_text(0, p_tooltip); + item->set_tooltip_text(1, p_tooltip); + item->set_metadata(0, item_meta); + + if (is_deprecated) { + Ref<Texture2D> error_icon = ui_service->get_editor_theme_icon(SNAME("StatusError")); + item->add_button(0, error_icon, 0, false, TTR("This member is marked as deprecated.")); + } else if (is_experimental) { + Ref<Texture2D> warning_icon = ui_service->get_editor_theme_icon(SNAME("NodeWarning")); + item->add_button(0, warning_icon, 0, false, TTR("This member is marked as experimental.")); + } } - TreeItem *item = results_tree->create_item(p_parent); - item->set_icon(0, icon); - item->set_text(0, text); - item->set_text(1, TTRGET(p_type)); - item->set_tooltip_text(0, p_tooltip); - item->set_tooltip_text(1, p_tooltip); - item->set_metadata(0, "class_" + p_metatype + ":" + p_class_name + ":" + p_name); - - if (is_deprecated) { - Ref<Texture2D> error_icon = ui_service->get_editor_theme_icon("StatusError"); - item->add_button(0, error_icon, 0, false, TTR("This member is marked as deprecated.")); - } else if (is_experimental) { - Ref<Texture2D> warning_icon = ui_service->get_editor_theme_icon("NodeWarning"); - item->add_button(0, warning_icon, 0, false, TTR("This member is marked as experimental.")); + if (search_flags & SEARCH_SHOW_HIERARCHY) { + item->set_text(0, p_text); + } else { + item->set_text(0, p_class_name + "." + p_text); } _match_item(item, p_name); @@ -721,9 +786,10 @@ bool EditorHelpSearch::Runner::work(uint64_t slot) { return true; } -EditorHelpSearch::Runner::Runner(Control *p_icon_service, Tree *p_results_tree, const String &p_term, int p_search_flags) : +EditorHelpSearch::Runner::Runner(Control *p_icon_service, Tree *p_results_tree, TreeCache *p_tree_cache, const String &p_term, int p_search_flags) : ui_service(p_icon_service), results_tree(p_results_tree), + tree_cache(p_tree_cache), term((p_search_flags & SEARCH_CASE_SENSITIVE) == 0 ? p_term.strip_edges().to_lower() : p_term.strip_edges()), search_flags(p_search_flags), disabled_color(ui_service->get_theme_color(SNAME("font_disabled_color"), EditorStringName(Editor))) { diff --git a/editor/editor_help_search.h b/editor/editor_help_search.h index 1d7f2b81df..e4980d6ff7 100644 --- a/editor/editor_help_search.h +++ b/editor/editor_help_search.h @@ -67,6 +67,16 @@ class EditorHelpSearch : public ConfirmationDialog { class Runner; Ref<Runner> search; + struct TreeCache { + HashMap<String, TreeItem *> item_cache; + + void clear(); + + ~TreeCache() { + clear(); + } + } tree_cache; + void _update_results(); void _search_box_gui_input(const Ref<InputEvent> &p_event); @@ -117,6 +127,7 @@ class EditorHelpSearch::Runner : public RefCounted { Control *ui_service = nullptr; Tree *results_tree = nullptr; + TreeCache *tree_cache = nullptr; String term; Vector<String> terms; int search_flags; @@ -134,6 +145,9 @@ class EditorHelpSearch::Runner : public RefCounted { bool _is_class_disabled_by_feature_profile(const StringName &p_class); + void _populate_cache(); + bool _find_or_create_item(TreeItem *p_parent, const String &p_item_meta, TreeItem *&r_item); + bool _slice(); bool _phase_match_classes_init(); bool _phase_match_classes(); @@ -162,7 +176,7 @@ class EditorHelpSearch::Runner : public RefCounted { public: bool work(uint64_t slot = 100000); - Runner(Control *p_icon_service, Tree *p_results_tree, const String &p_term, int p_search_flags); + Runner(Control *p_icon_service, Tree *p_results_tree, TreeCache *p_tree_cache, const String &p_term, int p_search_flags); }; #endif // EDITOR_HELP_SEARCH_H diff --git a/editor/filesystem_dock.cpp b/editor/filesystem_dock.cpp index a46dffb1f3..0aa9a3bfee 100644 --- a/editor/filesystem_dock.cpp +++ b/editor/filesystem_dock.cpp @@ -865,18 +865,7 @@ void FileSystemDock::_search(EditorFileSystemDirectory *p_path, List<FileInfo> * struct FileSystemDock::FileInfoTypeComparator { bool operator()(const FileInfo &p_a, const FileInfo &p_b) const { - // Uses the extension, then the icon name to distinguish file types. - String icon_path_a = ""; - String icon_path_b = ""; - Ref<Texture2D> icon_a = EditorNode::get_singleton()->get_class_icon(p_a.type); - if (icon_a.is_valid()) { - icon_path_a = icon_a->get_name(); - } - Ref<Texture2D> icon_b = EditorNode::get_singleton()->get_class_icon(p_b.type); - if (icon_b.is_valid()) { - icon_path_b = icon_b->get_name(); - } - return NaturalNoCaseComparator()(p_a.name.get_extension() + icon_path_a + p_a.name.get_basename(), p_b.name.get_extension() + icon_path_b + p_b.name.get_basename()); + return NaturalNoCaseComparator()(p_a.name.get_extension() + p_a.type + p_a.name.get_basename(), p_b.name.get_extension() + p_b.type + p_b.name.get_basename()); } }; diff --git a/editor/icons/TileMapLayer.svg b/editor/icons/TileMapLayer.svg new file mode 100644 index 0000000000..1903a87e3b --- /dev/null +++ b/editor/icons/TileMapLayer.svg @@ -0,0 +1 @@ +<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m1 7v2h2v-2zm3 0v2h2v-2zm3 0v2h2v-2zm3 0v2h2v-2zm3 0v2h2v-2z" fill="#8da5f3"/></svg>
\ No newline at end of file diff --git a/editor/plugins/script_editor_plugin.cpp b/editor/plugins/script_editor_plugin.cpp index ad82801692..55191f44d4 100644 --- a/editor/plugins/script_editor_plugin.cpp +++ b/editor/plugins/script_editor_plugin.cpp @@ -1001,7 +1001,10 @@ void ScriptEditor::_res_saved_callback(const Ref<Resource> &p_res) { } _update_script_names(); - trigger_live_script_reload(); + Ref<Script> scr = p_res; + if (scr.is_valid()) { + trigger_live_script_reload(scr->get_path()); + } } void ScriptEditor::_scene_saved_callback(const String &p_path) { @@ -1029,16 +1032,33 @@ void ScriptEditor::_scene_saved_callback(const String &p_path) { } } -void ScriptEditor::trigger_live_script_reload() { +void ScriptEditor::trigger_live_script_reload(const String &p_script_path) { + if (!script_paths_to_reload.has(p_script_path)) { + script_paths_to_reload.append(p_script_path); + } if (!pending_auto_reload && auto_reload_running_scripts) { callable_mp(this, &ScriptEditor::_live_auto_reload_running_scripts).call_deferred(); pending_auto_reload = true; } } +void ScriptEditor::trigger_live_script_reload_all() { + if (!pending_auto_reload && auto_reload_running_scripts) { + call_deferred(SNAME("_live_auto_reload_running_scripts")); + pending_auto_reload = true; + reload_all_scripts = true; + } +} + void ScriptEditor::_live_auto_reload_running_scripts() { pending_auto_reload = false; - EditorDebuggerNode::get_singleton()->reload_scripts(); + if (reload_all_scripts) { + EditorDebuggerNode::get_singleton()->reload_all_scripts(); + } else { + EditorDebuggerNode::get_singleton()->reload_scripts(script_paths_to_reload); + } + reload_all_scripts = false; + script_paths_to_reload.clear(); } bool ScriptEditor::_test_script_times_on_disk(Ref<Resource> p_for_script) { diff --git a/editor/plugins/script_editor_plugin.h b/editor/plugins/script_editor_plugin.h index 4a814ea1bc..68eb23c838 100644 --- a/editor/plugins/script_editor_plugin.h +++ b/editor/plugins/script_editor_plugin.h @@ -382,6 +382,8 @@ class ScriptEditor : public PanelContainer { bool pending_auto_reload; bool auto_reload_running_scripts; + bool reload_all_scripts = false; + Vector<String> script_paths_to_reload; void _live_auto_reload_running_scripts(); void _update_selected_editor_menu(); @@ -542,7 +544,8 @@ public: void clear_docs_from_script(const Ref<Script> &p_script); void update_docs_from_script(const Ref<Script> &p_script); - void trigger_live_script_reload(); + void trigger_live_script_reload(const String &p_script_path); + void trigger_live_script_reload_all(); bool can_take_away_focus() const; diff --git a/editor/plugins/script_text_editor.cpp b/editor/plugins/script_text_editor.cpp index babae9eae8..370144a427 100644 --- a/editor/plugins/script_text_editor.cpp +++ b/editor/plugins/script_text_editor.cpp @@ -825,7 +825,7 @@ void ScriptEditor::_update_modified_scripts_for_external_editor(Ref<Script> p_fo scr->set_last_modified_time(rel_scr->get_last_modified_time()); scr->update_exports(); - trigger_live_script_reload(); + trigger_live_script_reload(scr->get_path()); } } } diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp index 9d1c0d6c62..9c7ba827b7 100644 --- a/editor/scene_tree_dock.cpp +++ b/editor/scene_tree_dock.cpp @@ -3998,6 +3998,7 @@ void SceneTreeDock::_list_all_subresources(PopupMenu *p_menu) { } p_menu->add_item(display_text); + p_menu->set_item_tooltip(-1, pair.first->get_path()); p_menu->set_item_metadata(-1, pair.first->get_instance_id()); } } diff --git a/main/main.cpp b/main/main.cpp index b50def9cec..dbe186d63a 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -475,7 +475,7 @@ void Main::print_help(const char *p_binary) { OS::get_singleton()->print(" --profiling Enable profiling in the script debugger.\n"); OS::get_singleton()->print(" --gpu-profile Show a GPU profile of the tasks that took the most time during frame rendering.\n"); OS::get_singleton()->print(" --gpu-validation Enable graphics API validation layers for debugging.\n"); -#if DEBUG_ENABLED +#ifdef DEBUG_ENABLED OS::get_singleton()->print(" --gpu-abort Abort on graphics API usage errors (usually validation layer errors). May help see the problem if your system freezes.\n"); #endif OS::get_singleton()->print(" --generate-spirv-debug-info Generate SPIR-V debug information. This allows source-level shader debugging with RenderDoc.\n"); @@ -1615,12 +1615,18 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph } // Initialize WorkerThreadPool. - if (editor || project_manager) { - WorkerThreadPool::get_singleton()->init(-1, 0.75); - } else { - int worker_threads = GLOBAL_GET("threading/worker_pool/max_threads"); - float low_priority_ratio = GLOBAL_GET("threading/worker_pool/low_priority_thread_ratio"); - WorkerThreadPool::get_singleton()->init(worker_threads, low_priority_ratio); + { +#ifdef THREADS_ENABLED + if (editor || project_manager) { + WorkerThreadPool::get_singleton()->init(-1, 0.75); + } else { + int worker_threads = GLOBAL_GET("threading/worker_pool/max_threads"); + float low_priority_ratio = GLOBAL_GET("threading/worker_pool/low_priority_thread_ratio"); + WorkerThreadPool::get_singleton()->init(worker_threads, low_priority_ratio); + } +#else + WorkerThreadPool::get_singleton()->init(0, 0); +#endif } #ifdef TOOLS_ENABLED diff --git a/misc/dist/html/editor.html b/misc/dist/html/editor.html index 93afbf085d..e5c68c6338 100644 --- a/misc/dist/html/editor.html +++ b/misc/dist/html/editor.html @@ -23,7 +23,7 @@ <link id="-gd-engine-icon" rel="icon" type="image/png" href="favicon.png"> <link rel="apple-touch-icon" type="image/png" href="favicon.png"> <link rel="manifest" href="manifest.json"> - <title>Godot Engine Web Editor (@GODOT_VERSION@)</title> + <title>Godot Engine Web Editor (___GODOT_VERSION___)</title> <style> *:focus { /* More visible outline for better keyboard navigation. */ @@ -294,7 +294,7 @@ a:active { <br > <img src="logo.svg" alt="Godot Engine logo" width="1024" height="414" style="width: auto; height: auto; max-width: min(85%, 50vh); max-height: 250px"> <br > - @GODOT_VERSION@ + ___GODOT_VERSION___ <br > <a href="releases/">Need an old version?</a> <br > @@ -384,7 +384,9 @@ window.addEventListener('load', () => { }); } - const missing = Engine.getMissingFeatures(); + const missing = Engine.getMissingFeatures({ + threads: ___GODOT_THREADS_ENABLED___, + }); if (missing.length) { // Display error dialog as threading support is required for the editor. document.getElementById('startButton').disabled = 'disabled'; diff --git a/misc/dist/html/full-size.html b/misc/dist/html/full-size.html index 6710cb1533..54571e27c7 100644 --- a/misc/dist/html/full-size.html +++ b/misc/dist/html/full-size.html @@ -136,6 +136,7 @@ body { <script src="$GODOT_URL"></script> <script> const GODOT_CONFIG = $GODOT_CONFIG; +const GODOT_THREADS_ENABLED = $GODOT_THREADS_ENABLED; const engine = new Engine(GODOT_CONFIG); (function () { @@ -213,7 +214,9 @@ const engine = new Engine(GODOT_CONFIG); initializing = false; } - const missing = Engine.getMissingFeatures(); + const missing = Engine.getMissingFeatures({ + threads: GODOT_THREADS_ENABLED, + }); if (missing.length !== 0) { const missingMsg = 'Error\nThe following features required to run Godot projects on the Web are missing:\n'; displayFailureNotice(missingMsg + missing.join('\n')); diff --git a/misc/dist/html/service-worker.js b/misc/dist/html/service-worker.js index 310574f21d..70e7a399e1 100644 --- a/misc/dist/html/service-worker.js +++ b/misc/dist/html/service-worker.js @@ -3,14 +3,14 @@ // that they need an Internet connection to run the project if desired. // Incrementing CACHE_VERSION will kick off the install event and force // previously cached resources to be updated from the network. -const CACHE_VERSION = "@GODOT_VERSION@"; -const CACHE_PREFIX = "@GODOT_NAME@-sw-cache-"; +const CACHE_VERSION = "___GODOT_VERSION___"; +const CACHE_PREFIX = "___GODOT_NAME___-sw-cache-"; const CACHE_NAME = CACHE_PREFIX + CACHE_VERSION; -const OFFLINE_URL = "@GODOT_OFFLINE_PAGE@"; +const OFFLINE_URL = "___GODOT_OFFLINE_PAGE___"; // Files that will be cached on load. -const CACHED_FILES = @GODOT_CACHE@; +const CACHED_FILES = ___GODOT_CACHE___; // Files that we might not want the user to preload, and will only be cached on first load. -const CACHABLE_FILES = @GODOT_OPT_CACHE@; +const CACHABLE_FILES = ___GODOT_OPT_CACHE___; const FULL_CACHE = CACHED_FILES.concat(CACHABLE_FILES); self.addEventListener("install", (event) => { @@ -22,7 +22,7 @@ self.addEventListener("activate", (event) => { function (keys) { // Remove old caches. return Promise.all(keys.filter(key => key.startsWith(CACHE_PREFIX) && key != CACHE_NAME).map(key => caches.delete(key))); - }).then(function() { + }).then(function () { // Enable navigation preload if available. return ("navigationPreload" in self.registration) ? self.registration.navigationPreload.enable() : Promise.resolve(); }) diff --git a/misc/extension_api_validation/4.2-stable.expected b/misc/extension_api_validation/4.2-stable.expected index 2c52144896..25094dda77 100644 --- a/misc/extension_api_validation/4.2-stable.expected +++ b/misc/extension_api_validation/4.2-stable.expected @@ -51,3 +51,11 @@ Validate extension JSON: Error: Field 'classes/RenderingDevice/methods/texture_u Barrier arguments have been removed from all relevant functions as they're no longer required. Draw and compute list overlap no longer needs to be specified. Initial and final actions have been simplified into fewer options. + + +GH-87115 +-------- +Validate extension JSON: Error: Field 'classes/TileMap/methods/get_collision_visibility_mode': is_const changed value in new API, from false to true. +Validate extension JSON: Error: Field 'classes/TileMap/methods/get_navigation_visibility_mode': is_const changed value in new API, from false to true. + +Two TileMap getters were made const. No adjustments should be necessary. diff --git a/modules/etcpak/image_compress_etcpak.cpp b/modules/etcpak/image_compress_etcpak.cpp index f528b92cf2..e9ce92dd80 100644 --- a/modules/etcpak/image_compress_etcpak.cpp +++ b/modules/etcpak/image_compress_etcpak.cpp @@ -40,7 +40,7 @@ EtcpakType _determine_etc_type(Image::UsedChannels p_channels) { switch (p_channels) { case Image::USED_CHANNELS_L: - return EtcpakType::ETCPAK_TYPE_ETC1; + return EtcpakType::ETCPAK_TYPE_ETC2; case Image::USED_CHANNELS_LA: return EtcpakType::ETCPAK_TYPE_ETC2_ALPHA; case Image::USED_CHANNELS_R: diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp index c4509aa5c3..7b486f2a35 100644 --- a/modules/gdscript/gdscript.cpp +++ b/modules/gdscript/gdscript.cpp @@ -2351,14 +2351,13 @@ struct GDScriptDepSort { void GDScriptLanguage::reload_all_scripts() { #ifdef DEBUG_ENABLED print_verbose("GDScript: Reloading all scripts"); - List<Ref<GDScript>> scripts; + Array scripts; { MutexLock lock(this->mutex); SelfList<GDScript> *elem = script_list.first(); while (elem) { - // Scripts will reload all subclasses, so only reload root scripts. - if (elem->self()->is_root_script() && elem->self()->get_path().is_resource_file()) { + if (elem->self()->get_path().is_resource_file()) { print_verbose("GDScript: Found: " + elem->self()->get_path()); scripts.push_back(Ref<GDScript>(elem->self())); //cast to gdscript to avoid being erased by accident } @@ -2379,19 +2378,11 @@ void GDScriptLanguage::reload_all_scripts() { #endif } - //as scripts are going to be reloaded, must proceed without locking here - - scripts.sort_custom<GDScriptDepSort>(); //update in inheritance dependency order - - for (Ref<GDScript> &scr : scripts) { - print_verbose("GDScript: Reloading: " + scr->get_path()); - scr->load_source_code(scr->get_path()); - scr->reload(true); - } + reload_scripts(scripts, true); #endif } -void GDScriptLanguage::reload_tool_script(const Ref<Script> &p_script, bool p_soft_reload) { +void GDScriptLanguage::reload_scripts(const Array &p_scripts, bool p_soft_reload) { #ifdef DEBUG_ENABLED List<Ref<GDScript>> scripts; @@ -2417,7 +2408,7 @@ void GDScriptLanguage::reload_tool_script(const Ref<Script> &p_script, bool p_so scripts.sort_custom<GDScriptDepSort>(); //update in inheritance dependency order for (Ref<GDScript> &scr : scripts) { - bool reload = scr == p_script || to_reload.has(scr->get_base()); + bool reload = p_scripts.has(scr) || to_reload.has(scr->get_base()); if (!reload) { continue; @@ -2440,7 +2431,7 @@ void GDScriptLanguage::reload_tool_script(const Ref<Script> &p_script, bool p_so } } -//same thing for placeholders + //same thing for placeholders #ifdef TOOLS_ENABLED while (scr->placeholders.size()) { @@ -2468,6 +2459,8 @@ void GDScriptLanguage::reload_tool_script(const Ref<Script> &p_script, bool p_so for (KeyValue<Ref<GDScript>, HashMap<ObjectID, List<Pair<StringName, Variant>>>> &E : to_reload) { Ref<GDScript> scr = E.key; + print_verbose("GDScript: Reloading: " + scr->get_path()); + scr->load_source_code(scr->get_path()); scr->reload(p_soft_reload); //restore state if saved @@ -2515,6 +2508,12 @@ void GDScriptLanguage::reload_tool_script(const Ref<Script> &p_script, bool p_so #endif } +void GDScriptLanguage::reload_tool_script(const Ref<Script> &p_script, bool p_soft_reload) { + Array scripts; + scripts.push_back(p_script); + reload_scripts(scripts, p_soft_reload); +} + void GDScriptLanguage::frame() { calls = 0; diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h index e245738070..fdfd79f0fc 100644 --- a/modules/gdscript/gdscript.h +++ b/modules/gdscript/gdscript.h @@ -579,6 +579,7 @@ public: virtual String debug_parse_stack_level_expression(int p_level, const String &p_expression, int p_max_subitems = -1, int p_max_depth = -1) override; virtual void reload_all_scripts() override; + virtual void reload_scripts(const Array &p_scripts, bool p_soft_reload) override; virtual void reload_tool_script(const Ref<Script> &p_script, bool p_soft_reload) override; virtual void frame() override; diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index 3fd5b3f519..7026d131e3 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -4908,8 +4908,19 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_property(const PropertyInfo } result.builtin_type = p_property.type; if (p_property.type == Variant::OBJECT) { - result.kind = GDScriptParser::DataType::NATIVE; - result.native_type = p_property.class_name == StringName() ? SNAME("Object") : p_property.class_name; + if (ScriptServer::is_global_class(p_property.class_name)) { + result.kind = GDScriptParser::DataType::SCRIPT; + result.script_path = ScriptServer::get_global_class_path(p_property.class_name); + result.native_type = ScriptServer::get_global_class_native_base(p_property.class_name); + + Ref<Script> scr = ResourceLoader::load(ScriptServer::get_global_class_path(p_property.class_name)); + if (scr.is_valid()) { + result.script_type = scr; + } + } else { + result.kind = GDScriptParser::DataType::NATIVE; + result.native_type = p_property.class_name == StringName() ? "Object" : p_property.class_name; + } } else { result.kind = GDScriptParser::DataType::BUILTIN; result.builtin_type = p_property.type; diff --git a/modules/gdscript/gdscript_byte_codegen.h b/modules/gdscript/gdscript_byte_codegen.h index 9bface6136..f902cb10cc 100644 --- a/modules/gdscript/gdscript_byte_codegen.h +++ b/modules/gdscript/gdscript_byte_codegen.h @@ -121,7 +121,7 @@ class GDScriptByteCodeGenerator : public GDScriptCodeGenerator { RBMap<MethodBind *, int> method_bind_map; RBMap<GDScriptFunction *, int> lambdas_map; -#if DEBUG_ENABLED +#ifdef DEBUG_ENABLED // Keep method and property names for pointer and validated operations. // Used when disassembling the bytecode. Vector<String> operator_names; diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index 9ad2ba1914..210e2c3898 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -1370,7 +1370,7 @@ static void _find_identifiers(const GDScriptParser::CompletionContext &p_context } } -static GDScriptCompletionIdentifier _type_from_variant(const Variant &p_value) { +static GDScriptCompletionIdentifier _type_from_variant(const Variant &p_value, GDScriptParser::CompletionContext &p_context) { GDScriptCompletionIdentifier ci; ci.value = p_value; ci.type.is_constant = true; @@ -1392,8 +1392,22 @@ static GDScriptCompletionIdentifier _type_from_variant(const Variant &p_value) { scr = obj->get_script(); } if (scr.is_valid()) { - ci.type.script_type = scr; + ci.type.script_path = scr->get_path(); + + if (scr->get_path().ends_with(".gd")) { + Error err; + Ref<GDScriptParserRef> parser = GDScriptCache::get_parser(scr->get_path(), GDScriptParserRef::INTERFACE_SOLVED, err); + if (err == OK) { + ci.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; + ci.type.class_type = parser->get_parser()->get_tree(); + ci.type.kind = GDScriptParser::DataType::CLASS; + p_context.dependent_parsers.push_back(parser); + return ci; + } + } + ci.type.kind = GDScriptParser::DataType::SCRIPT; + ci.type.script_type = scr; ci.type.native_type = scr->get_instance_base_type(); } else { ci.type.kind = GDScriptParser::DataType::NATIVE; @@ -1418,8 +1432,19 @@ static GDScriptCompletionIdentifier _type_from_property(const PropertyInfo &p_pr ci.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; ci.type.builtin_type = p_property.type; if (p_property.type == Variant::OBJECT) { - ci.type.kind = GDScriptParser::DataType::NATIVE; - ci.type.native_type = p_property.class_name == StringName() ? "Object" : p_property.class_name; + if (ScriptServer::is_global_class(p_property.class_name)) { + ci.type.kind = GDScriptParser::DataType::SCRIPT; + ci.type.script_path = ScriptServer::get_global_class_path(p_property.class_name); + ci.type.native_type = ScriptServer::get_global_class_native_base(p_property.class_name); + + Ref<Script> scr = ResourceLoader::load(ScriptServer::get_global_class_path(p_property.class_name)); + if (scr.is_valid()) { + ci.type.script_type = scr; + } + } else { + ci.type.kind = GDScriptParser::DataType::NATIVE; + ci.type.native_type = p_property.class_name == StringName() ? "Object" : p_property.class_name; + } } else { ci.type.kind = GDScriptParser::DataType::BUILTIN; } @@ -1481,7 +1506,7 @@ static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context, if (p_expression->is_constant) { // Already has a value, so just use that. - r_type = _type_from_variant(p_expression->reduced_value); + r_type = _type_from_variant(p_expression->reduced_value, p_context); switch (p_expression->get_datatype().kind) { case GDScriptParser::DataType::ENUM: case GDScriptParser::DataType::CLASS: @@ -1495,7 +1520,7 @@ static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context, switch (p_expression->type) { case GDScriptParser::Node::LITERAL: { const GDScriptParser::LiteralNode *literal = static_cast<const GDScriptParser::LiteralNode *>(p_expression); - r_type = _type_from_variant(literal->value); + r_type = _type_from_variant(literal->value, p_context); found = true; } break; case GDScriptParser::Node::SELF: { @@ -1676,7 +1701,7 @@ static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context, if (!which.is_empty()) { // Try singletons first if (GDScriptLanguage::get_singleton()->get_named_globals_map().has(which)) { - r_type = _type_from_variant(GDScriptLanguage::get_singleton()->get_named_globals_map()[which]); + r_type = _type_from_variant(GDScriptLanguage::get_singleton()->get_named_globals_map()[which], p_context); found = true; } else { for (const KeyValue<StringName, ProjectSettings::AutoloadInfo> &E : ProjectSettings::get_singleton()->get_autoload_list()) { @@ -1727,7 +1752,7 @@ static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context, if (ce.error == Callable::CallError::CALL_OK && ret.get_type() != Variant::NIL) { if (ret.get_type() != Variant::OBJECT || ret.operator Object *() != nullptr) { - r_type = _type_from_variant(ret); + r_type = _type_from_variant(ret, p_context); found = true; } } @@ -1755,7 +1780,7 @@ static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context, if (base.value.get_type() == Variant::DICTIONARY && base.value.operator Dictionary().has(String(subscript->attribute->name))) { Variant value = base.value.operator Dictionary()[String(subscript->attribute->name)]; - r_type = _type_from_variant(value); + r_type = _type_from_variant(value, p_context); found = true; break; } @@ -1807,7 +1832,7 @@ static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context, if (base.value.in(index.value)) { Variant value = base.value.get(index.value); - r_type = _type_from_variant(value); + r_type = _type_from_variant(value, p_context); found = true; break; } @@ -1862,7 +1887,7 @@ static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context, bool valid = false; Variant res = base_val.get(index.value, &valid); if (valid) { - r_type = _type_from_variant(res); + r_type = _type_from_variant(res, p_context); r_type.value = Variant(); r_type.type.is_constant = false; found = true; @@ -1920,7 +1945,7 @@ static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context, found = false; break; } - r_type = _type_from_variant(res); + r_type = _type_from_variant(res, p_context); if (!v1_use_value || !v2_use_value) { r_type.value = Variant(); r_type.type.is_constant = false; @@ -2155,7 +2180,7 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, } else { Ref<Script> scr = ResourceLoader::load(ScriptServer::get_global_class_path(p_identifier->name)); if (scr.is_valid()) { - r_type = _type_from_variant(scr); + r_type = _type_from_variant(scr, p_context); r_type.type.is_meta_type = true; return true; } @@ -2165,7 +2190,7 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, // Check global variables (including autoloads). if (GDScriptLanguage::get_singleton()->get_named_globals_map().has(p_identifier->name)) { - r_type = _type_from_variant(GDScriptLanguage::get_singleton()->get_named_globals_map()[p_identifier->name]); + r_type = _type_from_variant(GDScriptLanguage::get_singleton()->get_named_globals_map()[p_identifier->name], p_context); return true; } @@ -2218,7 +2243,7 @@ static bool _guess_identifier_type_from_base(GDScriptParser::CompletionContext & const GDScriptParser::ExpressionNode *init = member.variable->initializer; if (init->is_constant) { r_type.value = init->reduced_value; - r_type = _type_from_variant(init->reduced_value); + r_type = _type_from_variant(init->reduced_value, p_context); return true; } else if (init->start_line == p_context.current_line) { return false; @@ -2245,7 +2270,7 @@ static bool _guess_identifier_type_from_base(GDScriptParser::CompletionContext & r_type.enumeration = member.m_enum->identifier->name; return true; case GDScriptParser::ClassNode::Member::ENUM_VALUE: - r_type = _type_from_variant(member.enum_value.value); + r_type = _type_from_variant(member.enum_value.value, p_context); return true; case GDScriptParser::ClassNode::Member::SIGNAL: r_type.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; @@ -2281,7 +2306,7 @@ static bool _guess_identifier_type_from_base(GDScriptParser::CompletionContext & HashMap<StringName, Variant> constants; scr->get_constants(&constants); if (constants.has(p_identifier)) { - r_type = _type_from_variant(constants[p_identifier]); + r_type = _type_from_variant(constants[p_identifier], p_context); return true; } @@ -2345,7 +2370,7 @@ static bool _guess_identifier_type_from_base(GDScriptParser::CompletionContext & bool valid = false; Variant res = tmp.get(p_identifier, &valid); if (valid) { - r_type = _type_from_variant(res); + r_type = _type_from_variant(res, p_context); r_type.value = Variant(); r_type.type.is_constant = false; return true; @@ -3197,6 +3222,11 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c List<String> opts; p_owner->get_argument_options("get_node", 0, &opts); + bool for_unique_name = false; + if (completion_context.node != nullptr && completion_context.node->type == GDScriptParser::Node::GET_NODE && !static_cast<GDScriptParser::GetNodeNode *>(completion_context.node)->use_dollar) { + for_unique_name = true; + } + for (const String &E : opts) { r_forced = true; String opt = E.strip_edges(); @@ -3205,6 +3235,14 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c // or handle NodePaths which are valid identifiers and don't need quotes. opt = opt.unquote(); } + + if (for_unique_name) { + if (!opt.begins_with("%")) { + continue; + } + opt = opt.substr(1); + } + // The path needs quotes if it's not a valid identifier (with an exception // for "/" as path separator, which also doesn't require quotes). if (!opt.replace("/", "_").is_valid_identifier()) { @@ -3216,11 +3254,13 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c options.insert(option.display, option); } - // Get autoloads. - for (const KeyValue<StringName, ProjectSettings::AutoloadInfo> &E : ProjectSettings::get_singleton()->get_autoload_list()) { - String path = "/root/" + E.key; - ScriptLanguage::CodeCompletionOption option(path.quote(quote_style), ScriptLanguage::CODE_COMPLETION_KIND_NODE_PATH); - options.insert(option.display, option); + if (!for_unique_name) { + // Get autoloads. + for (const KeyValue<StringName, ProjectSettings::AutoloadInfo> &E : ProjectSettings::get_singleton()->get_autoload_list()) { + String path = "/root/" + E.key; + ScriptLanguage::CodeCompletionOption option(path.quote(quote_style), ScriptLanguage::CODE_COMPLETION_KIND_NODE_PATH); + options.insert(option.display, option); + } } } } break; @@ -3564,7 +3604,8 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co case GDScriptParser::COMPLETION_ASSIGN: case GDScriptParser::COMPLETION_CALL_ARGUMENTS: case GDScriptParser::COMPLETION_IDENTIFIER: - case GDScriptParser::COMPLETION_PROPERTY_METHOD: { + case GDScriptParser::COMPLETION_PROPERTY_METHOD: + case GDScriptParser::COMPLETION_SUBSCRIPT: { GDScriptParser::DataType base_type; if (context.current_class) { if (context.type != GDScriptParser::COMPLETION_SUPER_METHOD) { diff --git a/modules/gdscript/language_server/gdscript_text_document.cpp b/modules/gdscript/language_server/gdscript_text_document.cpp index 95b3be2811..e00b92b752 100644 --- a/modules/gdscript/language_server/gdscript_text_document.cpp +++ b/modules/gdscript/language_server/gdscript_text_document.cpp @@ -114,7 +114,7 @@ void GDScriptTextDocument::didSave(const Variant &p_param) { scr->update_exports(); ScriptEditor::get_singleton()->reload_scripts(true); ScriptEditor::get_singleton()->update_docs_from_script(scr); - ScriptEditor::get_singleton()->trigger_live_script_reload(); + ScriptEditor::get_singleton()->trigger_live_script_reload(scr->get_path()); } } diff --git a/modules/gdscript/tests/README.md b/modules/gdscript/tests/README.md index 361d586d32..cea251bab5 100644 --- a/modules/gdscript/tests/README.md +++ b/modules/gdscript/tests/README.md @@ -6,3 +6,44 @@ and output files. See the [Integration tests for GDScript documentation](https://docs.godotengine.org/en/latest/contributing/development/core_and_modules/unit_testing.html#integration-tests-for-gdscript) for information about creating and running GDScript integration tests. + +# GDScript Autocompletion tests + +The `script/completion` folder contains test for the GDScript autocompletion. + +Each test case consists of at least one `.gd` file, which contains the code, and one `.cfg` file, which contains expected results and configuration. Inside of the GDScript file the character `➡` represents the cursor position, at which autocompletion is invoked. + +The config file contains two section: + +`[input]` contains keys that configure the test environment. The following keys are possible: + +- `cs: boolean = false`: If `true`, the test will be skipped when running a non C# build. +- `use_single_quotes: boolean = false`: Configures the corresponding editor setting for the test. +- `scene: String`: Allows to specify a scene which is opened while autocompletion is performed. If this is not set the test runner will search for a `.tscn` file with the same basename as the GDScript file. If that isn't found either, autocompletion will behave as if no scene was opened. + +`[output]` specifies the expected results for the test. The following key are supported: + +- `include: Array`: An unordered list of suggestions that should be in the result. Each entry is one dictionary with the following keys: `display`, `insert_text`, `kind`, `location`, which correspond to the suggestion struct which is used in the code. The runner only tests against specified keys, so in most cases `display` will suffice. +- `exclude: Array`: An array of suggestions which should not be in the result. The entries take the same form as for `include`. +- `call_hint: String`: The expected call hint returned by autocompletion. +- `forced: boolean`: Whether autocompletion is expected to force opening a completion window. + +Tests will only test against entries in `[output]` that were specified. + +## Writing autocompletion tests + +To avoid failing edge cases a certain behaviour needs to be tested multiple times. Some things that tests should account for: + +- All possible types: Test with all possible types that apply to the tested behaviour. (For the last points testing against `SCRIPT` and `CLASS` should suffice. `CLASS` can be obtained through C#, `SCRIPT` through GDScript. Relying on autoloads to be of type `SCRIPT` is not good, since this might change in the future.) + + - `BUILTIN` + - `NATIVE` + - GDScripts (with `class_name` as well as `preload`ed) + - C# (as standin for all other language bindings) (with `class_name` as well as `preload`ed) + - Autoloads + +- Possible contexts: the completion might be placed in different places of the program. e.g: + - initializers of class members + - directly inside a suite + - assignments inside a suite + - as parameter to a call diff --git a/modules/gdscript/tests/scripts/completion/get_node/get_node_member_annotated.cfg b/modules/gdscript/tests/scripts/completion/get_node/get_node_member_annotated.cfg index 4edee46039..27e695d245 100644 --- a/modules/gdscript/tests/scripts/completion/get_node/get_node_member_annotated.cfg +++ b/modules/gdscript/tests/scripts/completion/get_node/get_node_member_annotated.cfg @@ -1,4 +1,4 @@ [output] -expected=[ +include=[ {"display": "autoplay"}, ] diff --git a/modules/gdscript/tests/test_completion.h b/modules/gdscript/tests/test_completion.h index abc34bd4bf..fd6b5321e6 100644 --- a/modules/gdscript/tests/test_completion.h +++ b/modules/gdscript/tests/test_completion.h @@ -128,19 +128,23 @@ static void test_directory(const String &p_dir) { EditorSettings::get_singleton()->set_setting("text_editor/completion/use_single_quotes", conf.get_value("input", "use_single_quotes", false)); List<Dictionary> include; - to_dict_list(conf.get_value("result", "include", Array()), include); + to_dict_list(conf.get_value("output", "include", Array()), include); List<Dictionary> exclude; - to_dict_list(conf.get_value("result", "exclude", Array()), exclude); + to_dict_list(conf.get_value("output", "exclude", Array()), exclude); List<ScriptLanguage::CodeCompletionOption> options; String call_hint; bool forced; Node *owner = nullptr; - if (dir->file_exists(next.get_basename() + ".tscn")) { - String project_path = "res://completion"; - Ref<PackedScene> scene = ResourceLoader::load(project_path.path_join(next.get_basename() + ".tscn"), "PackedScene"); + if (conf.has_section_key("input", "scene")) { + Ref<PackedScene> scene = ResourceLoader::load(conf.get_value("input", "scene"), "PackedScene"); + if (scene.is_valid()) { + owner = scene->instantiate(); + } + } else if (dir->file_exists(next.get_basename() + ".tscn")) { + Ref<PackedScene> scene = ResourceLoader::load(path.path_join(next.get_basename() + ".tscn"), "PackedScene"); if (scene.is_valid()) { owner = scene->instantiate(); } @@ -169,8 +173,8 @@ static void test_directory(const String &p_dir) { CHECK_MESSAGE(contains_excluded.is_empty(), "Autocompletion suggests illegal option '", contains_excluded, "' for '", path.path_join(next), "'."); CHECK(include.is_empty()); - String expected_call_hint = conf.get_value("result", "call_hint", call_hint); - bool expected_forced = conf.get_value("result", "forced", forced); + String expected_call_hint = conf.get_value("output", "call_hint", call_hint); + bool expected_forced = conf.get_value("output", "forced", forced); CHECK(expected_call_hint == call_hint); CHECK(expected_forced == forced); diff --git a/modules/gltf/structures/gltf_buffer_view.cpp b/modules/gltf/structures/gltf_buffer_view.cpp index d40ed69915..8588de0752 100644 --- a/modules/gltf/structures/gltf_buffer_view.cpp +++ b/modules/gltf/structures/gltf_buffer_view.cpp @@ -94,6 +94,7 @@ void GLTFBufferView::set_indices(bool p_indices) { } Vector<uint8_t> GLTFBufferView::load_buffer_view_data(const Ref<GLTFState> p_state) const { + ERR_FAIL_COND_V(p_state.is_null(), Vector<uint8_t>()); ERR_FAIL_COND_V_MSG(byte_stride > 0, Vector<uint8_t>(), "Buffer views with byte stride are not yet supported by this method."); const TypedArray<Vector<uint8_t>> &buffers = p_state->get_buffers(); ERR_FAIL_INDEX_V(buffer, buffers.size(), Vector<uint8_t>()); diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp index ac6977504a..ef24dc35ca 100644 --- a/modules/mono/csharp_script.cpp +++ b/modules/mono/csharp_script.cpp @@ -720,11 +720,22 @@ void CSharpLanguage::reload_all_scripts() { #endif } -void CSharpLanguage::reload_tool_script(const Ref<Script> &p_script, bool p_soft_reload) { - (void)p_script; // UNUSED - +void CSharpLanguage::reload_scripts(const Array &p_scripts, bool p_soft_reload) { CRASH_COND(!Engine::get_singleton()->is_editor_hint()); + bool has_csharp_script = false; + for (int i = 0; i < p_scripts.size(); ++i) { + Ref<CSharpScript> cs_script = p_scripts[i]; + if (cs_script.is_valid()) { + has_csharp_script = true; + break; + } + } + + if (!has_csharp_script) { + return; + } + #ifdef TOOLS_ENABLED get_godotsharp_editor()->get_node(NodePath("HotReloadAssemblyWatcher"))->call("RestartTimer"); #endif @@ -736,6 +747,12 @@ void CSharpLanguage::reload_tool_script(const Ref<Script> &p_script, bool p_soft #endif } +void CSharpLanguage::reload_tool_script(const Ref<Script> &p_script, bool p_soft_reload) { + Array scripts; + scripts.push_back(p_script); + reload_scripts(scripts, p_soft_reload); +} + #ifdef GD_MONO_HOT_RELOAD bool CSharpLanguage::is_assembly_reloading_needed() { ERR_FAIL_NULL_V(gdmono, false); @@ -1074,7 +1091,7 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { } // The script instance could not be instantiated or wasn't in the list of placeholders to replace. obj->set_script(scr); -#if DEBUG_ENABLED +#ifdef DEBUG_ENABLED // If we reached here, the instantiated script must be a placeholder. CRASH_COND(!obj->get_script_instance()->is_placeholder()); #endif @@ -2310,7 +2327,7 @@ void CSharpScript::reload_registered_script(Ref<CSharpScript> p_script) { p_script->_update_exports(); -#if TOOLS_ENABLED +#ifdef TOOLS_ENABLED // If the EditorFileSystem singleton is available, update the file; // otherwise, the file will be updated when the singleton becomes available. EditorFileSystem *efs = EditorFileSystem::get_singleton(); @@ -2666,7 +2683,7 @@ Error CSharpScript::reload(bool p_keep_state) { _update_exports(); -#if TOOLS_ENABLED +#ifdef TOOLS_ENABLED // If the EditorFileSystem singleton is available, update the file; // otherwise, the file will be updated when the singleton becomes available. EditorFileSystem *efs = EditorFileSystem::get_singleton(); diff --git a/modules/mono/csharp_script.h b/modules/mono/csharp_script.h index 41e8d63be1..310cb81929 100644 --- a/modules/mono/csharp_script.h +++ b/modules/mono/csharp_script.h @@ -478,6 +478,7 @@ public: /* TODO? */ void get_public_annotations(List<MethodInfo> *p_annotations) const override {} void reload_all_scripts() override; + void reload_scripts(const Array &p_scripts, bool p_soft_reload) override; void reload_tool_script(const Ref<Script> &p_script, bool p_soft_reload) override; /* LOADER FUNCTIONS */ diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk.sln b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk.sln index 03a7dc453c..9674626183 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk.sln +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk.sln @@ -6,6 +6,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Godot.SourceGenerators", "G EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Godot.SourceGenerators.Sample", "Godot.SourceGenerators.Sample\Godot.SourceGenerators.Sample.csproj", "{7297A614-8DF5-43DE-9EAD-99671B26BD1F}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Godot.SourceGenerators.Tests", "Godot.SourceGenerators.Tests\Godot.SourceGenerators.Tests.csproj", "{07E6D201-35C9-4463-9B29-D16621EA733D}" +EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GodotSharp", "..\..\glue\GodotSharp\GodotSharp\GodotSharp.csproj", "{AEBF0036-DA76-4341-B651-A3F2856AB2FA}" EndProject Global @@ -26,6 +28,10 @@ Global {7297A614-8DF5-43DE-9EAD-99671B26BD1F}.Debug|Any CPU.Build.0 = Debug|Any CPU {7297A614-8DF5-43DE-9EAD-99671B26BD1F}.Release|Any CPU.ActiveCfg = Release|Any CPU {7297A614-8DF5-43DE-9EAD-99671B26BD1F}.Release|Any CPU.Build.0 = Release|Any CPU + {07E6D201-35C9-4463-9B29-D16621EA733D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {07E6D201-35C9-4463-9B29-D16621EA733D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {07E6D201-35C9-4463-9B29-D16621EA733D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {07E6D201-35C9-4463-9B29-D16621EA733D}.Release|Any CPU.Build.0 = Release|Any CPU {AEBF0036-DA76-4341-B651-A3F2856AB2FA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {AEBF0036-DA76-4341-B651-A3F2856AB2FA}.Debug|Any CPU.Build.0 = Debug|Any CPU {AEBF0036-DA76-4341-B651-A3F2856AB2FA}.Release|Any CPU.ActiveCfg = Release|Any CPU diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/GlobalClass.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/GlobalClass.cs new file mode 100644 index 0000000000..b9f11908e1 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/GlobalClass.cs @@ -0,0 +1,14 @@ +namespace Godot.SourceGenerators.Sample; + +[GlobalClass] +public partial class CustomGlobalClass : GodotObject +{ +} + +// This doesn't works because global classes can't have any generic type parameter. +/* +[GlobalClass] +public partial class CustomGlobalClass<T> : Node +{ +} +*/ diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Godot.SourceGenerators.Sample.csproj b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Godot.SourceGenerators.Sample.csproj index 3f569ebac3..d0907c1cd4 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Godot.SourceGenerators.Sample.csproj +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Godot.SourceGenerators.Sample.csproj @@ -2,6 +2,7 @@ <PropertyGroup> <TargetFramework>net6.0</TargetFramework> + <LangVersion>11</LangVersion> </PropertyGroup> <PropertyGroup> diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/MustBeVariantSamples.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/MustBeVariantSamples.cs new file mode 100644 index 0000000000..1e06091e80 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/MustBeVariantSamples.cs @@ -0,0 +1,164 @@ +using System; +using Godot.Collections; +using Array = Godot.Collections.Array; + +namespace Godot.SourceGenerators.Sample; + +public class MustBeVariantMethods +{ + public void MustBeVariantMethodCalls() + { + Method<bool>(); + Method<char>(); + Method<sbyte>(); + Method<byte>(); + Method<short>(); + Method<ushort>(); + Method<int>(); + Method<uint>(); + Method<long>(); + Method<ulong>(); + Method<float>(); + Method<double>(); + Method<string>(); + Method<Vector2>(); + Method<Vector2I>(); + Method<Rect2>(); + Method<Rect2I>(); + Method<Transform2D>(); + Method<Vector3>(); + Method<Vector3I>(); + Method<Vector4>(); + Method<Vector4I>(); + Method<Basis>(); + Method<Quaternion>(); + Method<Transform3D>(); + Method<Projection>(); + Method<Aabb>(); + Method<Color>(); + Method<Plane>(); + Method<Callable>(); + Method<Signal>(); + Method<GodotObject>(); + Method<StringName>(); + Method<NodePath>(); + Method<Rid>(); + Method<Dictionary>(); + Method<Array>(); + Method<byte[]>(); + Method<int[]>(); + Method<long[]>(); + Method<float[]>(); + Method<double[]>(); + Method<string[]>(); + Method<Vector2[]>(); + Method<Vector3[]>(); + Method<Color[]>(); + Method<GodotObject[]>(); + Method<StringName[]>(); + Method<NodePath[]>(); + Method<Rid[]>(); + + // This call fails because generic type is not Variant-compatible. + //Method<object>(); + } + + public void Method<[MustBeVariant] T>() + { + } + + public void MustBeVariantClasses() + { + new ClassWithGenericVariant<bool>(); + new ClassWithGenericVariant<char>(); + new ClassWithGenericVariant<sbyte>(); + new ClassWithGenericVariant<byte>(); + new ClassWithGenericVariant<short>(); + new ClassWithGenericVariant<ushort>(); + new ClassWithGenericVariant<int>(); + new ClassWithGenericVariant<uint>(); + new ClassWithGenericVariant<long>(); + new ClassWithGenericVariant<ulong>(); + new ClassWithGenericVariant<float>(); + new ClassWithGenericVariant<double>(); + new ClassWithGenericVariant<string>(); + new ClassWithGenericVariant<Vector2>(); + new ClassWithGenericVariant<Vector2I>(); + new ClassWithGenericVariant<Rect2>(); + new ClassWithGenericVariant<Rect2I>(); + new ClassWithGenericVariant<Transform2D>(); + new ClassWithGenericVariant<Vector3>(); + new ClassWithGenericVariant<Vector3I>(); + new ClassWithGenericVariant<Vector4>(); + new ClassWithGenericVariant<Vector4I>(); + new ClassWithGenericVariant<Basis>(); + new ClassWithGenericVariant<Quaternion>(); + new ClassWithGenericVariant<Transform3D>(); + new ClassWithGenericVariant<Projection>(); + new ClassWithGenericVariant<Aabb>(); + new ClassWithGenericVariant<Color>(); + new ClassWithGenericVariant<Plane>(); + new ClassWithGenericVariant<Callable>(); + new ClassWithGenericVariant<Signal>(); + new ClassWithGenericVariant<GodotObject>(); + new ClassWithGenericVariant<StringName>(); + new ClassWithGenericVariant<NodePath>(); + new ClassWithGenericVariant<Rid>(); + new ClassWithGenericVariant<Dictionary>(); + new ClassWithGenericVariant<Array>(); + new ClassWithGenericVariant<byte[]>(); + new ClassWithGenericVariant<int[]>(); + new ClassWithGenericVariant<long[]>(); + new ClassWithGenericVariant<float[]>(); + new ClassWithGenericVariant<double[]>(); + new ClassWithGenericVariant<string[]>(); + new ClassWithGenericVariant<Vector2[]>(); + new ClassWithGenericVariant<Vector3[]>(); + new ClassWithGenericVariant<Color[]>(); + new ClassWithGenericVariant<GodotObject[]>(); + new ClassWithGenericVariant<StringName[]>(); + new ClassWithGenericVariant<NodePath[]>(); + new ClassWithGenericVariant<Rid[]>(); + + // This class fails because generic type is not Variant-compatible. + //new ClassWithGenericVariant<object>(); + } +} + +public class ClassWithGenericVariant<[MustBeVariant] T> +{ +} + +public class MustBeVariantAnnotatedMethods +{ + [GenericTypeAttribute<string>()] + public void MethodWithAttributeOk() + { + } + + // This method definition fails because generic type is not Variant-compatible. + /* + [GenericTypeAttribute<object>()] + public void MethodWithWrongAttribute() + { + } + */ +} + +[GenericTypeAttribute<string>()] +public class ClassVariantAnnotated +{ +} + +// This class definition fails because generic type is not Variant-compatible. +/* +[GenericTypeAttribute<object>()] +public class ClassNonVariantAnnotated +{ +} +*/ + +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)] +public class GenericTypeAttribute<[MustBeVariant] T> : Attribute +{ +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/CSharpAnalyzerVerifier.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/CSharpAnalyzerVerifier.cs new file mode 100644 index 0000000000..e3e7373b2e --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/CSharpAnalyzerVerifier.cs @@ -0,0 +1,56 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Testing; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Testing; +using Microsoft.CodeAnalysis.Testing.Verifiers; +using Microsoft.CodeAnalysis.Text; + +namespace Godot.SourceGenerators.Tests; + +public static class CSharpAnalyzerVerifier<TAnalyzer> +where TAnalyzer : DiagnosticAnalyzer, new() +{ + public class Test : CSharpAnalyzerTest<TAnalyzer, XUnitVerifier> + { + public Test() + { + ReferenceAssemblies = ReferenceAssemblies.Net.Net60; + + SolutionTransforms.Add((Solution solution, ProjectId projectId) => + { + Project project = + solution.GetProject(projectId)!.AddMetadataReference(Constants.GodotSharpAssembly + .CreateMetadataReference()); + + return project.Solution; + }); + } + } + + public static Task Verify(string sources, params DiagnosticResult[] expected) + { + return MakeVerifier(new string[] { sources }, expected).RunAsync(); + } + + public static Test MakeVerifier(ICollection<string> sources, params DiagnosticResult[] expected) + { + var verifier = new Test(); + + verifier.TestState.AnalyzerConfigFiles.Add(("/.globalconfig", $""" + is_global = true + build_property.GodotProjectDir = {Constants.ExecutingAssemblyPath} + """)); + + verifier.TestState.Sources.AddRange(sources.Select(source => + { + return (source, SourceText.From(File.ReadAllText(Path.Combine(Constants.SourceFolderPath, source)))); + })); + + verifier.ExpectedDiagnostics.AddRange(expected); + return verifier; + } +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/GlobalClassAnalyzerTests.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/GlobalClassAnalyzerTests.cs new file mode 100644 index 0000000000..74d6afceb3 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/GlobalClassAnalyzerTests.cs @@ -0,0 +1,20 @@ +using Xunit; + +namespace Godot.SourceGenerators.Tests; + +public class GlobalClassAnalyzerTests +{ + [Fact] + public async void GlobalClassMustDeriveFromGodotObjectTest() + { + const string GlobalClassGD0401 = "GlobalClass.GD0401.cs"; + await CSharpAnalyzerVerifier<GlobalClassAnalyzer>.Verify(GlobalClassGD0401); + } + + [Fact] + public async void GlobalClassMustNotBeGenericTest() + { + const string GlobalClassGD0402 = "GlobalClass.GD0402.cs"; + await CSharpAnalyzerVerifier<GlobalClassAnalyzer>.Verify(GlobalClassGD0402); + } +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/Godot.SourceGenerators.Tests.csproj b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/Godot.SourceGenerators.Tests.csproj index e39c14f049..13e54a543f 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/Godot.SourceGenerators.Tests.csproj +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/Godot.SourceGenerators.Tests.csproj @@ -17,6 +17,8 @@ <ItemGroup> <PackageReference Include="Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.XUnit" Version="1.1.1" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.1" /> + <PackageReference Include="Microsoft.CodeAnalysis.Testing.Verifiers.XUnit" Version="1.1.1" /> + <PackageReference Include="Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.XUnit" Version="1.1.1" /> <PackageReference Include="xunit" Version="2.4.2" /> <PackageReference Include="xunit.runner.visualstudio" Version="2.4.5"> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/MustBeVariantAnalyzerTests.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/MustBeVariantAnalyzerTests.cs new file mode 100644 index 0000000000..62c602efbb --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/MustBeVariantAnalyzerTests.cs @@ -0,0 +1,20 @@ +using Xunit; + +namespace Godot.SourceGenerators.Tests; + +public class MustBeVariantAnalyzerTests +{ + [Fact] + public async void GenericTypeArgumentMustBeVariantTest() + { + const string MustBeVariantGD0301 = "MustBeVariant.GD0301.cs"; + await CSharpAnalyzerVerifier<MustBeVariantAnalyzer>.Verify(MustBeVariantGD0301); + } + + [Fact] + public async void GenericTypeParameterMustBeVariantAnnotatedTest() + { + const string MustBeVariantGD0302 = "MustBeVariant.GD0302.cs"; + await CSharpAnalyzerVerifier<MustBeVariantAnalyzer>.Verify(MustBeVariantGD0302); + } +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/GlobalClass.GD0401.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/GlobalClass.GD0401.cs new file mode 100644 index 0000000000..6e6d3a6f39 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/GlobalClass.GD0401.cs @@ -0,0 +1,22 @@ +using Godot; + +// This works because it inherits from GodotObject. +[GlobalClass] +public partial class CustomGlobalClass1 : GodotObject +{ + +} + +// This works because it inherits from an object that inherits from GodotObject +[GlobalClass] +public partial class CustomGlobalClass2 : Node +{ + +} + +// This raises a GD0401 diagnostic error: global classes must inherit from GodotObject +{|GD0401:[GlobalClass] +public partial class CustomGlobalClass3 +{ + +}|} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/GlobalClass.GD0402.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/GlobalClass.GD0402.cs new file mode 100644 index 0000000000..1c0a169841 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/GlobalClass.GD0402.cs @@ -0,0 +1,15 @@ +using Godot; + +// This works because it inherits from GodotObject and it doesn't have any generic type parameter. +[GlobalClass] +public partial class CustomGlobalClass : GodotObject +{ + +} + +// This raises a GD0402 diagnostic error: global classes can't have any generic type parameter +{|GD0402:[GlobalClass] +public partial class CustomGlobalClass<T> : GodotObject +{ + +}|} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/MustBeVariant.GD0301.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/MustBeVariant.GD0301.cs new file mode 100644 index 0000000000..031039cba1 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/MustBeVariant.GD0301.cs @@ -0,0 +1,71 @@ +using System; +using Godot; +using Godot.Collections; +using Array = Godot.Collections.Array; + +public class MustBeVariantGD0301 +{ + public void MethodCallsError() + { + // This raises a GD0301 diagnostic error: object is not Variant (and Method<T> requires a variant generic type). + Method<{|GD0301:object|}>(); + } + public void MethodCallsOk() + { + // All these calls are valid because they are Variant types. + Method<bool>(); + Method<char>(); + Method<sbyte>(); + Method<byte>(); + Method<short>(); + Method<ushort>(); + Method<int>(); + Method<uint>(); + Method<long>(); + Method<ulong>(); + Method<float>(); + Method<double>(); + Method<string>(); + Method<Vector2>(); + Method<Vector2I>(); + Method<Rect2>(); + Method<Rect2I>(); + Method<Transform2D>(); + Method<Vector3>(); + Method<Vector3I>(); + Method<Vector4>(); + Method<Vector4I>(); + Method<Basis>(); + Method<Quaternion>(); + Method<Transform3D>(); + Method<Projection>(); + Method<Aabb>(); + Method<Color>(); + Method<Plane>(); + Method<Callable>(); + Method<Signal>(); + Method<GodotObject>(); + Method<StringName>(); + Method<NodePath>(); + Method<Rid>(); + Method<Dictionary>(); + Method<Array>(); + Method<byte[]>(); + Method<int[]>(); + Method<long[]>(); + Method<float[]>(); + Method<double[]>(); + Method<string[]>(); + Method<Vector2[]>(); + Method<Vector3[]>(); + Method<Color[]>(); + Method<GodotObject[]>(); + Method<StringName[]>(); + Method<NodePath[]>(); + Method<Rid[]>(); + } + + public void Method<[MustBeVariant] T>() + { + } +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/MustBeVariant.GD0302.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/MustBeVariant.GD0302.cs new file mode 100644 index 0000000000..ce182e8c62 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/MustBeVariant.GD0302.cs @@ -0,0 +1,27 @@ +using Godot; + +public class MustBeVariantGD0302 +{ + public void MethodOk<[MustBeVariant] T>() + { + // T is guaranteed to be a Variant-compatible type because it's annotated with the [MustBeVariant] attribute, so it can be used here. + new ExampleClass<T>(); + Method<T>(); + } + + public void MethodFail<T>() + { + // These two calls raise a GD0302 diagnostic error: T is not valid here because it may not a Variant type and method call and class require it. + new ExampleClass<{|GD0302:T|}>(); + Method<{|GD0302:T|}>(); + } + + public void Method<[MustBeVariant] T>() + { + } +} + +public class ExampleClass<[MustBeVariant] T> +{ + +} diff --git a/modules/mono/editor/editor_internal_calls.cpp b/modules/mono/editor/editor_internal_calls.cpp index 322078423f..05dacd28fb 100644 --- a/modules/mono/editor/editor_internal_calls.cpp +++ b/modules/mono/editor/editor_internal_calls.cpp @@ -148,7 +148,7 @@ void godot_icall_Internal_ReloadAssemblies(bool p_soft_reload) { } void godot_icall_Internal_EditorDebuggerNodeReloadScripts() { - EditorDebuggerNode::get_singleton()->reload_scripts(); + EditorDebuggerNode::get_singleton()->reload_all_scripts(); } bool godot_icall_Internal_ScriptEditorEdit(Resource *p_resource, int32_t p_line, int32_t p_col, bool p_grab_focus) { @@ -175,7 +175,7 @@ void godot_icall_Internal_EditorPlugin_AddControlToEditorRunBar(Control *p_contr void godot_icall_Internal_ScriptEditorDebugger_ReloadScripts() { EditorDebuggerNode *ed = EditorDebuggerNode::get_singleton(); if (ed) { - ed->reload_scripts(); + ed->reload_all_scripts(); } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Aabb.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Aabb.cs index 57b5b09ebb..63af6ee6e8 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Aabb.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Aabb.cs @@ -98,11 +98,11 @@ namespace Godot Vector3 dstMax = with._position + with._size; return srcMin.X <= dstMin.X && - srcMax.X > dstMax.X && + srcMax.X >= dstMax.X && srcMin.Y <= dstMin.Y && - srcMax.Y > dstMax.Y && + srcMax.Y >= dstMax.Y && srcMin.Z <= dstMin.Z && - srcMax.Z > dstMax.Z; + srcMax.Z >= dstMax.Z; } /// <summary> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs index 71a35ab809..cf4ac45a9f 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs @@ -123,8 +123,8 @@ namespace Godot public readonly bool Encloses(Rect2 b) { return b._position.X >= _position.X && b._position.Y >= _position.Y && - b._position.X + b._size.X < _position.X + _size.X && - b._position.Y + b._size.Y < _position.Y + _size.Y; + b._position.X + b._size.X <= _position.X + _size.X && + b._position.Y + b._size.Y <= _position.Y + _size.Y; } /// <summary> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2I.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2I.cs index ef7e9eacd8..58560df0c5 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2I.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2I.cs @@ -113,8 +113,8 @@ namespace Godot public readonly bool Encloses(Rect2I b) { return b._position.X >= _position.X && b._position.Y >= _position.Y && - b._position.X + b._size.X < _position.X + _size.X && - b._position.Y + b._size.Y < _position.Y + _size.Y; + b._position.X + b._size.X <= _position.X + _size.X && + b._position.Y + b._size.Y <= _position.Y + _size.Y; } /// <summary> diff --git a/modules/mono/glue/runtime_interop.cpp b/modules/mono/glue/runtime_interop.cpp index 3518507f8c..0089e9c2a2 100644 --- a/modules/mono/glue/runtime_interop.cpp +++ b/modules/mono/glue/runtime_interop.cpp @@ -316,7 +316,7 @@ void godotsharp_internal_new_csharp_script(Ref<CSharpScript> *r_dest) { } void godotsharp_internal_editor_file_system_update_file(const String *p_script_path) { -#if TOOLS_ENABLED +#ifdef TOOLS_ENABLED // If the EditorFileSystem singleton is available, update the file; // otherwise, the file will be updated when the singleton becomes available. EditorFileSystem *efs = EditorFileSystem::get_singleton(); diff --git a/modules/mono/utils/path_utils.cpp b/modules/mono/utils/path_utils.cpp index aa97534675..ee17a668d7 100644 --- a/modules/mono/utils/path_utils.cpp +++ b/modules/mono/utils/path_utils.cpp @@ -152,7 +152,7 @@ String realpath(const String &p_path) { } return result.simplify_path(); -#elif UNIX_ENABLED +#elif defined(UNIX_ENABLED) char *resolved_path = ::realpath(p_path.utf8().get_data(), nullptr); if (!resolved_path) { diff --git a/modules/openxr/doc_classes/OpenXRHand.xml b/modules/openxr/doc_classes/OpenXRHand.xml index eb7decd30d..1c4da83138 100644 --- a/modules/openxr/doc_classes/OpenXRHand.xml +++ b/modules/openxr/doc_classes/OpenXRHand.xml @@ -7,10 +7,14 @@ This node enables OpenXR's hand tracking functionality. The node should be a child node of an [XROrigin3D] node, tracking will update its position to the player's tracked hand Palm joint location (the center of the middle finger's metacarpal bone). This node also updates the skeleton of a properly skinned hand or avatar model. If the skeleton is a hand (one of the hand bones is the root node of the skeleton), then the skeleton will be placed relative to the hand palm location and the hand mesh and skeleton should be children of the OpenXRHand node. If the hand bones are part of a full skeleton, then the root of the hand will keep its location with the assumption that IK is used to position the hand and arm. + By default the skeleton hand bones are repositioned to match the size of the tracked hand. To preserve the modeled bone sizes change [member bone_update] to apply rotation only. </description> <tutorials> </tutorials> <members> + <member name="bone_update" type="int" setter="set_bone_update" getter="get_bone_update" enum="OpenXRHand.BoneUpdate" default="0"> + Specify the type of updates to perform on the bone. + </member> <member name="hand" type="int" setter="set_hand" getter="get_hand" enum="OpenXRHand.Hands" default="0"> Specifies whether this node tracks the left or right hand of the player. </member> @@ -52,5 +56,14 @@ <constant name="SKELETON_RIG_MAX" value="2" enum="SkeletonRig"> Maximum supported hands. </constant> + <constant name="BONE_UPDATE_FULL" value="0" enum="BoneUpdate"> + The skeletons bones are fully updated (both position and rotation) to match the tracked bones. + </constant> + <constant name="BONE_UPDATE_ROTATION_ONLY" value="1" enum="BoneUpdate"> + The skeletons bones are only rotated to align with the tracked bones, preserving bone length. + </constant> + <constant name="BONE_UPDATE_MAX" value="2" enum="BoneUpdate"> + Maximum supported bone update mode. + </constant> </constants> </class> diff --git a/modules/openxr/extensions/openxr_fb_update_swapchain_extension.cpp b/modules/openxr/extensions/openxr_fb_update_swapchain_extension.cpp index 1289183ea4..c3f692185b 100644 --- a/modules/openxr/extensions/openxr_fb_update_swapchain_extension.cpp +++ b/modules/openxr/extensions/openxr_fb_update_swapchain_extension.cpp @@ -30,7 +30,7 @@ #include "openxr_fb_update_swapchain_extension.h" -// always include this as late as possible +// Always include this as late as possible. #include "../openxr_platform_inc.h" OpenXRFBUpdateSwapchainExtension *OpenXRFBUpdateSwapchainExtension::singleton = nullptr; diff --git a/modules/openxr/extensions/openxr_opengl_extension.h b/modules/openxr/extensions/openxr_opengl_extension.h index 3b0aa0bce9..5f529829a7 100644 --- a/modules/openxr/extensions/openxr_opengl_extension.h +++ b/modules/openxr/extensions/openxr_opengl_extension.h @@ -39,7 +39,7 @@ #include "core/templates/vector.h" -// always include this as late as possible +// Always include this as late as possible. #include "../openxr_platform_inc.h" class OpenXROpenGLExtension : public OpenXRGraphicsExtensionWrapper { @@ -65,9 +65,9 @@ private: #ifdef WIN32 static XrGraphicsBindingOpenGLWin32KHR graphics_binding_gl; -#elif ANDROID_ENABLED +#elif defined(ANDROID_ENABLED) static XrGraphicsBindingOpenGLESAndroidKHR graphics_binding_gl; -#else +#else // Linux/X11 static XrGraphicsBindingOpenGLXlibKHR graphics_binding_gl; #endif diff --git a/modules/openxr/extensions/openxr_vulkan_extension.h b/modules/openxr/extensions/openxr_vulkan_extension.h index f31621fda0..86c0f327dd 100644 --- a/modules/openxr/extensions/openxr_vulkan_extension.h +++ b/modules/openxr/extensions/openxr_vulkan_extension.h @@ -37,7 +37,7 @@ #include "core/templates/vector.h" -// always include this as late as possible +// Always include this as late as possible. #include "../openxr_platform_inc.h" class OpenXRVulkanExtension : public OpenXRGraphicsExtensionWrapper, VulkanHooks { diff --git a/modules/openxr/scene/openxr_hand.cpp b/modules/openxr/scene/openxr_hand.cpp index 8ce33b55c3..2a4104f6ee 100644 --- a/modules/openxr/scene/openxr_hand.cpp +++ b/modules/openxr/scene/openxr_hand.cpp @@ -49,10 +49,14 @@ void OpenXRHand::_bind_methods() { ClassDB::bind_method(D_METHOD("set_skeleton_rig", "skeleton_rig"), &OpenXRHand::set_skeleton_rig); ClassDB::bind_method(D_METHOD("get_skeleton_rig"), &OpenXRHand::get_skeleton_rig); + ClassDB::bind_method(D_METHOD("set_bone_update", "bone_update"), &OpenXRHand::set_bone_update); + ClassDB::bind_method(D_METHOD("get_bone_update"), &OpenXRHand::get_bone_update); + ADD_PROPERTY(PropertyInfo(Variant::INT, "hand", PROPERTY_HINT_ENUM, "Left,Right"), "set_hand", "get_hand"); ADD_PROPERTY(PropertyInfo(Variant::INT, "motion_range", PROPERTY_HINT_ENUM, "Unobstructed,Conform to controller"), "set_motion_range", "get_motion_range"); ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "hand_skeleton", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Skeleton3D"), "set_hand_skeleton", "get_hand_skeleton"); ADD_PROPERTY(PropertyInfo(Variant::INT, "skeleton_rig", PROPERTY_HINT_ENUM, "OpenXR,Humanoid"), "set_skeleton_rig", "get_skeleton_rig"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "bone_update", PROPERTY_HINT_ENUM, "Full,Rotation Only"), "set_bone_update", "get_bone_update"); BIND_ENUM_CONSTANT(HAND_LEFT); BIND_ENUM_CONSTANT(HAND_RIGHT); @@ -65,6 +69,10 @@ void OpenXRHand::_bind_methods() { BIND_ENUM_CONSTANT(SKELETON_RIG_OPENXR); BIND_ENUM_CONSTANT(SKELETON_RIG_HUMANOID); BIND_ENUM_CONSTANT(SKELETON_RIG_MAX); + + BIND_ENUM_CONSTANT(BONE_UPDATE_FULL); + BIND_ENUM_CONSTANT(BONE_UPDATE_ROTATION_ONLY); + BIND_ENUM_CONSTANT(BONE_UPDATE_MAX); } OpenXRHand::OpenXRHand() { @@ -134,6 +142,16 @@ OpenXRHand::SkeletonRig OpenXRHand::get_skeleton_rig() const { return skeleton_rig; } +void OpenXRHand::set_bone_update(BoneUpdate p_bone_update) { + ERR_FAIL_INDEX(p_bone_update, BONE_UPDATE_MAX); + + bone_update = p_bone_update; +} + +OpenXRHand::BoneUpdate OpenXRHand::get_bone_update() const { + return bone_update; +} + Skeleton3D *OpenXRHand::get_skeleton() { if (!has_node(hand_skeleton)) { return nullptr; @@ -346,8 +364,12 @@ void OpenXRHand::_update_skeleton() { const Quaternion q = inv_quaternions[parent_joint] * quaternions[joint]; const Vector3 p = inv_quaternions[parent_joint].xform(positions[joint] - positions[parent_joint]); - // and set our pose - skeleton->set_bone_pose_position(joints[joint].bone, p); + // Update the bone position if enabled by update mode. + if (bone_update == BONE_UPDATE_FULL) { + skeleton->set_bone_pose_position(joints[joint].bone, p); + } + + // Always update the bone rotation. skeleton->set_bone_pose_rotation(joints[joint].bone, q); } diff --git a/modules/openxr/scene/openxr_hand.h b/modules/openxr/scene/openxr_hand.h index 14eb893bcc..4c77e7277c 100644 --- a/modules/openxr/scene/openxr_hand.h +++ b/modules/openxr/scene/openxr_hand.h @@ -61,6 +61,12 @@ public: SKELETON_RIG_MAX }; + enum BoneUpdate { + BONE_UPDATE_FULL, + BONE_UPDATE_ROTATION_ONLY, + BONE_UPDATE_MAX + }; + private: struct JointData { int bone = -1; @@ -74,6 +80,7 @@ private: MotionRange motion_range = MOTION_RANGE_UNOBSTRUCTED; NodePath hand_skeleton; SkeletonRig skeleton_rig = SKELETON_RIG_OPENXR; + BoneUpdate bone_update = BONE_UPDATE_FULL; JointData joints[XR_HAND_JOINT_COUNT_EXT]; @@ -101,11 +108,15 @@ public: void set_skeleton_rig(SkeletonRig p_skeleton_rig); SkeletonRig get_skeleton_rig() const; + void set_bone_update(BoneUpdate p_bone_update); + BoneUpdate get_bone_update() const; + void _notification(int p_what); }; VARIANT_ENUM_CAST(OpenXRHand::Hands) VARIANT_ENUM_CAST(OpenXRHand::MotionRange) VARIANT_ENUM_CAST(OpenXRHand::SkeletonRig) +VARIANT_ENUM_CAST(OpenXRHand::BoneUpdate) #endif // OPENXR_HAND_H diff --git a/modules/svg/register_types.cpp b/modules/svg/register_types.cpp index 7b61749f61..82d816d833 100644 --- a/modules/svg/register_types.cpp +++ b/modules/svg/register_types.cpp @@ -34,6 +34,12 @@ #include <thorvg.h> +#ifdef THREADS_ENABLED +#define TVG_THREADS 1 +#else +#define TVG_THREADS 0 +#endif + static Ref<ImageLoaderSVG> image_loader_svg; void initialize_svg_module(ModuleInitializationLevel p_level) { @@ -42,7 +48,8 @@ void initialize_svg_module(ModuleInitializationLevel p_level) { } tvg::CanvasEngine tvgEngine = tvg::CanvasEngine::Sw; - if (tvg::Initializer::init(tvgEngine, 1) != tvg::Result::Success) { + + if (tvg::Initializer::init(tvgEngine, TVG_THREADS) != tvg::Result::Success) { return; } diff --git a/platform/linuxbsd/x11/display_server_x11.cpp b/platform/linuxbsd/x11/display_server_x11.cpp index d8a81266d0..2fff4ce32c 100644 --- a/platform/linuxbsd/x11/display_server_x11.cpp +++ b/platform/linuxbsd/x11/display_server_x11.cpp @@ -2010,7 +2010,7 @@ void DisplayServerX11::window_set_transient(WindowID p_window, WindowID p_parent // RevertToPointerRoot is used to make sure we don't lose all focus in case // a subwindow and its parent are both destroyed. if (!wd_window.no_focus && !wd_window.is_popup && wd_window.focused) { - if ((xwa.map_state == IsViewable) && !wd_parent.no_focus && !wd_window.is_popup) { + if ((xwa.map_state == IsViewable) && !wd_parent.no_focus && !wd_window.is_popup && _window_focus_check()) { XSetInputFocus(x11_display, wd_parent.x11_window, RevertToPointerRoot, CurrentTime); } } @@ -2950,7 +2950,7 @@ void DisplayServerX11::window_set_ime_active(const bool p_active, WindowID p_win XWindowAttributes xwa; XSync(x11_display, False); XGetWindowAttributes(x11_display, wd.x11_xim_window, &xwa); - if (xwa.map_state == IsViewable) { + if (xwa.map_state == IsViewable && _window_focus_check()) { XSetInputFocus(x11_display, wd.x11_xim_window, RevertToParent, CurrentTime); } XSetICFocus(wd.xic); @@ -4233,6 +4233,22 @@ bool DisplayServerX11::mouse_process_popups() { return closed; } +bool DisplayServerX11::_window_focus_check() { + Window focused_window; + int focus_ret_state; + XGetInputFocus(x11_display, &focused_window, &focus_ret_state); + + bool has_focus = false; + for (const KeyValue<int, DisplayServerX11::WindowData> &wid : windows) { + if (wid.value.x11_window == focused_window) { + has_focus = true; + break; + } + } + + return has_focus; +} + void DisplayServerX11::process_events() { _THREAD_SAFE_METHOD_ @@ -4504,7 +4520,7 @@ void DisplayServerX11::process_events() { // Set focus when menu window is started. // RevertToPointerRoot is used to make sure we don't lose all focus in case // a subwindow and its parent are both destroyed. - if ((xwa.map_state == IsViewable) && !wd.no_focus && !wd.is_popup) { + if ((xwa.map_state == IsViewable) && !wd.no_focus && !wd.is_popup && _window_focus_check()) { XSetInputFocus(x11_display, wd.x11_window, RevertToPointerRoot, CurrentTime); } @@ -4680,7 +4696,7 @@ void DisplayServerX11::process_events() { // Set focus when menu window is re-used. // RevertToPointerRoot is used to make sure we don't lose all focus in case // a subwindow and its parent are both destroyed. - if ((xwa.map_state == IsViewable) && !wd.no_focus && !wd.is_popup) { + if ((xwa.map_state == IsViewable) && !wd.no_focus && !wd.is_popup && _window_focus_check()) { XSetInputFocus(x11_display, wd.x11_window, RevertToPointerRoot, CurrentTime); } diff --git a/platform/linuxbsd/x11/display_server_x11.h b/platform/linuxbsd/x11/display_server_x11.h index 378d8bb407..3b362e5c22 100644 --- a/platform/linuxbsd/x11/display_server_x11.h +++ b/platform/linuxbsd/x11/display_server_x11.h @@ -354,6 +354,7 @@ class DisplayServerX11 : public DisplayServer { Context context = CONTEXT_ENGINE; WindowID _get_focused_window_or_popup() const; + bool _window_focus_check(); void _send_window_event(const WindowData &wd, WindowEvent p_event); static void _dispatch_input_events(const Ref<InputEvent> &p_event); diff --git a/platform/macos/detect.py b/platform/macos/detect.py index 12db2690de..bc14d233bb 100644 --- a/platform/macos/detect.py +++ b/platform/macos/detect.py @@ -67,21 +67,30 @@ def get_mvk_sdk_path(): if not os.path.exists(dirname): return "" - ver_file = "0.0.0.0" - ver_num = ver_parse(ver_file) - + ver_num = ver_parse("0.0.0.0") files = os.listdir(dirname) + lib_name_out = dirname for file in files: if os.path.isdir(os.path.join(dirname, file)): ver_comp = ver_parse(file) - lib_name = os.path.join( - os.path.join(dirname, file), "MoltenVK/MoltenVK.xcframework/macos-arm64_x86_64/libMoltenVK.a" - ) - if os.path.isfile(lib_name) and ver_comp > ver_num: - ver_num = ver_comp - ver_file = file + if ver_comp > ver_num: + # Try new SDK location. + lib_name = os.path.join( + os.path.join(dirname, file), "macOS/lib/MoltenVK.xcframework/macos-arm64_x86_64/" + ) + if os.path.isfile(os.path.join(lib_name, "libMoltenVK.a")): + ver_num = ver_comp + lib_name_out = lib_name + else: + # Try old SDK location. + lib_name = os.path.join( + os.path.join(dirname, file), "MoltenVK/MoltenVK.xcframework/macos-arm64_x86_64/" + ) + if os.path.isfile(os.path.join(lib_name, "libMoltenVK.a")): + ver_num = ver_comp + lib_name_out = lib_name - return os.path.join(os.path.join(dirname, ver_file), "MoltenVK/MoltenVK.xcframework/macos-arm64_x86_64/") + return lib_name_out def configure(env: "Environment"): @@ -272,6 +281,12 @@ def configure(env: "Environment"): mvk_list.insert( 0, os.path.join( + os.path.expanduser(env["vulkan_sdk_path"]), "macOS/lib/MoltenVK.xcframework/macos-arm64_x86_64/" + ), + ) + mvk_list.insert( + 0, + os.path.join( os.path.expanduser(env["vulkan_sdk_path"]), "MoltenVK/MoltenVK.xcframework/macos-arm64_x86_64/" ), ) diff --git a/platform/web/.eslintrc.html.js b/platform/web/.eslintrc.html.js index 5cb8de360a..8c9a3d83da 100644 --- a/platform/web/.eslintrc.html.js +++ b/platform/web/.eslintrc.html.js @@ -15,5 +15,7 @@ module.exports = { "Godot": true, "Engine": true, "$GODOT_CONFIG": true, + "$GODOT_THREADS_ENABLED": true, + "___GODOT_THREADS_ENABLED___": true, }, }; diff --git a/platform/web/SCsub b/platform/web/SCsub index 1af0642554..3e0cc9ac4a 100644 --- a/platform/web/SCsub +++ b/platform/web/SCsub @@ -13,7 +13,7 @@ if "serve" in COMMAND_LINE_TARGETS or "run" in COMMAND_LINE_TARGETS: except Exception: print("GODOT_WEB_TEST_PORT must be a valid integer") sys.exit(255) - serve(env.Dir("#bin/.web_zip").abspath, port, "run" in COMMAND_LINE_TARGETS) + serve(env.Dir(env.GetTemplateZipPath()).abspath, port, "run" in COMMAND_LINE_TARGETS) sys.exit(0) web_files = [ @@ -95,7 +95,7 @@ engine = [ "js/engine/engine.js", ] externs = [env.File("#platform/web/js/engine/engine.externs.js")] -js_engine = env.CreateEngineFile("#bin/godot${PROGSUFFIX}.engine.js", engine, externs) +js_engine = env.CreateEngineFile("#bin/godot${PROGSUFFIX}.engine.js", engine, externs, env["threads"]) env.Depends(js_engine, externs) wrap_list = [ diff --git a/platform/web/audio_driver_web.cpp b/platform/web/audio_driver_web.cpp index 1298d28ebf..ec3c22bf7c 100644 --- a/platform/web/audio_driver_web.cpp +++ b/platform/web/audio_driver_web.cpp @@ -30,6 +30,8 @@ #include "audio_driver_web.h" +#include "godot_audio.h" + #include "core/config/project_settings.h" #include <emscripten.h> @@ -184,6 +186,8 @@ Error AudioDriverWeb::input_stop() { return OK; } +#ifdef THREADS_ENABLED + /// AudioWorkletNode implementation (threads) void AudioDriverWorklet::_audio_thread_func(void *p_data) { AudioDriverWorklet *driver = static_cast<AudioDriverWorklet *>(p_data); @@ -245,3 +249,51 @@ void AudioDriverWorklet::finish_driver() { quit = true; // Ask thread to quit. thread.wait_to_finish(); } + +#else // No threads. + +/// AudioWorkletNode implementation (no threads) +AudioDriverWorklet *AudioDriverWorklet::singleton = nullptr; + +Error AudioDriverWorklet::create(int &p_buffer_size, int p_channels) { + if (!godot_audio_has_worklet()) { + return ERR_UNAVAILABLE; + } + return (Error)godot_audio_worklet_create(p_channels); +} + +void AudioDriverWorklet::start(float *p_out_buf, int p_out_buf_size, float *p_in_buf, int p_in_buf_size) { + _audio_driver_process(); + godot_audio_worklet_start_no_threads(p_out_buf, p_out_buf_size, &_process_callback, p_in_buf, p_in_buf_size, &_capture_callback); +} + +void AudioDriverWorklet::_process_callback(int p_pos, int p_samples) { + AudioDriverWorklet *driver = AudioDriverWorklet::get_singleton(); + driver->_audio_driver_process(p_pos, p_samples); +} + +void AudioDriverWorklet::_capture_callback(int p_pos, int p_samples) { + AudioDriverWorklet *driver = AudioDriverWorklet::get_singleton(); + driver->_audio_driver_capture(p_pos, p_samples); +} + +/// ScriptProcessorNode implementation +AudioDriverScriptProcessor *AudioDriverScriptProcessor::singleton = nullptr; + +void AudioDriverScriptProcessor::_process_callback() { + AudioDriverScriptProcessor::get_singleton()->_audio_driver_capture(); + AudioDriverScriptProcessor::get_singleton()->_audio_driver_process(); +} + +Error AudioDriverScriptProcessor::create(int &p_buffer_samples, int p_channels) { + if (!godot_audio_has_script_processor()) { + return ERR_UNAVAILABLE; + } + return (Error)godot_audio_script_create(&p_buffer_samples, p_channels); +} + +void AudioDriverScriptProcessor::start(float *p_out_buf, int p_out_buf_size, float *p_in_buf, int p_in_buf_size) { + godot_audio_script_start(p_in_buf, p_in_buf_size, p_out_buf, p_out_buf_size, &_process_callback); +} + +#endif // THREADS_ENABLED diff --git a/platform/web/audio_driver_web.h b/platform/web/audio_driver_web.h index 12a61746c3..df88d0a94c 100644 --- a/platform/web/audio_driver_web.h +++ b/platform/web/audio_driver_web.h @@ -90,6 +90,7 @@ public: AudioDriverWeb() {} }; +#ifdef THREADS_ENABLED class AudioDriverWorklet : public AudioDriverWeb { private: enum { @@ -120,4 +121,54 @@ public: virtual void unlock() override; }; +#else + +class AudioDriverWorklet : public AudioDriverWeb { +private: + static void _process_callback(int p_pos, int p_samples); + static void _capture_callback(int p_pos, int p_samples); + + static AudioDriverWorklet *singleton; + +protected: + virtual Error create(int &p_buffer_size, int p_output_channels) override; + virtual void start(float *p_out_buf, int p_out_buf_size, float *p_in_buf, int p_in_buf_size) override; + +public: + virtual const char *get_name() const override { + return "AudioWorklet"; + } + + virtual void lock() override {} + virtual void unlock() override {} + + static AudioDriverWorklet *get_singleton() { return singleton; } + + AudioDriverWorklet() { singleton = this; } +}; + +class AudioDriverScriptProcessor : public AudioDriverWeb { +private: + static void _process_callback(); + + static AudioDriverScriptProcessor *singleton; + +protected: + virtual Error create(int &p_buffer_size, int p_output_channels) override; + virtual void start(float *p_out_buf, int p_out_buf_size, float *p_in_buf, int p_in_buf_size) override; + virtual void finish_driver() override; + +public: + virtual const char *get_name() const override { return "ScriptProcessor"; } + + virtual void lock() override {} + virtual void unlock() override {} + + static AudioDriverScriptProcessor *get_singleton() { return singleton; } + + AudioDriverScriptProcessor() { singleton = this; } +}; + +#endif // THREADS_ENABLED + #endif // AUDIO_DRIVER_WEB_H diff --git a/platform/web/detect.py b/platform/web/detect.py index 53fae67f6a..bce03eb79e 100644 --- a/platform/web/detect.py +++ b/platform/web/detect.py @@ -8,6 +8,7 @@ from emscripten_helpers import ( add_js_pre, add_js_externs, create_template_zip, + get_template_zip_path, ) from methods import get_compiler_version from SCons.Util import WhereIs @@ -161,6 +162,9 @@ def configure(env: "Environment"): # Add method that joins/compiles our Engine files. env.AddMethod(create_engine_file, "CreateEngineFile") + # Add method for getting the final zip path + env.AddMethod(get_template_zip_path, "GetTemplateZipPath") + # Add method for creating the final zip file env.AddMethod(create_template_zip, "CreateTemplateZip") @@ -209,13 +213,17 @@ def configure(env: "Environment"): stack_size_opt = "STACK_SIZE" if cc_semver >= (3, 1, 25) else "TOTAL_STACK" env.Append(LINKFLAGS=["-s", "%s=%sKB" % (stack_size_opt, env["stack_size"])]) - # Thread support (via SharedArrayBuffer). - env.Append(CPPDEFINES=["PTHREAD_NO_RENAME"]) - env.Append(CCFLAGS=["-s", "USE_PTHREADS=1"]) - env.Append(LINKFLAGS=["-s", "USE_PTHREADS=1"]) - env.Append(LINKFLAGS=["-s", "DEFAULT_PTHREAD_STACK_SIZE=%sKB" % env["default_pthread_stack_size"]]) - env.Append(LINKFLAGS=["-s", "PTHREAD_POOL_SIZE=8"]) - env.Append(LINKFLAGS=["-s", "WASM_MEM_MAX=2048MB"]) + if env["threads"]: + # Thread support (via SharedArrayBuffer). + env.Append(CPPDEFINES=["PTHREAD_NO_RENAME"]) + env.Append(CCFLAGS=["-s", "USE_PTHREADS=1"]) + env.Append(LINKFLAGS=["-s", "USE_PTHREADS=1"]) + env.Append(LINKFLAGS=["-s", "DEFAULT_PTHREAD_STACK_SIZE=%sKB" % env["default_pthread_stack_size"]]) + env.Append(LINKFLAGS=["-s", "PTHREAD_POOL_SIZE=8"]) + env.Append(LINKFLAGS=["-s", "WASM_MEM_MAX=2048MB"]) + elif env["proxy_to_pthread"]: + print('"threads=no" support requires "proxy_to_pthread=no", disabling proxy to pthread.') + env["proxy_to_pthread"] = False if env["lto"] != "none": # Workaround https://github.com/emscripten-core/emscripten/issues/19781. @@ -224,7 +232,7 @@ def configure(env: "Environment"): if env["dlink_enabled"]: if env["proxy_to_pthread"]: - print("GDExtension support requires proxy_to_pthread=no, disabling") + print("GDExtension support requires proxy_to_pthread=no, disabling proxy to pthread.") env["proxy_to_pthread"] = False if cc_semver < (3, 1, 14): diff --git a/platform/web/doc_classes/EditorExportPlatformWeb.xml b/platform/web/doc_classes/EditorExportPlatformWeb.xml index c4c4fd870b..f07f265b0d 100644 --- a/platform/web/doc_classes/EditorExportPlatformWeb.xml +++ b/platform/web/doc_classes/EditorExportPlatformWeb.xml @@ -47,6 +47,10 @@ </member> <member name="variant/extensions_support" type="bool" setter="" getter=""> </member> + <member name="variant/thread_support" type="bool" setter="" getter=""> + If enabled, the exported game will support threads. It requires [url=https://web.dev/articles/coop-coep]a "cross-origin isolated" website[/url], which can be difficult to setup and brings some limitations (e.g. not being able to communicate with third-party websites). + If disabled, the exported game will not support threads. As a result, it is more prone to performance and audio issues, but will only require to be run on a HTTPS website. + </member> <member name="vram_texture_compression/for_desktop" type="bool" setter="" getter=""> </member> <member name="vram_texture_compression/for_mobile" type="bool" setter="" getter=""> diff --git a/platform/web/emscripten_helpers.py b/platform/web/emscripten_helpers.py index ec33397842..3ba133c9a1 100644 --- a/platform/web/emscripten_helpers.py +++ b/platform/web/emscripten_helpers.py @@ -4,7 +4,12 @@ from SCons.Util import WhereIs def run_closure_compiler(target, source, env, for_signature): - closure_bin = os.path.join(os.path.dirname(WhereIs("emcc")), "node_modules", ".bin", "google-closure-compiler") + closure_bin = os.path.join( + os.path.dirname(WhereIs("emcc")), + "node_modules", + ".bin", + "google-closure-compiler", + ) cmd = [WhereIs("node"), closure_bin] cmd.extend(["--compilation_level", "ADVANCED_OPTIMIZATIONS"]) for f in env["JSEXTERNS"]: @@ -31,27 +36,29 @@ def get_build_version(): return v -def create_engine_file(env, target, source, externs): +def create_engine_file(env, target, source, externs, threads_enabled): if env["use_closure_compiler"]: return env.BuildJS(target, source, JSEXTERNS=externs) - return env.Textfile(target, [env.File(s) for s in source]) + subst_dict = {"___GODOT_THREADS_ENABLED": "true" if threads_enabled else "false"} + return env.Substfile(target=target, source=[env.File(s) for s in source], SUBST_DICT=subst_dict) def create_template_zip(env, js, wasm, worker, side): binary_name = "godot.editor" if env.editor_build else "godot" - zip_dir = env.Dir("#bin/.web_zip") + zip_dir = env.Dir(env.GetTemplateZipPath()) in_files = [ js, wasm, - worker, "#platform/web/js/libs/audio.worklet.js", ] out_files = [ zip_dir.File(binary_name + ".js"), zip_dir.File(binary_name + ".wasm"), - zip_dir.File(binary_name + ".worker.js"), zip_dir.File(binary_name + ".audio.worklet.js"), ] + if env["threads"]: + in_files.append(worker) + out_files.append(zip_dir.File(binary_name + ".worker.js")) # Dynamic linking (extensions) specific. if env["dlink_enabled"]: in_files.append(side) # Side wasm (contains the actual Godot code). @@ -65,18 +72,20 @@ def create_template_zip(env, js, wasm, worker, side): "godot.editor.html", "offline.html", "godot.editor.js", - "godot.editor.worker.js", "godot.editor.audio.worklet.js", "logo.svg", "favicon.png", ] + if env["threads"]: + cache.append("godot.editor.worker.js") opt_cache = ["godot.editor.wasm"] subst_dict = { - "@GODOT_VERSION@": get_build_version(), - "@GODOT_NAME@": "GodotEngine", - "@GODOT_CACHE@": json.dumps(cache), - "@GODOT_OPT_CACHE@": json.dumps(opt_cache), - "@GODOT_OFFLINE_PAGE@": "offline.html", + "___GODOT_VERSION___": get_build_version(), + "___GODOT_NAME___": "GodotEngine", + "___GODOT_CACHE___": json.dumps(cache), + "___GODOT_OPT_CACHE___": json.dumps(opt_cache), + "___GODOT_OFFLINE_PAGE___": "offline.html", + "___GODOT_THREADS_ENABLED___": "true" if env["threads"] else "false", } html = env.Substfile(target="#bin/godot${PROGSUFFIX}.html", source=html, SUBST_DICT=subst_dict) in_files.append(html) @@ -88,7 +97,9 @@ def create_template_zip(env, js, wasm, worker, side): out_files.append(zip_dir.File("favicon.png")) # PWA service_worker = env.Substfile( - target="#bin/godot${PROGSUFFIX}.service.worker.js", source=service_worker, SUBST_DICT=subst_dict + target="#bin/godot${PROGSUFFIX}.service.worker.js", + source=service_worker, + SUBST_DICT=subst_dict, ) in_files.append(service_worker) out_files.append(zip_dir.File("service.worker.js")) @@ -115,6 +126,10 @@ def create_template_zip(env, js, wasm, worker, side): ) +def get_template_zip_path(env): + return "#bin/.web_zip" + + def add_js_libraries(env, libraries): env.Append(JS_LIBS=env.File(libraries)) diff --git a/platform/web/export/export_plugin.cpp b/platform/web/export/export_plugin.cpp index 9ffb776c29..d7e72612b4 100644 --- a/platform/web/export/export_plugin.cpp +++ b/platform/web/export/export_plugin.cpp @@ -169,6 +169,13 @@ void EditorExportPlatformWeb::_fix_html(Vector<uint8_t> &p_html, const Ref<Edito replaces["$GODOT_PROJECT_NAME"] = GLOBAL_GET("application/config/name"); replaces["$GODOT_HEAD_INCLUDE"] = head_include + custom_head_include; replaces["$GODOT_CONFIG"] = str_config; + + if (p_preset->get("variant/thread_support")) { + replaces["$GODOT_THREADS_ENABLED"] = "true"; + } else { + replaces["$GODOT_THREADS_ENABLED"] = "false"; + } + _replace_strings(replaces, p_html); } @@ -216,9 +223,9 @@ Error EditorExportPlatformWeb::_build_pwa(const Ref<EditorExportPreset> &p_prese const String name = p_path.get_file().get_basename(); bool extensions = (bool)p_preset->get("variant/extensions_support"); HashMap<String, String> replaces; - replaces["@GODOT_VERSION@"] = String::num_int64(OS::get_singleton()->get_unix_time()) + "|" + String::num_int64(OS::get_singleton()->get_ticks_usec()); - replaces["@GODOT_NAME@"] = proj_name.substr(0, 16); - replaces["@GODOT_OFFLINE_PAGE@"] = name + ".offline.html"; + replaces["___GODOT_VERSION___"] = String::num_int64(OS::get_singleton()->get_unix_time()) + "|" + String::num_int64(OS::get_singleton()->get_ticks_usec()); + replaces["___GODOT_NAME___"] = proj_name.substr(0, 16); + replaces["___GODOT_OFFLINE_PAGE___"] = name + ".offline.html"; // Files cached during worker install. Array cache_files; @@ -231,7 +238,7 @@ Error EditorExportPlatformWeb::_build_pwa(const Ref<EditorExportPreset> &p_prese } cache_files.push_back(name + ".worker.js"); cache_files.push_back(name + ".audio.worklet.js"); - replaces["@GODOT_CACHE@"] = Variant(cache_files).to_json_string(); + replaces["___GODOT_CACHE___"] = Variant(cache_files).to_json_string(); // Heavy files that are cached on demand. Array opt_cache_files; @@ -243,7 +250,7 @@ Error EditorExportPlatformWeb::_build_pwa(const Ref<EditorExportPreset> &p_prese opt_cache_files.push_back(p_shared_objects[i].path.get_file()); } } - replaces["@GODOT_OPT_CACHE@"] = Variant(opt_cache_files).to_json_string(); + replaces["___GODOT_OPT_CACHE___"] = Variant(opt_cache_files).to_json_string(); const String sw_path = dir.path_join(name + ".service.worker.js"); Vector<uint8_t> sw; @@ -335,6 +342,7 @@ void EditorExportPlatformWeb::get_export_options(List<ExportOption> *r_options) r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/release", PROPERTY_HINT_GLOBAL_FILE, "*.zip"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "variant/extensions_support"), false)); // Export type. + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "variant/thread_support"), true)); // Thread support (i.e. run with or without COEP/COOP headers). r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "vram_texture_compression/for_desktop"), true)); // S3TC r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "vram_texture_compression/for_mobile"), false)); // ETC or ETC2, depending on renderer @@ -377,10 +385,11 @@ bool EditorExportPlatformWeb::has_valid_export_configuration(const Ref<EditorExp String err; bool valid = false; bool extensions = (bool)p_preset->get("variant/extensions_support"); + bool thread_support = (bool)p_preset->get("variant/thread_support"); // Look for export templates (first official, and if defined custom templates). - bool dvalid = exists_export_template(_get_template_name(extensions, true), &err); - bool rvalid = exists_export_template(_get_template_name(extensions, false), &err); + bool dvalid = exists_export_template(_get_template_name(extensions, thread_support, true), &err); + bool rvalid = exists_export_template(_get_template_name(extensions, thread_support, false), &err); if (p_preset->get("custom_template/debug") != "") { dvalid = FileAccess::exists(p_preset->get("custom_template/debug")); @@ -454,7 +463,8 @@ Error EditorExportPlatformWeb::export_project(const Ref<EditorExportPreset> &p_p template_path = template_path.strip_edges(); if (template_path.is_empty()) { bool extensions = (bool)p_preset->get("variant/extensions_support"); - template_path = find_export_template(_get_template_name(extensions, p_debug)); + bool thread_support = (bool)p_preset->get("variant/thread_support"); + template_path = find_export_template(_get_template_name(extensions, thread_support, p_debug)); } if (!template_path.is_empty() && !FileAccess::exists(template_path)) { diff --git a/platform/web/export/export_plugin.h b/platform/web/export/export_plugin.h index 9bb82d472e..98e3fe729e 100644 --- a/platform/web/export/export_plugin.h +++ b/platform/web/export/export_plugin.h @@ -56,11 +56,14 @@ class EditorExportPlatformWeb : public EditorExportPlatform { Mutex server_lock; Thread server_thread; - String _get_template_name(bool p_extension, bool p_debug) const { + String _get_template_name(bool p_extension, bool p_thread_support, bool p_debug) const { String name = "web"; if (p_extension) { name += "_dlink"; } + if (!p_thread_support) { + name += "_nothreads"; + } if (p_debug) { name += "_debug.zip"; } else { diff --git a/platform/web/js/engine/features.js b/platform/web/js/engine/features.js index b7c6c9d445..81bc82f3c6 100644 --- a/platform/web/js/engine/features.js +++ b/platform/web/js/engine/features.js @@ -72,8 +72,14 @@ const Features = { // eslint-disable-line no-unused-vars * * @returns {Array<string>} A list of human-readable missing features. * @function Engine.getMissingFeatures + * @typedef {{ threads: boolean }} SupportedFeatures + * @param {SupportedFeatures} supportedFeatures */ - getMissingFeatures: function () { + getMissingFeatures: function (supportedFeatures = {}) { + const { + threads: supportsThreads = true, + } = supportedFeatures; + const missing = []; if (!Features.isWebGLAvailable(2)) { missing.push('WebGL2 - Check web browser configuration and hardware support'); @@ -84,12 +90,16 @@ const Features = { // eslint-disable-line no-unused-vars if (!Features.isSecureContext()) { missing.push('Secure Context - Check web server configuration (use HTTPS)'); } - if (!Features.isCrossOriginIsolated()) { - missing.push('Cross Origin Isolation - Check web server configuration (send correct headers)'); - } - if (!Features.isSharedArrayBufferAvailable()) { - missing.push('SharedArrayBuffer - Check web server configuration (send correct headers)'); + + if (supportsThreads) { + if (!Features.isCrossOriginIsolated()) { + missing.push('Cross-Origin Isolation - Check that the web server configuration sends the correct headers.'); + } + if (!Features.isSharedArrayBufferAvailable()) { + missing.push('SharedArrayBuffer - Check that the web server configuration sends the correct headers.'); + } } + // Audio is normally optional since we have a dummy fallback. return missing; }, diff --git a/platform/web/js/libs/audio.worklet.js b/platform/web/js/libs/audio.worklet.js index 89b581b3d6..3b94cab85c 100644 --- a/platform/web/js/libs/audio.worklet.js +++ b/platform/web/js/libs/audio.worklet.js @@ -167,7 +167,7 @@ class GodotProcessor extends AudioWorkletProcessor { GodotProcessor.write_input(this.input_buffer, input); this.input.write(this.input_buffer); } else { - this.port.postMessage('Input buffer is full! Skipping input frame.'); + // this.port.postMessage('Input buffer is full! Skipping input frame.'); // Uncomment this line to debug input buffer. } } const process_output = GodotProcessor.array_has_data(outputs); @@ -184,7 +184,7 @@ class GodotProcessor extends AudioWorkletProcessor { this.port.postMessage({ 'cmd': 'read', 'data': chunk }); } } else { - this.port.postMessage('Output buffer has not enough frames! Skipping output frame.'); + // this.port.postMessage('Output buffer has not enough frames! Skipping output frame.'); // Uncomment this line to debug output buffer. } } this.process_notify(); diff --git a/scene/2d/audio_stream_player_2d.cpp b/scene/2d/audio_stream_player_2d.cpp index a99964c0e0..f6e6eb8b17 100644 --- a/scene/2d/audio_stream_player_2d.cpp +++ b/scene/2d/audio_stream_player_2d.cpp @@ -33,86 +33,48 @@ #include "core/config/project_settings.h" #include "scene/2d/area_2d.h" #include "scene/2d/audio_listener_2d.h" -#include "scene/main/window.h" +#include "scene/audio/audio_stream_player_internal.h" +#include "scene/main/viewport.h" #include "scene/resources/world_2d.h" - -#define PARAM_PREFIX "parameters/" +#include "scene/scene_string_names.h" +#include "servers/audio/audio_stream.h" +#include "servers/audio_server.h" void AudioStreamPlayer2D::_notification(int p_what) { + internal->notification(p_what); + switch (p_what) { case NOTIFICATION_ENTER_TREE: { AudioServer::get_singleton()->add_listener_changed_callback(_listener_changed_cb, this); - if (autoplay && !Engine::get_singleton()->is_editor_hint()) { - play(); - } - set_stream_paused(!can_process()); } break; case NOTIFICATION_EXIT_TREE: { - set_stream_paused(true); AudioServer::get_singleton()->remove_listener_changed_callback(_listener_changed_cb, this); } break; - case NOTIFICATION_PREDELETE: { - stop(); - } break; - - case NOTIFICATION_PAUSED: { - if (!can_process()) { - // Node can't process so we start fading out to silence. - set_stream_paused(true); - } - } break; - - case NOTIFICATION_UNPAUSED: { - set_stream_paused(false); - } break; - case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { // Update anything related to position first, if possible of course. - if (setplay.get() > 0 || (active.is_set() && last_mix_count != AudioServer::get_singleton()->get_mix_count()) || force_update_panning) { + if (setplay.get() > 0 || (internal->active.is_set() && last_mix_count != AudioServer::get_singleton()->get_mix_count()) || force_update_panning) { force_update_panning = false; _update_panning(); } if (setplayback.is_valid() && setplay.get() >= 0) { - active.set(); - AudioServer::get_singleton()->start_playback_stream(setplayback, _get_actual_bus(), volume_vector, setplay.get(), pitch_scale); + internal->active.set(); + AudioServer::get_singleton()->start_playback_stream(setplayback, _get_actual_bus(), volume_vector, setplay.get(), internal->pitch_scale); setplayback.unref(); setplay.set(-1); } - if (!stream_playbacks.is_empty() && active.is_set()) { - // Stop playing if no longer active. - Vector<Ref<AudioStreamPlayback>> playbacks_to_remove; - for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { - if (playback.is_valid() && !AudioServer::get_singleton()->is_playback_active(playback) && !AudioServer::get_singleton()->is_playback_paused(playback)) { - playbacks_to_remove.push_back(playback); - } - } - // Now go through and remove playbacks that have finished. Removing elements from a Vector in a range based for is asking for trouble. - for (Ref<AudioStreamPlayback> &playback : playbacks_to_remove) { - stream_playbacks.erase(playback); - } - if (!playbacks_to_remove.is_empty() && stream_playbacks.is_empty()) { - // This node is no longer actively playing audio. - active.clear(); - set_physics_process_internal(false); - } - if (!playbacks_to_remove.is_empty()) { - emit_signal(SNAME("finished")); - } - } - - while (stream_playbacks.size() > max_polyphony) { - AudioServer::get_singleton()->stop_playback_stream(stream_playbacks[0]); - stream_playbacks.remove_at(0); + if (!internal->stream_playbacks.is_empty() && internal->active.is_set()) { + internal->process(); } + internal->ensure_playback_limit(); } break; } } -// Interacts with PhysicsServer2D, so can only be called during _physics_process +// Interacts with PhysicsServer2D, so can only be called during _physics_process. StringName AudioStreamPlayer2D::_get_actual_bus() { Vector2 global_pos = get_global_position(); @@ -144,12 +106,12 @@ StringName AudioStreamPlayer2D::_get_actual_bus() { return area2d->get_audio_bus_name(); } - return default_bus; + return internal->bus; } // Interacts with PhysicsServer2D, so can only be called during _physics_process void AudioStreamPlayer2D::_update_panning() { - if (!active.is_set() || stream.is_null()) { + if (!internal->active.is_set() || internal->stream.is_null()) { return; } @@ -194,7 +156,7 @@ void AudioStreamPlayer2D::_update_panning() { } float multiplier = Math::pow(1.0f - dist / max_distance, attenuation); - multiplier *= Math::db_to_linear(volume_db); // Also apply player volume! + multiplier *= Math::db_to_linear(internal->volume_db); // Also apply player volume! float pan = relative_to_listener.x / screen_size.x; // Don't let the panning effect extend (too far) beyond the screen. @@ -213,178 +175,96 @@ void AudioStreamPlayer2D::_update_panning() { volume_vector.write[0] = AudioFrame(MAX(prev_sample[0], new_sample[0]), MAX(prev_sample[1], new_sample[1])); } - for (const Ref<AudioStreamPlayback> &playback : stream_playbacks) { + for (const Ref<AudioStreamPlayback> &playback : internal->stream_playbacks) { AudioServer::get_singleton()->set_playback_bus_exclusive(playback, _get_actual_bus(), volume_vector); } - for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { - AudioServer::get_singleton()->set_playback_pitch_scale(playback, pitch_scale); + for (Ref<AudioStreamPlayback> &playback : internal->stream_playbacks) { + AudioServer::get_singleton()->set_playback_pitch_scale(playback, internal->pitch_scale); } last_mix_count = AudioServer::get_singleton()->get_mix_count(); } -void AudioStreamPlayer2D::_update_stream_parameters() { - if (stream.is_null()) { - return; - } - - List<AudioStream::Parameter> parameters; - stream->get_parameter_list(¶meters); - for (const AudioStream::Parameter &K : parameters) { - const PropertyInfo &pi = K.property; - StringName key = PARAM_PREFIX + pi.name; - if (!playback_parameters.has(key)) { - ParameterData pd; - pd.path = pi.name; - pd.value = K.default_value; - playback_parameters.insert(key, pd); - } - } -} - void AudioStreamPlayer2D::set_stream(Ref<AudioStream> p_stream) { - if (stream.is_valid()) { - stream->disconnect(SNAME("parameter_list_changed"), callable_mp(this, &AudioStreamPlayer2D::_update_stream_parameters)); - } - stop(); - stream = p_stream; - _update_stream_parameters(); - if (stream.is_valid()) { - stream->connect(SNAME("parameter_list_changed"), callable_mp(this, &AudioStreamPlayer2D::_update_stream_parameters)); - } - notify_property_list_changed(); + internal->set_stream(p_stream); } Ref<AudioStream> AudioStreamPlayer2D::get_stream() const { - return stream; + return internal->stream; } void AudioStreamPlayer2D::set_volume_db(float p_volume) { - volume_db = p_volume; + internal->volume_db = p_volume; } float AudioStreamPlayer2D::get_volume_db() const { - return volume_db; + return internal->volume_db; } void AudioStreamPlayer2D::set_pitch_scale(float p_pitch_scale) { - ERR_FAIL_COND(!(p_pitch_scale > 0.0)); - pitch_scale = p_pitch_scale; - for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { - AudioServer::get_singleton()->set_playback_pitch_scale(playback, p_pitch_scale); - } + internal->set_pitch_scale(p_pitch_scale); } float AudioStreamPlayer2D::get_pitch_scale() const { - return pitch_scale; + return internal->pitch_scale; } void AudioStreamPlayer2D::play(float p_from_pos) { - if (stream.is_null()) { + Ref<AudioStreamPlayback> stream_playback = internal->play_basic(); + if (stream_playback.is_null()) { return; } - ERR_FAIL_COND_MSG(!is_inside_tree(), "Playback can only happen when a node is inside the scene tree"); - if (stream->is_monophonic() && is_playing()) { - stop(); - } - Ref<AudioStreamPlayback> stream_playback = stream->instantiate_playback(); - ERR_FAIL_COND_MSG(stream_playback.is_null(), "Failed to instantiate playback."); - - for (const KeyValue<StringName, ParameterData> &K : playback_parameters) { - stream_playback->set_parameter(K.value.path, K.value.value); - } - - stream_playbacks.push_back(stream_playback); setplayback = stream_playback; setplay.set(p_from_pos); - active.set(); - set_physics_process_internal(true); } void AudioStreamPlayer2D::seek(float p_seconds) { - if (is_playing()) { - stop(); - play(p_seconds); - } + internal->seek(p_seconds); } void AudioStreamPlayer2D::stop() { setplay.set(-1); - for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { - AudioServer::get_singleton()->stop_playback_stream(playback); - } - stream_playbacks.clear(); - active.clear(); - set_physics_process_internal(false); + internal->stop(); } bool AudioStreamPlayer2D::is_playing() const { - for (const Ref<AudioStreamPlayback> &playback : stream_playbacks) { - if (AudioServer::get_singleton()->is_playback_active(playback)) { - return true; - } - } if (setplay.get() >= 0) { return true; // play() has been called this frame, but no playback exists just yet. } - return false; + return internal->is_playing(); } float AudioStreamPlayer2D::get_playback_position() { - // Return the playback position of the most recently started playback stream. - if (!stream_playbacks.is_empty()) { - return AudioServer::get_singleton()->get_playback_position(stream_playbacks[stream_playbacks.size() - 1]); - } - return 0; + return internal->get_playback_position(); } void AudioStreamPlayer2D::set_bus(const StringName &p_bus) { - default_bus = p_bus; // This will be pushed to the audio server during the next physics timestep, which is fast enough. + internal->bus = p_bus; // This will be pushed to the audio server during the next physics timestep, which is fast enough. } StringName AudioStreamPlayer2D::get_bus() const { - for (int i = 0; i < AudioServer::get_singleton()->get_bus_count(); i++) { - if (AudioServer::get_singleton()->get_bus_name(i) == default_bus) { - return default_bus; - } - } - return SceneStringNames::get_singleton()->Master; + return internal->get_bus(); } void AudioStreamPlayer2D::set_autoplay(bool p_enable) { - autoplay = p_enable; + internal->autoplay = p_enable; } bool AudioStreamPlayer2D::is_autoplay_enabled() { - return autoplay; + return internal->autoplay; } void AudioStreamPlayer2D::_set_playing(bool p_enable) { - if (p_enable) { - play(); - } else { - stop(); - } + internal->set_playing(p_enable); } bool AudioStreamPlayer2D::_is_active() const { - return active.is_set(); + return internal->is_active(); } void AudioStreamPlayer2D::_validate_property(PropertyInfo &p_property) const { - if (p_property.name == "bus") { - String options; - for (int i = 0; i < AudioServer::get_singleton()->get_bus_count(); i++) { - if (i > 0) { - options += ","; - } - String name = AudioServer::get_singleton()->get_bus_name(i); - options += name; - } - - p_property.hint_string = options; - } + internal->validate_property(p_property); } void AudioStreamPlayer2D::set_max_distance(float p_pixels) { @@ -413,37 +293,27 @@ uint32_t AudioStreamPlayer2D::get_area_mask() const { } void AudioStreamPlayer2D::set_stream_paused(bool p_pause) { - // TODO this does not have perfect recall, fix that maybe? If there are zero playbacks registered with the AudioServer, this bool isn't persisted. - for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { - AudioServer::get_singleton()->set_playback_paused(playback, p_pause); - } + internal->set_stream_paused(p_pause); } bool AudioStreamPlayer2D::get_stream_paused() const { - // There's currently no way to pause some playback streams but not others. Check the first and don't bother looking at the rest. - if (!stream_playbacks.is_empty()) { - return AudioServer::get_singleton()->is_playback_paused(stream_playbacks[0]); - } - return false; + return internal->get_stream_paused(); } bool AudioStreamPlayer2D::has_stream_playback() { - return !stream_playbacks.is_empty(); + return internal->has_stream_playback(); } Ref<AudioStreamPlayback> AudioStreamPlayer2D::get_stream_playback() { - ERR_FAIL_COND_V_MSG(stream_playbacks.is_empty(), Ref<AudioStreamPlayback>(), "Player is inactive. Call play() before requesting get_stream_playback()."); - return stream_playbacks[stream_playbacks.size() - 1]; + return internal->get_stream_playback(); } void AudioStreamPlayer2D::set_max_polyphony(int p_max_polyphony) { - if (p_max_polyphony > 0) { - max_polyphony = p_max_polyphony; - } + internal->set_max_polyphony(p_max_polyphony); } int AudioStreamPlayer2D::get_max_polyphony() const { - return max_polyphony; + return internal->max_polyphony; } void AudioStreamPlayer2D::set_panning_strength(float p_panning_strength) { @@ -455,48 +325,16 @@ float AudioStreamPlayer2D::get_panning_strength() const { return panning_strength; } -void AudioStreamPlayer2D::_on_bus_layout_changed() { - notify_property_list_changed(); -} - -void AudioStreamPlayer2D::_on_bus_renamed(int p_bus_index, const StringName &p_old_name, const StringName &p_new_name) { - notify_property_list_changed(); -} - bool AudioStreamPlayer2D::_set(const StringName &p_name, const Variant &p_value) { - HashMap<StringName, ParameterData>::Iterator I = playback_parameters.find(p_name); - if (!I) { - return false; - } - ParameterData &pd = I->value; - pd.value = p_value; - for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { - playback->set_parameter(pd.path, pd.value); - } - return true; + return internal->set(p_name, p_value); } bool AudioStreamPlayer2D::_get(const StringName &p_name, Variant &r_ret) const { - HashMap<StringName, ParameterData>::ConstIterator I = playback_parameters.find(p_name); - if (!I) { - return false; - } - - r_ret = I->value.value; - return true; + return internal->get(p_name, r_ret); } void AudioStreamPlayer2D::_get_property_list(List<PropertyInfo> *p_list) const { - if (stream.is_null()) { - return; - } - List<AudioStream::Parameter> parameters; - stream->get_parameter_list(¶meters); - for (const AudioStream::Parameter &K : parameters) { - PropertyInfo pi = K.property; - pi.name = PARAM_PREFIX + pi.name; - p_list->push_back(pi); - } + internal->get_property_list(p_list); } void AudioStreamPlayer2D::_bind_methods() { @@ -563,11 +401,11 @@ void AudioStreamPlayer2D::_bind_methods() { } AudioStreamPlayer2D::AudioStreamPlayer2D() { - AudioServer::get_singleton()->connect("bus_layout_changed", callable_mp(this, &AudioStreamPlayer2D::_on_bus_layout_changed)); - AudioServer::get_singleton()->connect("bus_renamed", callable_mp(this, &AudioStreamPlayer2D::_on_bus_renamed)); + internal = memnew(AudioStreamPlayerInternal(this, callable_mp(this, &AudioStreamPlayer2D::play), true)); cached_global_panning_strength = GLOBAL_GET("audio/general/2d_panning_strength"); set_hide_clip_children(true); } AudioStreamPlayer2D::~AudioStreamPlayer2D() { + memdelete(internal); } diff --git a/scene/2d/audio_stream_player_2d.h b/scene/2d/audio_stream_player_2d.h index 267d6a625b..3552735cd7 100644 --- a/scene/2d/audio_stream_player_2d.h +++ b/scene/2d/audio_stream_player_2d.h @@ -32,9 +32,11 @@ #define AUDIO_STREAM_PLAYER_2D_H #include "scene/2d/node_2d.h" -#include "scene/scene_string_names.h" -#include "servers/audio/audio_stream.h" -#include "servers/audio_server.h" + +struct AudioFrame; +class AudioStream; +class AudioStreamPlayback; +class AudioStreamPlayerInternal; class AudioStreamPlayer2D : public Node2D { GDCLASS(AudioStreamPlayer2D, Node2D); @@ -52,10 +54,8 @@ private: Viewport *viewport = nullptr; //pointer only used for reference to previous mix }; - Vector<Ref<AudioStreamPlayback>> stream_playbacks; - Ref<AudioStream> stream; + AudioStreamPlayerInternal *internal = nullptr; - SafeFlag active{ false }; SafeNumeric<float> setplay{ -1.0 }; Ref<AudioStreamPlayback> setplayback; @@ -64,21 +64,12 @@ private: uint64_t last_mix_count = -1; bool force_update_panning = false; - float volume_db = 0.0; - float pitch_scale = 1.0; - bool autoplay = false; - StringName default_bus = SceneStringNames::get_singleton()->Master; - int max_polyphony = 1; - void _set_playing(bool p_enable); bool _is_active() const; StringName _get_actual_bus(); void _update_panning(); - void _on_bus_layout_changed(); - void _on_bus_renamed(int p_bus_index, const StringName &p_old_name, const StringName &p_new_name); - static void _listener_changed_cb(void *self) { reinterpret_cast<AudioStreamPlayer2D *>(self)->force_update_panning = true; } uint32_t area_mask = 1; @@ -89,14 +80,6 @@ private: float panning_strength = 1.0f; float cached_global_panning_strength = 0.5f; - struct ParameterData { - StringName path; - Variant value; - }; - - HashMap<StringName, ParameterData> playback_parameters; - void _update_stream_parameters(); - protected: void _validate_property(PropertyInfo &p_property) const; void _notification(int p_what); diff --git a/scene/2d/tile_map.compat.inc b/scene/2d/tile_map.compat.inc index ed216173c7..04937bdf7e 100644 --- a/scene/2d/tile_map.compat.inc +++ b/scene/2d/tile_map.compat.inc @@ -42,10 +42,20 @@ int TileMap::_get_quadrant_size_compat_81070() const { return get_rendering_quadrant_size(); } +TileMap::VisibilityMode TileMap::_get_collision_visibility_mode_bind_compat_87115() { + return get_collision_visibility_mode(); +} + +TileMap::VisibilityMode TileMap::_get_navigation_visibility_mode_bind_compat_87115() { + return get_navigation_visibility_mode(); +} + void TileMap::_bind_compatibility_methods() { ClassDB::bind_compatibility_method(D_METHOD("get_used_rect"), &TileMap::_get_used_rect_bind_compat_78328); ClassDB::bind_compatibility_method(D_METHOD("set_quadrant_size", "quadrant_size"), &TileMap::_set_quadrant_size_compat_81070); ClassDB::bind_compatibility_method(D_METHOD("get_quadrant_size"), &TileMap::_get_quadrant_size_compat_81070); + ClassDB::bind_compatibility_method(D_METHOD("get_collision_visibility_mode"), &TileMap::_get_collision_visibility_mode_bind_compat_87115); + ClassDB::bind_compatibility_method(D_METHOD("get_navigation_visibility_mode"), &TileMap::_get_navigation_visibility_mode_bind_compat_87115); } #endif diff --git a/scene/2d/tile_map.cpp b/scene/2d/tile_map.cpp index decbbab476..24eefce99d 100644 --- a/scene/2d/tile_map.cpp +++ b/scene/2d/tile_map.cpp @@ -167,7 +167,7 @@ void TileMap::set_selected_layer(int p_layer_id) { emit_signal(CoreStringNames::get_singleton()->changed); // Update the layers modulation. - for (Ref<TileMapLayer> &layer : layers) { + for (TileMapLayer *layer : layers) { layer->notify_tile_map_change(TileMapLayer::DIRTY_FLAGS_TILE_MAP_SELECTED_LAYER); } } @@ -178,45 +178,9 @@ int TileMap::get_selected_layer() const { void TileMap::_notification(int p_what) { switch (p_what) { - case NOTIFICATION_ENTER_TREE: { - for (Ref<TileMapLayer> &layer : layers) { - layer->notify_tile_map_change(TileMapLayer::DIRTY_FLAGS_TILE_MAP_IN_TREE); - } - } break; - - case NOTIFICATION_EXIT_TREE: { - for (Ref<TileMapLayer> &layer : layers) { - layer->notify_tile_map_change(TileMapLayer::DIRTY_FLAGS_TILE_MAP_IN_TREE); - } - } break; - - case TileMap::NOTIFICATION_ENTER_CANVAS: { - for (Ref<TileMapLayer> &layer : layers) { - layer->notify_tile_map_change(TileMapLayer::DIRTY_FLAGS_TILE_MAP_IN_CANVAS); - } - } break; - - case TileMap::NOTIFICATION_EXIT_CANVAS: { - for (Ref<TileMapLayer> &layer : layers) { - layer->notify_tile_map_change(TileMapLayer::DIRTY_FLAGS_TILE_MAP_IN_CANVAS); - } - } 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_tile_map_change(TileMapLayer::DIRTY_FLAGS_TILE_MAP_VISIBILITY); - } - } break; - case TileMap::NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { - // Physics. + // This is only executed when collision_animatable is enabled. + bool in_editor = false; #ifdef TOOLS_ENABLED in_editor = Engine::get_singleton()->is_editor_hint(); @@ -224,39 +188,30 @@ void TileMap::_notification(int p_what) { if (is_inside_tree() && collision_animatable && !in_editor) { // Update transform on the physics tick when in animatable mode. last_valid_transform = new_transform; + print_line("Physics: ", new_transform); set_notify_local_transform(false); set_global_transform(new_transform); - _update_notify_local_transform(); - } - } break; - - case NOTIFICATION_TRANSFORM_CHANGED: { - // Physics. - for (Ref<TileMapLayer> &layer : layers) { - layer->notify_tile_map_change(TileMapLayer::DIRTY_FLAGS_TILE_MAP_XFORM); + set_notify_local_transform(true); } } break; case TileMap::NOTIFICATION_LOCAL_TRANSFORM_CHANGED: { - for (Ref<TileMapLayer> &layer : layers) { - layer->notify_tile_map_change(TileMapLayer::DIRTY_FLAGS_TILE_MAP_LOCAL_XFORM); - } + // This is only executed when collision_animatable is enabled. - // 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() && collision_animatable && !in_editor) { // Store last valid transform. new_transform = get_global_transform(); + print_line("local XFORM: ", last_valid_transform); // ... but then revert changes. set_notify_local_transform(false); set_global_transform(last_valid_transform); - _update_notify_local_transform(); + set_notify_local_transform(true); } } break; } @@ -285,7 +240,7 @@ void TileMap::_internal_update() { } // Update dirty quadrants on layers. - for (Ref<TileMapLayer> &layer : layers) { + for (TileMapLayer *layer : layers) { layer->internal_update(); } @@ -308,7 +263,7 @@ void TileMap::set_tileset(const Ref<TileSet> &p_tileset) { tile_set->connect_changed(callable_mp(this, &TileMap::_tile_set_changed)); } - for (Ref<TileMapLayer> &layer : layers) { + for (TileMapLayer *layer : layers) { layer->notify_tile_map_change(TileMapLayer::DIRTY_FLAGS_TILE_MAP_TILE_SET); } @@ -323,7 +278,7 @@ void TileMap::set_rendering_quadrant_size(int p_size) { ERR_FAIL_COND_MSG(p_size < 1, "TileMapQuadrant size cannot be smaller than 1."); rendering_quadrant_size = p_size; - for (Ref<TileMapLayer> &layer : layers) { + for (TileMapLayer *layer : layers) { layer->notify_tile_map_change(TileMapLayer::DIRTY_FLAGS_TILE_MAP_QUADRANT_SIZE); } emit_signal(CoreStringNames::get_singleton()->changed); @@ -429,10 +384,11 @@ void TileMap::add_layer(int p_to_pos) { ERR_FAIL_INDEX(p_to_pos, (int)layers.size() + 1); // Must clear before adding the layer. - Ref<TileMapLayer> new_layer; - new_layer.instantiate(); - new_layer->set_tile_map(this); + TileMapLayer *new_layer = memnew(TileMapLayer); layers.insert(p_to_pos, new_layer); + add_child(new_layer); + new_layer->set_name(vformat("Layer%d", p_to_pos)); + move_child(new_layer, p_to_pos); for (uint32_t i = 0; i < layers.size(); i++) { layers[i]->set_layer_index_in_tile_map_node(i); } @@ -449,10 +405,11 @@ void TileMap::move_layer(int p_layer, int p_to_pos) { ERR_FAIL_INDEX(p_to_pos, (int)layers.size() + 1); // Clear before shuffling layers. - Ref<TileMapLayer> layer = layers[p_layer]; + 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 (uint32_t i = 0; i < layers.size(); i++) { + move_child(layer, i); layers[i]->set_layer_index_in_tile_map_node(i); } queue_internal_update(); @@ -471,6 +428,7 @@ void TileMap::remove_layer(int p_layer) { ERR_FAIL_INDEX(p_layer, (int)layers.size()); // Clear before removing the layer. + layers[p_layer]->queue_free(); layers.remove_at(p_layer); for (uint32_t i = 0; i < layers.size(); i++) { layers[i]->set_layer_index_in_tile_map_node(i); @@ -513,7 +471,6 @@ Color TileMap::get_layer_modulate(int p_layer) const { 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); - _update_notify_local_transform(); } bool TileMap::is_layer_y_sort_enabled(int p_layer) const { @@ -552,17 +509,16 @@ 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) { +void TileMap::set_collision_animatable(bool p_collision_animatable) { + if (collision_animatable == p_collision_animatable) { return; } - collision_animatable = p_enabled; - _update_notify_local_transform(); - set_physics_process_internal(p_enabled); - for (Ref<TileMapLayer> &layer : layers) { - layer->notify_tile_map_change(TileMapLayer::DIRTY_FLAGS_TILE_MAP_COLLISION_ANIMATABLE); + collision_animatable = p_collision_animatable; + set_notify_local_transform(p_collision_animatable); + set_physics_process_internal(p_collision_animatable); + for (TileMapLayer *layer : layers) { + layer->set_use_kinematic_bodies(layer); } - emit_signal(CoreStringNames::get_singleton()->changed); } bool TileMap::is_collision_animatable() const { @@ -574,13 +530,13 @@ void TileMap::set_collision_visibility_mode(TileMap::VisibilityMode p_show_colli return; } collision_visibility_mode = p_show_collision; - for (Ref<TileMapLayer> &layer : layers) { + for (TileMapLayer *layer : layers) { layer->notify_tile_map_change(TileMapLayer::DIRTY_FLAGS_TILE_MAP_COLLISION_VISIBILITY_MODE); } emit_signal(CoreStringNames::get_singleton()->changed); } -TileMap::VisibilityMode TileMap::get_collision_visibility_mode() { +TileMap::VisibilityMode TileMap::get_collision_visibility_mode() const { return collision_visibility_mode; } @@ -589,13 +545,13 @@ void TileMap::set_navigation_visibility_mode(TileMap::VisibilityMode p_show_navi return; } navigation_visibility_mode = p_show_navigation; - for (Ref<TileMapLayer> &layer : layers) { + for (TileMapLayer *layer : layers) { layer->notify_tile_map_change(TileMapLayer::DIRTY_FLAGS_TILE_MAP_NAVIGATION_VISIBILITY_MODE); } emit_signal(CoreStringNames::get_singleton()->changed); } -TileMap::VisibilityMode TileMap::get_navigation_visibility_mode() { +TileMap::VisibilityMode TileMap::get_navigation_visibility_mode() const { return navigation_visibility_mode; } @@ -604,7 +560,7 @@ void TileMap::set_y_sort_enabled(bool p_enable) { return; } Node2D::set_y_sort_enabled(p_enable); - for (Ref<TileMapLayer> &layer : layers) { + for (TileMapLayer *layer : layers) { layer->notify_tile_map_change(TileMapLayer::DIRTY_FLAGS_TILE_MAP_Y_SORT_ENABLED); } emit_signal(CoreStringNames::get_singleton()->changed); @@ -640,27 +596,8 @@ Ref<TileMapPattern> TileMap::get_pattern(int p_layer, TypedArray<Vector2i> p_coo } 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; + ERR_FAIL_COND_V(!tile_set.is_valid(), Vector2i()); + return tile_set->map_pattern(p_position_in_tilemap, p_coords_in_pattern, p_pattern); } void TileMap::set_pattern(int p_layer, const Vector2i &p_position, const Ref<TileMapPattern> p_pattern) { @@ -700,7 +637,7 @@ TileMapCell TileMap::get_cell(int p_layer, const Vector2i &p_coords, bool p_use_ } Vector2i TileMap::get_coords_for_body_rid(RID p_physics_body) { - for (const Ref<TileMapLayer> &layer : layers) { + for (const TileMapLayer *layer : layers) { if (layer->has_body_rid(p_physics_body)) { return layer->get_coords_for_body_rid(p_physics_body); } @@ -718,7 +655,7 @@ int TileMap::get_layer_for_body_rid(RID p_physics_body) { } void TileMap::fix_invalid_tiles() { - for (Ref<TileMapLayer> &layer : layers) { + for (TileMapLayer *layer : layers) { layer->fix_invalid_tiles(); } } @@ -728,7 +665,7 @@ void TileMap::clear_layer(int p_layer) { } void TileMap::clear() { - for (Ref<TileMapLayer> &layer : layers) { + for (TileMapLayer *layer : layers) { layer->clear(); } } @@ -742,7 +679,7 @@ void TileMap::notify_runtime_tile_data_update(int p_layer) { if (p_layer >= 0) { TILEMAP_CALL_FOR_LAYER(p_layer, notify_tile_map_change, TileMapLayer::DIRTY_FLAGS_TILE_MAP_RUNTIME_UPDATE); } else { - for (Ref<TileMapLayer> &layer : layers) { + for (TileMapLayer *layer : layers) { layer->notify_tile_map_change(TileMapLayer::DIRTY_FLAGS_TILE_MAP_RUNTIME_UPDATE); } } @@ -780,9 +717,9 @@ bool TileMap::_set(const StringName &p_name, const Variant &p_value) { else if (p_name == "tile_data") { // Kept for compatibility reasons. if (p_value.is_array()) { if (layers.size() == 0) { - Ref<TileMapLayer> new_layer; - new_layer.instantiate(); - new_layer->set_tile_map(this); + TileMapLayer *new_layer = memnew(TileMapLayer); + add_child(new_layer); + new_layer->set_name("Layer0"); new_layer->set_layer_index_in_tile_map_node(0); layers.push_back(new_layer); } @@ -804,9 +741,9 @@ bool TileMap::_set(const StringName &p_name, const Variant &p_value) { if (index >= (int)layers.size()) { while (index >= (int)layers.size()) { - Ref<TileMapLayer> new_layer; - new_layer.instantiate(); - new_layer->set_tile_map(this); + TileMapLayer *new_layer = memnew(TileMapLayer); + add_child(new_layer); + new_layer->set_name(vformat("Layer%d", index)); new_layer->set_layer_index_in_tile_map_node(index); layers.push_back(new_layer); } @@ -985,623 +922,23 @@ bool TileMap::_property_get_revert(const StringName &p_name, Variant &r_property } Vector2 TileMap::map_to_local(const Vector2i &p_pos) const { - // SHOULD RETURN THE CENTER OF THE CELL. ERR_FAIL_COND_V(!tile_set.is_valid(), Vector2()); - - Vector2 ret = p_pos; - TileSet::TileShape tile_shape = tile_set->get_tile_shape(); - TileSet::TileOffsetAxis tile_offset_axis = tile_set->get_tile_offset_axis(); - - 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. - if (tile_offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { - switch (tile_set->get_tile_layout()) { - case TileSet::TILE_LAYOUT_STACKED: - ret = Vector2(ret.x + (Math::posmod(ret.y, 2) == 0 ? 0.0 : 0.5), ret.y); - break; - case TileSet::TILE_LAYOUT_STACKED_OFFSET: - ret = Vector2(ret.x + (Math::posmod(ret.y, 2) == 1 ? 0.0 : 0.5), ret.y); - break; - case TileSet::TILE_LAYOUT_STAIRS_RIGHT: - ret = Vector2(ret.x + ret.y / 2, ret.y); - break; - case TileSet::TILE_LAYOUT_STAIRS_DOWN: - ret = Vector2(ret.x / 2, ret.y * 2 + ret.x); - break; - case TileSet::TILE_LAYOUT_DIAMOND_RIGHT: - ret = Vector2((ret.x + ret.y) / 2, ret.y - ret.x); - break; - case TileSet::TILE_LAYOUT_DIAMOND_DOWN: - ret = Vector2((ret.x - ret.y) / 2, ret.y + ret.x); - break; - } - } 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)); - break; - case TileSet::TILE_LAYOUT_STACKED_OFFSET: - ret = Vector2(ret.x, ret.y + (Math::posmod(ret.x, 2) == 1 ? 0.0 : 0.5)); - break; - case TileSet::TILE_LAYOUT_STAIRS_RIGHT: - ret = Vector2(ret.x * 2 + ret.y, ret.y / 2); - break; - case TileSet::TILE_LAYOUT_STAIRS_DOWN: - ret = Vector2(ret.x, ret.y + ret.x / 2); - break; - case TileSet::TILE_LAYOUT_DIAMOND_RIGHT: - ret = Vector2(ret.x + ret.y, (ret.y - ret.x) / 2); - break; - case TileSet::TILE_LAYOUT_DIAMOND_DOWN: - ret = Vector2(ret.x - ret.y, (ret.y + ret.x) / 2); - break; - } - } - } - - // 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) { - overlapping_ratio = 0.5; - } else if (tile_shape == TileSet::TILE_SHAPE_HEXAGON) { - overlapping_ratio = 0.75; - } - ret.y *= overlapping_ratio; - } else { // TILE_OFFSET_AXIS_VERTICAL. - if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC) { - overlapping_ratio = 0.5; - } else if (tile_shape == TileSet::TILE_SHAPE_HEXAGON) { - overlapping_ratio = 0.75; - } - ret.x *= overlapping_ratio; - } - - return (ret + Vector2(0.5, 0.5)) * tile_set->get_tile_size(); + return tile_set->map_to_local(p_pos); } -Vector2i TileMap::local_to_map(const Vector2 &p_local_position) const { +Vector2i TileMap::local_to_map(const Vector2 &p_pos) const { ERR_FAIL_COND_V(!tile_set.is_valid(), Vector2i()); - - Vector2 ret = p_local_position; - ret /= tile_set->get_tile_size(); - - TileSet::TileShape tile_shape = tile_set->get_tile_shape(); - 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. - double overlapping_ratio = 1.0; - if (tile_offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { - if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC) { - overlapping_ratio = 0.5; - } else if (tile_shape == TileSet::TILE_SHAPE_HEXAGON) { - overlapping_ratio = 0.75; - } - ret.y /= overlapping_ratio; - } else { // TILE_OFFSET_AXIS_VERTICAL. - if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC) { - overlapping_ratio = 0.5; - } else if (tile_shape == TileSet::TILE_SHAPE_HEXAGON) { - overlapping_ratio = 0.75; - } - ret.x /= overlapping_ratio; - } - - // 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. - if (tile_offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { - // Smart floor of the position - Vector2 raw_pos = ret; - if (Math::posmod(Math::floor(ret.y), 2) ^ (tile_layout == TileSet::TILE_LAYOUT_STACKED_OFFSET)) { - ret = Vector2(Math::floor(ret.x + 0.5) - 0.5, Math::floor(ret.y)); - } else { - ret = ret.floor(); - } - - // 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; - - switch (tile_layout) { - case TileSet::TILE_LAYOUT_STACKED: - ret = ret.floor(); - if (in_top_left_triangle) { - ret += Vector2i(Math::posmod(Math::floor(ret.y), 2) ? 0 : -1, -1); - } else if (in_top_right_triangle) { - ret += Vector2i(Math::posmod(Math::floor(ret.y), 2) ? 1 : 0, -1); - } - break; - case TileSet::TILE_LAYOUT_STACKED_OFFSET: - ret = ret.floor(); - if (in_top_left_triangle) { - ret += Vector2i(Math::posmod(Math::floor(ret.y), 2) ? -1 : 0, -1); - } else if (in_top_right_triangle) { - ret += Vector2i(Math::posmod(Math::floor(ret.y), 2) ? 0 : 1, -1); - } - break; - case TileSet::TILE_LAYOUT_STAIRS_RIGHT: - ret = Vector2(ret.x - ret.y / 2, ret.y).floor(); - if (in_top_left_triangle) { - ret += Vector2i(0, -1); - } else if (in_top_right_triangle) { - ret += Vector2i(1, -1); - } - break; - case TileSet::TILE_LAYOUT_STAIRS_DOWN: - ret = Vector2(ret.x * 2, ret.y / 2 - ret.x).floor(); - if (in_top_left_triangle) { - ret += Vector2i(-1, 0); - } else if (in_top_right_triangle) { - ret += Vector2i(1, -1); - } - break; - case TileSet::TILE_LAYOUT_DIAMOND_RIGHT: - ret = Vector2(ret.x - ret.y / 2, ret.y / 2 + ret.x).floor(); - if (in_top_left_triangle) { - ret += Vector2i(0, -1); - } else if (in_top_right_triangle) { - ret += Vector2i(1, 0); - } - break; - case TileSet::TILE_LAYOUT_DIAMOND_DOWN: - ret = Vector2(ret.x + ret.y / 2, ret.y / 2 - ret.x).floor(); - if (in_top_left_triangle) { - ret += Vector2i(-1, 0); - } else if (in_top_right_triangle) { - ret += Vector2i(0, -1); - } - break; - } - } 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); - } else { - ret = ret.floor(); - } - - // 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; - - switch (tile_layout) { - case TileSet::TILE_LAYOUT_STACKED: - ret = ret.floor(); - if (in_top_left_triangle) { - ret += Vector2i(-1, Math::posmod(Math::floor(ret.x), 2) ? 0 : -1); - } else if (in_bottom_left_triangle) { - ret += Vector2i(-1, Math::posmod(Math::floor(ret.x), 2) ? 1 : 0); - } - break; - case TileSet::TILE_LAYOUT_STACKED_OFFSET: - ret = ret.floor(); - if (in_top_left_triangle) { - ret += Vector2i(-1, Math::posmod(Math::floor(ret.x), 2) ? -1 : 0); - } else if (in_bottom_left_triangle) { - ret += Vector2i(-1, Math::posmod(Math::floor(ret.x), 2) ? 0 : 1); - } - break; - case TileSet::TILE_LAYOUT_STAIRS_RIGHT: - ret = Vector2(ret.x / 2 - ret.y, ret.y * 2).floor(); - if (in_top_left_triangle) { - ret += Vector2i(0, -1); - } else if (in_bottom_left_triangle) { - ret += Vector2i(-1, 1); - } - break; - case TileSet::TILE_LAYOUT_STAIRS_DOWN: - ret = Vector2(ret.x, ret.y - ret.x / 2).floor(); - if (in_top_left_triangle) { - ret += Vector2i(-1, 0); - } else if (in_bottom_left_triangle) { - ret += Vector2i(-1, 1); - } - break; - case TileSet::TILE_LAYOUT_DIAMOND_RIGHT: - ret = Vector2(ret.x / 2 - ret.y, ret.y + ret.x / 2).floor(); - if (in_top_left_triangle) { - ret += Vector2i(0, -1); - } else if (in_bottom_left_triangle) { - ret += Vector2i(-1, 0); - } - break; - case TileSet::TILE_LAYOUT_DIAMOND_DOWN: - ret = Vector2(ret.x / 2 + ret.y, ret.y - ret.x / 2).floor(); - if (in_top_left_triangle) { - ret += Vector2i(-1, 0); - } else if (in_bottom_left_triangle) { - ret += Vector2i(0, 1); - } - break; - } - } - } else { - ret = (ret + Vector2(0.00005, 0.00005)).floor(); - } - return Vector2i(ret); + return tile_set->local_to_map(p_pos); } bool TileMap::is_existing_neighbor(TileSet::CellNeighbor p_cell_neighbor) const { ERR_FAIL_COND_V(!tile_set.is_valid(), false); - - TileSet::TileShape shape = tile_set->get_tile_shape(); - if (shape == TileSet::TILE_SHAPE_SQUARE) { - return p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_SIDE || - p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER || - p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_SIDE || - p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER || - p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_SIDE || - p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER || - p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_SIDE || - p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER; - - } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC) { - return p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_CORNER || - p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE || - p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_CORNER || - p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE || - p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_CORNER || - p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE || - p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_CORNER || - p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE; - } else { - if (tile_set->get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { - return p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_SIDE || - p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE || - p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE || - p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_SIDE || - p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE || - p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE; - } else { - return p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE || - p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_SIDE || - p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE || - p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE || - p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_SIDE || - p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE; - } - } + return tile_set->is_existing_neighbor(p_cell_neighbor); } Vector2i TileMap::get_neighbor_cell(const Vector2i &p_coords, TileSet::CellNeighbor p_cell_neighbor) const { - ERR_FAIL_COND_V(!tile_set.is_valid(), p_coords); - - TileSet::TileShape shape = tile_set->get_tile_shape(); - if (shape == TileSet::TILE_SHAPE_SQUARE) { - switch (p_cell_neighbor) { - case TileSet::CELL_NEIGHBOR_RIGHT_SIDE: - return p_coords + Vector2i(1, 0); - case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER: - return p_coords + Vector2i(1, 1); - case TileSet::CELL_NEIGHBOR_BOTTOM_SIDE: - return p_coords + Vector2i(0, 1); - case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER: - return p_coords + Vector2i(-1, 1); - case TileSet::CELL_NEIGHBOR_LEFT_SIDE: - return p_coords + Vector2i(-1, 0); - case TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER: - return p_coords + Vector2i(-1, -1); - case TileSet::CELL_NEIGHBOR_TOP_SIDE: - return p_coords + Vector2i(0, -1); - case TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER: - return p_coords + Vector2i(1, -1); - default: - ERR_FAIL_V(p_coords); - } - } 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) { - bool is_offset = p_coords.y % 2; - if ((shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_CORNER) || - (shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_SIDE)) { - return p_coords + Vector2i(1, 0); - } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE) { - return p_coords + Vector2i(is_offset ? 1 : 0, 1); - } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_CORNER) { - return p_coords + Vector2i(0, 2); - } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE) { - return p_coords + Vector2i(is_offset ? 0 : -1, 1); - } else if ((shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_CORNER) || - (shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_SIDE)) { - return p_coords + Vector2i(-1, 0); - } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE) { - return p_coords + Vector2i(is_offset ? 0 : -1, -1); - } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_CORNER) { - return p_coords + Vector2i(0, -2); - } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE) { - return p_coords + Vector2i(is_offset ? 1 : 0, -1); - } else { - ERR_FAIL_V(p_coords); - } - } else { - bool is_offset = p_coords.x % 2; - - if ((shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_CORNER) || - (shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)) { - return p_coords + Vector2i(0, 1); - } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE) { - return p_coords + Vector2i(1, is_offset ? 1 : 0); - } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_CORNER) { - return p_coords + Vector2i(2, 0); - } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE) { - return p_coords + Vector2i(1, is_offset ? 0 : -1); - } else if ((shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_CORNER) || - (shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_SIDE)) { - return p_coords + Vector2i(0, -1); - } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE) { - return p_coords + Vector2i(-1, is_offset ? 0 : -1); - } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_CORNER) { - return p_coords + Vector2i(-2, 0); - } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE) { - return p_coords + Vector2i(-1, is_offset ? 1 : 0); - } else { - ERR_FAIL_V(p_coords); - } - } - } break; - case TileSet::TILE_LAYOUT_STACKED_OFFSET: { - if (tile_set->get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { - bool is_offset = p_coords.y % 2; - - if ((shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_CORNER) || - (shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_SIDE)) { - return p_coords + Vector2i(1, 0); - } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE) { - return p_coords + Vector2i(is_offset ? 0 : 1, 1); - } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_CORNER) { - return p_coords + Vector2i(0, 2); - } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE) { - return p_coords + Vector2i(is_offset ? -1 : 0, 1); - } else if ((shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_CORNER) || - (shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_SIDE)) { - return p_coords + Vector2i(-1, 0); - } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE) { - return p_coords + Vector2i(is_offset ? -1 : 0, -1); - } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_CORNER) { - return p_coords + Vector2i(0, -2); - } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE) { - return p_coords + Vector2i(is_offset ? 0 : 1, -1); - } else { - ERR_FAIL_V(p_coords); - } - } else { - bool is_offset = p_coords.x % 2; - - if ((shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_CORNER) || - (shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)) { - return p_coords + Vector2i(0, 1); - } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE) { - return p_coords + Vector2i(1, is_offset ? 0 : 1); - } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_CORNER) { - return p_coords + Vector2i(2, 0); - } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE) { - return p_coords + Vector2i(1, is_offset ? -1 : 0); - } else if ((shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_CORNER) || - (shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_SIDE)) { - return p_coords + Vector2i(0, -1); - } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE) { - return p_coords + Vector2i(-1, is_offset ? -1 : 0); - } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_CORNER) { - return p_coords + Vector2i(-2, 0); - } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE) { - return p_coords + Vector2i(-1, is_offset ? 0 : 1); - } else { - ERR_FAIL_V(p_coords); - } - } - } break; - case TileSet::TILE_LAYOUT_STAIRS_RIGHT: - case TileSet::TILE_LAYOUT_STAIRS_DOWN: { - if ((tile_set->get_tile_layout() == TileSet::TILE_LAYOUT_STAIRS_RIGHT) ^ (tile_set->get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_VERTICAL)) { - if (tile_set->get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { - if ((shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_CORNER) || - (shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_SIDE)) { - return p_coords + Vector2i(1, 0); - } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE) { - return p_coords + Vector2i(0, 1); - } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_CORNER) { - return p_coords + Vector2i(-1, 2); - } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE) { - return p_coords + Vector2i(-1, 1); - } else if ((shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_CORNER) || - (shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_SIDE)) { - return p_coords + Vector2i(-1, 0); - } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE) { - return p_coords + Vector2i(0, -1); - } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_CORNER) { - return p_coords + Vector2i(1, -2); - } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE) { - return p_coords + Vector2i(1, -1); - } else { - ERR_FAIL_V(p_coords); - } - - } else { - if ((shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_CORNER) || - (shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)) { - return p_coords + Vector2i(0, 1); - } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE) { - return p_coords + Vector2i(1, 0); - } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_CORNER) { - return p_coords + Vector2i(2, -1); - } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE) { - return p_coords + Vector2i(1, -1); - } else if ((shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_CORNER) || - (shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_SIDE)) { - return p_coords + Vector2i(0, -1); - } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE) { - return p_coords + Vector2i(-1, 0); - } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_CORNER) { - return p_coords + Vector2i(-2, 1); - - } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE) { - return p_coords + Vector2i(-1, 1); - } else { - ERR_FAIL_V(p_coords); - } - } - } else { - if (tile_set->get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { - if ((shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_CORNER) || - (shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_SIDE)) { - return p_coords + Vector2i(2, -1); - } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE) { - return p_coords + Vector2i(1, 0); - } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_CORNER) { - return p_coords + Vector2i(0, 1); - } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE) { - return p_coords + Vector2i(-1, 1); - } else if ((shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_CORNER) || - (shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_SIDE)) { - return p_coords + Vector2i(-2, 1); - } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE) { - return p_coords + Vector2i(-1, 0); - } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_CORNER) { - return p_coords + Vector2i(0, -1); - } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE) { - return p_coords + Vector2i(1, -1); - } else { - ERR_FAIL_V(p_coords); - } - - } else { - if ((shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_CORNER) || - (shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)) { - return p_coords + Vector2i(-1, 2); - } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE) { - return p_coords + Vector2i(0, 1); - } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_CORNER) { - return p_coords + Vector2i(1, 0); - } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE) { - return p_coords + Vector2i(1, -1); - } else if ((shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_CORNER) || - (shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_SIDE)) { - return p_coords + Vector2i(1, -2); - } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE) { - return p_coords + Vector2i(0, -1); - } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_CORNER) { - return p_coords + Vector2i(-1, 0); - - } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE) { - return p_coords + Vector2i(-1, 1); - } else { - ERR_FAIL_V(p_coords); - } - } - } - } break; - case TileSet::TILE_LAYOUT_DIAMOND_RIGHT: - case TileSet::TILE_LAYOUT_DIAMOND_DOWN: { - if ((tile_set->get_tile_layout() == TileSet::TILE_LAYOUT_DIAMOND_RIGHT) ^ (tile_set->get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_VERTICAL)) { - if (tile_set->get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { - if ((shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_CORNER) || - (shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_SIDE)) { - return p_coords + Vector2i(1, 1); - } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE) { - return p_coords + Vector2i(0, 1); - } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_CORNER) { - return p_coords + Vector2i(-1, 1); - } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE) { - return p_coords + Vector2i(-1, 0); - } else if ((shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_CORNER) || - (shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_SIDE)) { - return p_coords + Vector2i(-1, -1); - } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE) { - return p_coords + Vector2i(0, -1); - } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_CORNER) { - return p_coords + Vector2i(1, -1); - } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE) { - return p_coords + Vector2i(1, 0); - } else { - ERR_FAIL_V(p_coords); - } - - } else { - if ((shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_CORNER) || - (shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)) { - return p_coords + Vector2i(1, 1); - } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE) { - return p_coords + Vector2i(1, 0); - } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_CORNER) { - return p_coords + Vector2i(1, -1); - } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE) { - return p_coords + Vector2i(0, -1); - } else if ((shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_CORNER) || - (shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_SIDE)) { - return p_coords + Vector2i(-1, -1); - } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE) { - return p_coords + Vector2i(-1, 0); - } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_CORNER) { - return p_coords + Vector2i(-1, 1); - - } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE) { - return p_coords + Vector2i(0, 1); - } else { - ERR_FAIL_V(p_coords); - } - } - } else { - if (tile_set->get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { - if ((shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_CORNER) || - (shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_SIDE)) { - return p_coords + Vector2i(1, -1); - } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE) { - return p_coords + Vector2i(1, 0); - } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_CORNER) { - return p_coords + Vector2i(1, 1); - } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE) { - return p_coords + Vector2i(0, 1); - } else if ((shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_CORNER) || - (shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_SIDE)) { - return p_coords + Vector2i(-1, 1); - } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE) { - return p_coords + Vector2i(-1, 0); - } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_CORNER) { - return p_coords + Vector2i(-1, -1); - } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE) { - return p_coords + Vector2i(0, -1); - } else { - ERR_FAIL_V(p_coords); - } - - } else { - if ((shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_CORNER) || - (shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)) { - return p_coords + Vector2i(-1, 1); - } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE) { - return p_coords + Vector2i(0, 1); - } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_CORNER) { - return p_coords + Vector2i(1, 1); - } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE) { - return p_coords + Vector2i(1, 0); - } else if ((shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_CORNER) || - (shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_SIDE)) { - return p_coords + Vector2i(1, -1); - } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE) { - return p_coords + Vector2i(0, -1); - } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_CORNER) { - return p_coords + Vector2i(-1, -1); - - } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE) { - return p_coords + Vector2i(-1, 0); - } else { - ERR_FAIL_V(p_coords); - } - } - } - } break; - default: - ERR_FAIL_V(p_coords); - } - } + ERR_FAIL_COND_V(!tile_set.is_valid(), Vector2i()); + return tile_set->get_neighbor_cell(p_coords, p_cell_neighbor); } TypedArray<Vector2i> TileMap::get_used_cells(int p_layer) const { @@ -1616,7 +953,7 @@ Rect2i TileMap::get_used_rect() const { // Return the visible rect of the tilemap. bool first = true; Rect2i rect = Rect2i(); - for (const Ref<TileMapLayer> &layer : layers) { + for (const TileMapLayer *layer : layers) { Rect2i layer_rect = layer->get_used_rect(); if (layer_rect == Rect2i()) { continue; @@ -1636,7 +973,7 @@ Rect2i TileMap::get_used_rect() const { void TileMap::set_light_mask(int p_light_mask) { // Occlusion: set light mask. CanvasItem::set_light_mask(p_light_mask); - for (Ref<TileMapLayer> &layer : layers) { + for (TileMapLayer *layer : layers) { layer->notify_tile_map_change(TileMapLayer::DIRTY_FLAGS_TILE_MAP_LIGHT_MASK); } } @@ -1646,7 +983,7 @@ void TileMap::set_material(const Ref<Material> &p_material) { CanvasItem::set_material(p_material); // Update material for the whole tilemap. - for (Ref<TileMapLayer> &layer : layers) { + for (TileMapLayer *layer : layers) { layer->notify_tile_map_change(TileMapLayer::DIRTY_FLAGS_TILE_MAP_MATERIAL); } } @@ -1656,7 +993,7 @@ 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 (Ref<TileMapLayer> &layer : layers) { + for (TileMapLayer *layer : layers) { layer->notify_tile_map_change(TileMapLayer::DIRTY_FLAGS_TILE_MAP_USE_PARENT_MATERIAL); } } @@ -1664,7 +1001,7 @@ void TileMap::set_use_parent_material(bool p_use_parent_material) { 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); - for (Ref<TileMapLayer> &layer : layers) { + for (TileMapLayer *layer : layers) { layer->notify_tile_map_change(TileMapLayer::DIRTY_FLAGS_TILE_MAP_TEXTURE_FILTER); } } @@ -1672,7 +1009,7 @@ void TileMap::set_texture_filter(TextureFilter p_texture_filter) { 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); - for (Ref<TileMapLayer> &layer : layers) { + for (TileMapLayer *layer : layers) { layer->notify_tile_map_change(TileMapLayer::DIRTY_FLAGS_TILE_MAP_TEXTURE_REPEAT); } } @@ -1771,14 +1108,14 @@ 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 Ref<TileMapLayer> &layer : layers) { + for (const 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 Ref<TileMapLayer> &layer : layers) { + for (const 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; @@ -1787,7 +1124,7 @@ PackedStringArray TileMap::get_configuration_warnings() const { if (!is_y_sort_enabled()) { // Check if Y-sort is enabled on a layer but not on the node. - for (const Ref<TileMapLayer> &layer : layers) { + for (const 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; @@ -1796,7 +1133,7 @@ PackedStringArray TileMap::get_configuration_warnings() const { } else { // Check if Y-sort is enabled on the node, but not on any of the layers. bool need_warning = true; - for (const Ref<TileMapLayer> &layer : layers) { + for (const TileMapLayer *layer : layers) { if (layer->is_y_sort_enabled()) { need_warning = false; break; @@ -1811,7 +1148,7 @@ 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 Ref<TileMapLayer> &layer : layers) { + for (const TileMapLayer *layer : layers) { if (!layer->is_y_sort_enabled()) { warn = true; break; @@ -1926,42 +1263,27 @@ void TileMap::_bind_methods() { void TileMap::_tile_set_changed() { emit_signal(CoreStringNames::get_singleton()->changed); - for (Ref<TileMapLayer> layer : layers) { + for (TileMapLayer *layer : layers) { layer->notify_tile_map_change(TileMapLayer::DIRTY_FLAGS_TILE_MAP_TILE_SET); } update_configuration_warnings(); } -void TileMap::_update_notify_local_transform() { - bool notify = collision_animatable || is_y_sort_enabled(); - if (!notify) { - for (const Ref<TileMapLayer> &layer : layers) { - if (layer->is_y_sort_enabled()) { - notify = true; - break; - } - } - } - set_notify_local_transform(notify); -} - TileMap::TileMap() { - set_notify_transform(true); - _update_notify_local_transform(); - - Ref<TileMapLayer> new_layer; - new_layer.instantiate(); - new_layer->set_tile_map(this); + TileMapLayer *new_layer = memnew(TileMapLayer); + add_child(new_layer); + new_layer->set_name("Layer0"); new_layer->set_layer_index_in_tile_map_node(0); layers.push_back(new_layer); - default_layer.instantiate(); + default_layer = memnew(TileMapLayer); } TileMap::~TileMap() { if (tile_set.is_valid()) { tile_set->disconnect_changed(callable_mp(this, &TileMap::_tile_set_changed)); } + memdelete(default_layer); } #undef TILEMAP_CALL_FOR_LAYER diff --git a/scene/2d/tile_map.h b/scene/2d/tile_map.h index 29af0ad2b1..f2b481cdd1 100644 --- a/scene/2d/tile_map.h +++ b/scene/2d/tile_map.h @@ -71,18 +71,17 @@ private: VisibilityMode navigation_visibility_mode = VISIBILITY_MODE_DEFAULT; // Layers. - LocalVector<Ref<TileMapLayer>> layers; - Ref<TileMapLayer> default_layer; // Dummy layer to fetch default values. + LocalVector<TileMapLayer *> layers; + TileMapLayer *default_layer; // Dummy layer to fetch default values. int selected_layer = -1; bool pending_update = false; + // Transforms for collision_animatable. Transform2D last_valid_transform; Transform2D new_transform; void _tile_set_changed(); - void _update_notify_local_transform(); - protected: bool _set(const StringName &p_name, const Variant &p_value); bool _get(const StringName &p_name, Variant &r_ret) const; @@ -97,6 +96,8 @@ protected: Rect2i _get_used_rect_bind_compat_78328(); void _set_quadrant_size_compat_81070(int p_quadrant_size); int _get_quadrant_size_compat_81070() const; + VisibilityMode _get_collision_visibility_mode_bind_compat_87115(); + VisibilityMode _get_navigation_visibility_mode_bind_compat_87115(); static void _bind_compatibility_methods(); #endif @@ -150,15 +151,15 @@ public: void set_selected_layer(int p_layer_id); // For editor use. int get_selected_layer() const; - void set_collision_animatable(bool p_enabled); + void set_collision_animatable(bool p_collision_animatable); bool is_collision_animatable() const; // Debug visibility modes. void set_collision_visibility_mode(VisibilityMode p_show_collision); - VisibilityMode get_collision_visibility_mode(); + VisibilityMode get_collision_visibility_mode() const; void set_navigation_visibility_mode(VisibilityMode p_show_navigation); - VisibilityMode get_navigation_visibility_mode(); + VisibilityMode get_navigation_visibility_mode() 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); @@ -192,7 +193,6 @@ public: Vector2 map_to_local(const Vector2i &p_pos) const; Vector2i local_to_map(const Vector2 &p_pos) const; - bool is_existing_neighbor(TileSet::CellNeighbor p_cell_neighbor) const; Vector2i get_neighbor_cell(const Vector2i &p_coords, TileSet::CellNeighbor p_cell_neighbor) const; diff --git a/scene/2d/tile_map_layer.cpp b/scene/2d/tile_map_layer.cpp index ed93b8d051..df79b3fee6 100644 --- a/scene/2d/tile_map_layer.cpp +++ b/scene/2d/tile_map_layer.cpp @@ -40,6 +40,18 @@ #include "servers/navigation_server_3d.h" #endif // DEBUG_ENABLED +TileMap *TileMapLayer::_fetch_tilemap() const { + return TileMap::cast_to<TileMap>(get_parent()); +} + +Ref<TileSet> TileMapLayer::_fetch_tileset() const { + TileMap *tile_map_node = _fetch_tilemap(); + if (!tile_map_node) { + return Ref<TileSet>(); + } + return tile_map_node->get_tileset(); +} + #ifdef DEBUG_ENABLED /////////////////////////////// Debug ////////////////////////////////////////// constexpr int TILE_MAP_DEBUG_QUADRANT_SIZE = 16; @@ -51,11 +63,11 @@ Vector2i TileMapLayer::_coords_to_debug_quadrant_coords(const Vector2i &p_coords } void TileMapLayer::_debug_update() { - const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); + const Ref<TileSet> &tile_set = _fetch_tileset(); RenderingServer *rs = RenderingServer::get_singleton(); // Check if we should cleanup everything. - bool forced_cleanup = in_destructor || !enabled || !tile_map_node->is_inside_tree() || !tile_set.is_valid() || !tile_map_node->is_visible_in_tree(); + bool forced_cleanup = in_destructor || !enabled || !tile_set.is_valid() || !is_visible_in_tree(); if (forced_cleanup) { for (KeyValue<Vector2i, Ref<DebugQuadrant>> &kv : debug_quadrant_map) { @@ -120,10 +132,10 @@ void TileMapLayer::_debug_update() { } else { ci = rs->canvas_item_create(); rs->canvas_item_set_z_index(ci, RS::CANVAS_ITEM_Z_MAX - 1); - rs->canvas_item_set_parent(ci, tile_map_node->get_canvas_item()); + rs->canvas_item_set_parent(ci, get_canvas_item()); } - const Vector2 quadrant_pos = tile_map_node->map_to_local(debug_quadrant.quadrant_coords * TILE_MAP_DEBUG_QUADRANT_SIZE); + const Vector2 quadrant_pos = tile_set->map_to_local(debug_quadrant.quadrant_coords * TILE_MAP_DEBUG_QUADRANT_SIZE); Transform2D xform(0, quadrant_pos); rs->canvas_item_set_transform(ci, xform); @@ -179,48 +191,33 @@ void TileMapLayer::_debug_quadrants_update_cell(CellData &r_cell_data, SelfList< /////////////////////////////// Rendering ////////////////////////////////////// void TileMapLayer::_rendering_update() { - const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); + const TileMap *tile_map_node = _fetch_tilemap(); + const Ref<TileSet> &tile_set = _fetch_tileset(); RenderingServer *rs = RenderingServer::get_singleton(); // Check if we should cleanup everything. - bool forced_cleanup = in_destructor || !enabled || !tile_map_node->is_inside_tree() || !tile_set.is_valid() || !tile_map_node->is_visible_in_tree(); + bool forced_cleanup = in_destructor || !enabled || !tile_set.is_valid() || !is_visible_in_tree(); // ----------- Layer level processing ----------- - if (forced_cleanup) { - // Cleanup. - if (canvas_item.is_valid()) { - rs->free(canvas_item); - canvas_item = RID(); - } - } else { - // Create/Update the layer's CanvasItem. - if (!canvas_item.is_valid()) { - RID ci = rs->canvas_item_create(); - rs->canvas_item_set_parent(ci, tile_map_node->get_canvas_item()); - canvas_item = ci; - } - RID &ci = canvas_item; - rs->canvas_item_set_draw_index(ci, layer_index_in_tile_map_node - (int64_t)0x80000000); - 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()); + if (!forced_cleanup) { + // Update the layer's CanvasItem. + set_use_parent_material(true); + set_light_mask(tile_map_node->get_light_mask()); // Modulate the layer. - Color layer_modulate = modulate; + Color layer_modulate = get_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)) { + int layer_z_index = get_z_index(); + if (layer_z_index < z_selected || (layer_z_index == z_selected && layer_index_in_tile_map_node < selected_layer)) { layer_modulate = layer_modulate.darkened(0.5); - } else if (z_index > z_selected || (z_index == z_selected && layer_index_in_tile_map_node > selected_layer)) { + } else if (layer_z_index > z_selected || (layer_z_index == z_selected && layer_index_in_tile_map_node > selected_layer)) { layer_modulate = layer_modulate.darkened(0.5); layer_modulate.a *= 0.3; } } - rs->canvas_item_set_modulate(ci, layer_modulate); + rs->canvas_item_set_modulate(get_canvas_item(), layer_modulate); } // ----------- Quadrants processing ----------- @@ -231,7 +228,7 @@ void TileMapLayer::_rendering_update() { // Check if anything changed that might change the quadrant shape. // If so, recreate everything. bool quandrant_shape_changed = dirty.flags[DIRTY_FLAGS_TILE_MAP_QUADRANT_SIZE] || - (tile_map_node->is_y_sort_enabled() && y_sort_enabled && (dirty.flags[DIRTY_FLAGS_LAYER_Y_SORT_ENABLED] || dirty.flags[DIRTY_FLAGS_LAYER_Y_SORT_ORIGIN] || dirty.flags[DIRTY_FLAGS_TILE_MAP_Y_SORT_ENABLED] || dirty.flags[DIRTY_FLAGS_TILE_MAP_LOCAL_XFORM] || dirty.flags[DIRTY_FLAGS_TILE_MAP_TILE_SET])); + (is_y_sort_enabled() && (dirty.flags[DIRTY_FLAGS_LAYER_Y_SORT_ENABLED] || dirty.flags[DIRTY_FLAGS_LAYER_Y_SORT_ORIGIN] || dirty.flags[DIRTY_FLAGS_TILE_MAP_Y_SORT_ENABLED] || dirty.flags[DIRTY_FLAGS_LAYER_LOCAL_TRANSFORM] || dirty.flags[DIRTY_FLAGS_TILE_MAP_TILE_SET])); // Free all quadrants. if (forced_cleanup || quandrant_shape_changed) { @@ -250,7 +247,7 @@ void TileMapLayer::_rendering_update() { if (!forced_cleanup) { // List all quadrants to update, recreating them if needed. - if (dirty.flags[DIRTY_FLAGS_TILE_MAP_TILE_SET] || _rendering_was_cleaned_up) { + if (dirty.flags[DIRTY_FLAGS_TILE_MAP_TILE_SET] || dirty.flags[DIRTY_FLAGS_LAYER_IN_TREE] || _rendering_was_cleaned_up) { // Update all cells. for (KeyValue<Vector2i, CellData> &kv : tile_map) { CellData &cell_data = kv.value; @@ -290,7 +287,7 @@ void TileMapLayer::_rendering_update() { rendering_quadrant->canvas_items.clear(); // Sort the quadrant cells. - if (tile_map_node->is_y_sort_enabled() && is_y_sort_enabled()) { + if (is_y_sort_enabled()) { // For compatibility reasons, we use another comparator for Y-sorted layers. rendering_quadrant->cells.sort_custom<CellDataYSortedComparator>(); } else { @@ -330,8 +327,8 @@ void TileMapLayer::_rendering_update() { if (mat.is_valid()) { rs->canvas_item_set_material(ci, mat->get_rid()); } - 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()); + rs->canvas_item_set_parent(ci, get_canvas_item()); + rs->canvas_item_set_use_parent_material(ci, true); Transform2D xform(0, rendering_quadrant->canvas_items_position); rs->canvas_item_set_transform(ci, xform); @@ -340,8 +337,8 @@ void TileMapLayer::_rendering_update() { 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(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_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())); rendering_quadrant->canvas_items.push_back(ci); @@ -354,7 +351,7 @@ void TileMapLayer::_rendering_update() { ci = prev_ci; } - const Vector2 local_tile_pos = tile_map_node->map_to_local(cell_data.coords); + const Vector2 local_tile_pos = tile_set->map_to_local(cell_data.coords); // Random animation offset. real_t random_animation_offset = 0.0; @@ -366,7 +363,7 @@ void TileMapLayer::_rendering_update() { } // Drawing the tile in the canvas item. - tile_map_node->draw_tile(ci, local_tile_pos - rendering_quadrant->canvas_items_position, tile_set, cell_data.cell.source_id, cell_data.cell.get_atlas_coords(), cell_data.cell.alternative_tile, -1, tile_map_node->get_self_modulate(), tile_data, random_animation_offset); + TileMap::draw_tile(ci, local_tile_pos - rendering_quadrant->canvas_items_position, tile_set, cell_data.cell.source_id, cell_data.cell.get_atlas_coords(), cell_data.cell.alternative_tile, -1, get_self_modulate(), tile_data, random_animation_offset); } } else { // Free the quadrant. @@ -393,7 +390,7 @@ void TileMapLayer::_rendering_update() { RBMap<Vector2, Ref<RenderingQuadrant>, RenderingQuadrant::CoordsWorldComparator> local_to_map; for (KeyValue<Vector2i, Ref<RenderingQuadrant>> &kv : rendering_quadrant_map) { Ref<RenderingQuadrant> &rendering_quadrant = kv.value; - local_to_map[tile_map_node->map_to_local(rendering_quadrant->quadrant_coords)] = rendering_quadrant; + local_to_map[tile_set->map_to_local(rendering_quadrant->quadrant_coords)] = rendering_quadrant; } // Sort the quadrants. @@ -409,14 +406,15 @@ void TileMapLayer::_rendering_update() { dirty.flags[DIRTY_FLAGS_TILE_MAP_USE_PARENT_MATERIAL] || dirty.flags[DIRTY_FLAGS_TILE_MAP_MATERIAL] || dirty.flags[DIRTY_FLAGS_TILE_MAP_TEXTURE_FILTER] || - dirty.flags[DIRTY_FLAGS_TILE_MAP_TEXTURE_REPEAT]) { + dirty.flags[DIRTY_FLAGS_TILE_MAP_TEXTURE_REPEAT] || + dirty.flags[DIRTY_FLAGS_LAYER_SELF_MODULATE]) { for (KeyValue<Vector2i, Ref<RenderingQuadrant>> &kv : rendering_quadrant_map) { Ref<RenderingQuadrant> &rendering_quadrant = kv.value; for (const RID &ci : rendering_quadrant->canvas_items) { rs->canvas_item_set_light_mask(ci, tile_map_node->get_light_mask()); - 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_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_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_self_modulate(ci, get_self_modulate()); } } } @@ -441,30 +439,37 @@ void TileMapLayer::_rendering_update() { _rendering_occluders_update_cell(cell_data); } } + } - // Updates on TileMap changes. - if (dirty.flags[DIRTY_FLAGS_TILE_MAP_IN_CANVAS] || dirty.flags[DIRTY_FLAGS_TILE_MAP_VISIBILITY]) { + // ----------- + // Mark the rendering state as up to date. + _rendering_was_cleaned_up = forced_cleanup; +} + +void TileMapLayer::_rendering_notification(int p_what) { + RenderingServer *rs = RenderingServer::get_singleton(); + const Ref<TileSet> &tile_set = _fetch_tileset(); + if (p_what == NOTIFICATION_TRANSFORM_CHANGED || p_what == NOTIFICATION_ENTER_CANVAS || p_what == NOTIFICATION_VISIBILITY_CHANGED) { + if (tile_set.is_valid()) { + Transform2D tilemap_xform = get_global_transform(); for (KeyValue<Vector2i, CellData> &kv : tile_map) { - CellData &cell_data = kv.value; + const CellData &cell_data = kv.value; for (const RID &occluder : cell_data.occluders) { if (occluder.is_null()) { continue; } - Transform2D xform(0, tile_map_node->map_to_local(kv.key)); - rs->canvas_light_occluder_attach_to_canvas(occluder, tile_map_node->get_canvas()); - rs->canvas_light_occluder_set_transform(occluder, tile_map_node->get_global_transform() * xform); + Transform2D xform(0, tile_set->map_to_local(kv.key)); + rs->canvas_light_occluder_attach_to_canvas(occluder, get_canvas()); + rs->canvas_light_occluder_set_transform(occluder, tilemap_xform * xform); } } } } - - // ----------- - // Mark the rendering state as up to date. - _rendering_was_cleaned_up = forced_cleanup; } void TileMapLayer::_rendering_quadrants_update_cell(CellData &r_cell_data, SelfList<RenderingQuadrant>::List &r_dirty_rendering_quadrant_list) { - const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); + const TileMap *tile_map_node = _fetch_tilemap(); + const Ref<TileSet> &tile_set = _fetch_tileset(); // Check if the cell is valid and retrieve its y_sort_origin. bool is_valid = false; @@ -489,8 +494,8 @@ void TileMapLayer::_rendering_quadrants_update_cell(CellData &r_cell_data, SelfL // Get the quadrant coords. Vector2 canvas_items_position; Vector2i quadrant_coords; - if (tile_map_node->is_y_sort_enabled() && is_y_sort_enabled()) { - canvas_items_position = Vector2(0, tile_map_node->map_to_local(r_cell_data.coords).y + tile_y_sort_origin + y_sort_origin); + if (is_y_sort_enabled()) { + canvas_items_position = Vector2(0, tile_set->map_to_local(r_cell_data.coords).y + tile_y_sort_origin + y_sort_origin); quadrant_coords = canvas_items_position * 100; } else { int quad_size = tile_map_node->get_rendering_quadrant_size(); @@ -500,7 +505,7 @@ void TileMapLayer::_rendering_quadrants_update_cell(CellData &r_cell_data, SelfL quadrant_coords = Vector2i( coords.x > 0 ? coords.x / quad_size : (coords.x - (quad_size - 1)) / quad_size, coords.y > 0 ? coords.y / quad_size : (coords.y - (quad_size - 1)) / quad_size); - canvas_items_position = tile_map_node->map_to_local(quad_size * quadrant_coords); + canvas_items_position = tile_set->map_to_local(quad_size * quadrant_coords); } Ref<RenderingQuadrant> rendering_quadrant; @@ -564,8 +569,7 @@ void TileMapLayer::_rendering_occluders_clear_cell(CellData &r_cell_data) { } void TileMapLayer::_rendering_occluders_update_cell(CellData &r_cell_data) { - bool node_visible = tile_map_node->is_visible_in_tree(); - const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); + const Ref<TileSet> &tile_set = _fetch_tileset(); RenderingServer *rs = RenderingServer::get_singleton(); // Free unused occluders then resize the occluders array. @@ -606,14 +610,13 @@ void TileMapLayer::_rendering_occluders_update_cell(CellData &r_cell_data) { if (occluder_polygon.is_valid()) { // Create or update occluder. Transform2D xform; - xform.set_origin(tile_map_node->map_to_local(r_cell_data.coords)); + xform.set_origin(tile_set->map_to_local(r_cell_data.coords)); if (!occluder.is_valid()) { occluder = rs->canvas_light_occluder_create(); } - rs->canvas_light_occluder_set_enabled(occluder, node_visible); - rs->canvas_light_occluder_set_transform(occluder, tile_map_node->get_global_transform() * xform); + rs->canvas_light_occluder_set_transform(occluder, get_global_transform() * xform); rs->canvas_light_occluder_set_polygon(occluder, tile_data->get_occluder(occlusion_layer_index, flip_h, flip_v, transpose)->get_rid()); - rs->canvas_light_occluder_attach_to_canvas(occluder, tile_map_node->get_canvas()); + rs->canvas_light_occluder_attach_to_canvas(occluder, get_canvas()); rs->canvas_light_occluder_set_light_mask(occluder, tile_set->get_occlusion_layer_light_mask(occlusion_layer_index)); } else { // Clear occluder. @@ -635,7 +638,7 @@ void TileMapLayer::_rendering_occluders_update_cell(CellData &r_cell_data) { #ifdef DEBUG_ENABLED void TileMapLayer::_rendering_draw_cell_debug(const RID &p_canvas_item, const Vector2 &p_quadrant_pos, const CellData &r_cell_data) { - const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); + const Ref<TileSet> &tile_set = _fetch_tileset(); ERR_FAIL_COND(!tile_set.is_valid()); if (!Engine::get_singleton()->is_editor_hint()) { @@ -671,7 +674,7 @@ void TileMapLayer::_rendering_draw_cell_debug(const RID &p_canvas_item, const Ve // Draw a placeholder tile. Transform2D cell_to_quadrant; - cell_to_quadrant.set_origin(tile_map_node->map_to_local(r_cell_data.coords) - p_quadrant_pos); + cell_to_quadrant.set_origin(tile_set->map_to_local(r_cell_data.coords) - p_quadrant_pos); rs->canvas_item_add_set_transform(p_canvas_item, cell_to_quadrant); rs->canvas_item_add_circle(p_canvas_item, Vector2(), MIN(tile_set->get_tile_size().x, tile_set->get_tile_size().y) / 4.0, color); } @@ -684,17 +687,17 @@ void TileMapLayer::_rendering_draw_cell_debug(const RID &p_canvas_item, const Ve /////////////////////////////// Physics ////////////////////////////////////// void TileMapLayer::_physics_update() { - const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); + const Ref<TileSet> &tile_set = _fetch_tileset(); // Check if we should cleanup everything. - bool forced_cleanup = in_destructor || !enabled || !tile_map_node->is_inside_tree() || !tile_set.is_valid(); + bool forced_cleanup = in_destructor || !enabled || !is_inside_tree() || !tile_set.is_valid(); if (forced_cleanup) { // Clean everything. for (KeyValue<Vector2i, CellData> &kv : tile_map) { _physics_clear_cell(kv.value); } } else { - if (_physics_was_cleaned_up || dirty.flags[DIRTY_FLAGS_TILE_MAP_TILE_SET] || dirty.flags[DIRTY_FLAGS_TILE_MAP_COLLISION_ANIMATABLE]) { + if (_physics_was_cleaned_up || dirty.flags[DIRTY_FLAGS_TILE_MAP_TILE_SET] || dirty.flags[DIRTY_FLAGS_LAYER_USE_KINEMATIC_BODIES] || dirty.flags[DIRTY_FLAGS_LAYER_IN_TREE]) { // Update all cells. for (KeyValue<Vector2i, CellData> &kv : tile_map) { _physics_update_cell(kv.value); @@ -713,60 +716,43 @@ void TileMapLayer::_physics_update() { _physics_was_cleaned_up = forced_cleanup; } -void TileMapLayer::_physics_notify_tilemap_change(TileMapLayer::DirtyFlags p_what) { - Transform2D gl_transform = tile_map_node->get_global_transform(); +void TileMapLayer::_physics_notification(int p_what) { + const Ref<TileSet> &tile_set = _fetch_tileset(); + Transform2D gl_transform = get_global_transform(); PhysicsServer2D *ps = PhysicsServer2D::get_singleton(); - bool in_editor = false; -#ifdef TOOLS_ENABLED - in_editor = Engine::get_singleton()->is_editor_hint(); -#endif - - if (p_what == DIRTY_FLAGS_TILE_MAP_XFORM) { - if (tile_map_node->is_inside_tree() && (!tile_map_node->is_collision_animatable() || in_editor)) { + switch (p_what) { + case NOTIFICATION_TRANSFORM_CHANGED: // Move the collisison shapes along with the TileMap. - for (KeyValue<Vector2i, CellData> &kv : tile_map) { - const CellData &cell_data = kv.value; + if (is_inside_tree() && tile_set.is_valid()) { + for (KeyValue<Vector2i, CellData> &kv : tile_map) { + const CellData &cell_data = kv.value; - for (RID body : cell_data.bodies) { - if (body.is_valid()) { - Transform2D xform(0, tile_map_node->map_to_local(bodies_coords[body])); - xform = gl_transform * xform; - ps->body_set_state(body, PhysicsServer2D::BODY_STATE_TRANSFORM, xform); - } - } - } - } - } else if (p_what == DIRTY_FLAGS_TILE_MAP_LOCAL_XFORM) { - // With collisions animatable, move the collisison shapes along with the TileMap only on local xform change (they are synchornized on physics tick instead). - if (tile_map_node->is_inside_tree() && tile_map_node->is_collision_animatable() && !in_editor) { - for (KeyValue<Vector2i, CellData> &kv : tile_map) { - const CellData &cell_data = kv.value; - - for (RID body : cell_data.bodies) { - if (body.is_valid()) { - Transform2D xform(0, tile_map_node->map_to_local(bodies_coords[body])); - xform = gl_transform * xform; - ps->body_set_state(body, PhysicsServer2D::BODY_STATE_TRANSFORM, xform); + for (RID body : cell_data.bodies) { + if (body.is_valid()) { + Transform2D xform(0, tile_set->map_to_local(kv.key)); + xform = gl_transform * xform; + ps->body_set_state(body, PhysicsServer2D::BODY_STATE_TRANSFORM, xform); + } } } } - } - } else if (p_what == DIRTY_FLAGS_TILE_MAP_IN_TREE) { - // Changes in the tree may cause the space to change (e.g. when reparenting to a SubViewport). - if (tile_map_node->is_inside_tree()) { - RID space = tile_map_node->get_world_2d()->get_space(); + break; + case NOTIFICATION_ENTER_TREE: + // Changes in the tree may cause the space to change (e.g. when reparenting to a SubViewport). + if (is_inside_tree()) { + RID space = get_world_2d()->get_space(); - for (KeyValue<Vector2i, CellData> &kv : tile_map) { - const CellData &cell_data = kv.value; + for (KeyValue<Vector2i, CellData> &kv : tile_map) { + const CellData &cell_data = kv.value; - for (RID body : cell_data.bodies) { - if (body.is_valid()) { - ps->body_set_space(body, space); + for (RID body : cell_data.bodies) { + if (body.is_valid()) { + ps->body_set_space(body, space); + } } } } - } } } @@ -784,9 +770,10 @@ void TileMapLayer::_physics_clear_cell(CellData &r_cell_data) { } void TileMapLayer::_physics_update_cell(CellData &r_cell_data) { - const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); - Transform2D gl_transform = tile_map_node->get_global_transform(); - RID space = tile_map_node->get_world_2d()->get_space(); + const TileMap *tile_map_node = _fetch_tilemap(); + const Ref<TileSet> &tile_set = _fetch_tileset(); + Transform2D gl_transform = get_global_transform(); + RID space = get_world_2d()->get_space(); PhysicsServer2D *ps = PhysicsServer2D::get_singleton(); // Recreate bodies and shapes. @@ -813,10 +800,11 @@ void TileMapLayer::_physics_update_cell(CellData &r_cell_data) { // Free unused bodies then resize the bodies array. for (uint32_t i = tile_set->get_physics_layers_count(); i < r_cell_data.bodies.size(); i++) { - RID body = r_cell_data.bodies[i]; + RID &body = r_cell_data.bodies[i]; if (body.is_valid()) { bodies_coords.erase(body); ps->free(body); + body = RID(); } } r_cell_data.bodies.resize(tile_set->get_physics_layers_count()); @@ -844,7 +832,7 @@ void TileMapLayer::_physics_update_cell(CellData &r_cell_data) { ps->body_set_space(body, space); Transform2D xform; - xform.set_origin(tile_map_node->map_to_local(r_cell_data.coords)); + xform.set_origin(tile_set->map_to_local(r_cell_data.coords)); xform = gl_transform * xform; ps->body_set_state(body, PhysicsServer2D::BODY_STATE_TRANSFORM, xform); @@ -900,17 +888,18 @@ void TileMapLayer::_physics_update_cell(CellData &r_cell_data) { #ifdef DEBUG_ENABLED void TileMapLayer::_physics_draw_cell_debug(const RID &p_canvas_item, const Vector2 &p_quadrant_pos, const CellData &r_cell_data) { // Draw the debug collision shapes. - const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); + TileMap *tile_map_node = _fetch_tilemap(); + const Ref<TileSet> &tile_set = _fetch_tileset(); ERR_FAIL_COND(!tile_set.is_valid()); - if (!tile_map_node->get_tree()) { + if (!get_tree()) { return; } bool show_collision = false; switch (tile_map_node->get_collision_visibility_mode()) { case TileMap::VISIBILITY_MODE_DEFAULT: - show_collision = !Engine::get_singleton()->is_editor_hint() && tile_map_node->get_tree()->is_debugging_collisions_hint(); + show_collision = !Engine::get_singleton()->is_editor_hint() && get_tree()->is_debugging_collisions_hint(); break; case TileMap::VISIBILITY_MODE_FORCE_HIDE: show_collision = false; @@ -926,12 +915,12 @@ void TileMapLayer::_physics_draw_cell_debug(const RID &p_canvas_item, const Vect RenderingServer *rs = RenderingServer::get_singleton(); PhysicsServer2D *ps = PhysicsServer2D::get_singleton(); - Color debug_collision_color = tile_map_node->get_tree()->get_debug_collisions_color(); + Color debug_collision_color = get_tree()->get_debug_collisions_color(); Vector<Color> color; color.push_back(debug_collision_color); Transform2D quadrant_to_local(0, p_quadrant_pos); - Transform2D global_to_quadrant = (tile_map_node->get_global_transform() * quadrant_to_local).affine_inverse(); + Transform2D global_to_quadrant = (get_global_transform() * quadrant_to_local).affine_inverse(); for (RID body : r_cell_data.bodies) { if (body.is_valid()) { @@ -956,11 +945,11 @@ void TileMapLayer::_physics_draw_cell_debug(const RID &p_canvas_item, const Vect void TileMapLayer::_navigation_update() { ERR_FAIL_NULL(NavigationServer2D::get_singleton()); - const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); + const Ref<TileSet> &tile_set = _fetch_tileset(); NavigationServer2D *ns = NavigationServer2D::get_singleton(); // Check if we should cleanup everything. - bool forced_cleanup = in_destructor || !enabled || !navigation_enabled || !tile_map_node->is_inside_tree() || !tile_set.is_valid(); + bool forced_cleanup = in_destructor || !enabled || !navigation_enabled || !is_inside_tree() || !tile_set.is_valid(); // ----------- Layer level processing ----------- if (forced_cleanup) { @@ -973,7 +962,7 @@ void TileMapLayer::_navigation_update() { if (!navigation_map.is_valid()) { if (layer_index_in_tile_map_node == 0) { // Use the default World2D navigation map for the first layer when empty. - navigation_map = tile_map_node->get_world_2d()->get_navigation_map(); + navigation_map = get_world_2d()->get_navigation_map(); uses_world_navigation_map = true; } else { RID new_layer_map = ns->map_create(); @@ -993,7 +982,7 @@ void TileMapLayer::_navigation_update() { _navigation_clear_cell(kv.value); } } else { - if (_navigation_was_cleaned_up || dirty.flags[DIRTY_FLAGS_TILE_MAP_TILE_SET]) { + if (_navigation_was_cleaned_up || dirty.flags[DIRTY_FLAGS_TILE_MAP_TILE_SET] || dirty.flags[DIRTY_FLAGS_LAYER_IN_TREE]) { // Update all cells. for (KeyValue<Vector2i, CellData> &kv : tile_map) { _navigation_update_cell(kv.value); @@ -1005,9 +994,18 @@ void TileMapLayer::_navigation_update() { _navigation_update_cell(cell_data); } } + } + + // ----------- + // Mark the navigation state as up to date. + _navigation_was_cleaned_up = forced_cleanup; +} - if (dirty.flags[DIRTY_FLAGS_TILE_MAP_XFORM]) { - Transform2D tilemap_xform = tile_map_node->get_global_transform(); +void TileMapLayer::_navigation_notification(int p_what) { + const Ref<TileSet> &tile_set = _fetch_tileset(); + if (p_what == NOTIFICATION_TRANSFORM_CHANGED) { + if (tile_set.is_valid()) { + Transform2D tilemap_xform = get_global_transform(); for (KeyValue<Vector2i, CellData> &kv : tile_map) { const CellData &cell_data = kv.value; // Update navigation regions transform. @@ -1016,16 +1014,12 @@ void TileMapLayer::_navigation_update() { continue; } Transform2D tile_transform; - tile_transform.set_origin(tile_map_node->map_to_local(kv.key)); + tile_transform.set_origin(tile_set->map_to_local(kv.key)); NavigationServer2D::get_singleton()->region_set_transform(region, tilemap_xform * tile_transform); } } } } - - // ----------- - // Mark the navigation state as up to date. - _navigation_was_cleaned_up = forced_cleanup; } void TileMapLayer::_navigation_clear_cell(CellData &r_cell_data) { @@ -1042,9 +1036,10 @@ void TileMapLayer::_navigation_clear_cell(CellData &r_cell_data) { } void TileMapLayer::_navigation_update_cell(CellData &r_cell_data) { - const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); + const TileMap *tile_map_node = _fetch_tilemap(); + const Ref<TileSet> &tile_set = _fetch_tileset(); NavigationServer2D *ns = NavigationServer2D::get_singleton(); - Transform2D tilemap_xform = tile_map_node->get_global_transform(); + Transform2D gl_xform = get_global_transform(); // Get the navigation polygons and create regions. TileMapCell &c = r_cell_data.cell; @@ -1088,13 +1083,13 @@ void TileMapLayer::_navigation_update_cell(CellData &r_cell_data) { if (navigation_polygon.is_valid() && (navigation_polygon->get_polygon_count() > 0 || navigation_polygon->get_outline_count() > 0)) { // Create or update regions. Transform2D tile_transform; - tile_transform.set_origin(tile_map_node->map_to_local(r_cell_data.coords)); + tile_transform.set_origin(tile_set->map_to_local(r_cell_data.coords)); if (!region.is_valid()) { region = ns->region_create(); } ns->region_set_owner_id(region, tile_map_node->get_instance_id()); ns->region_set_map(region, navigation_map); - ns->region_set_transform(region, tilemap_xform * tile_transform); + ns->region_set_transform(region, gl_xform * tile_transform); ns->region_set_navigation_layers(region, tile_set->get_navigation_layer_layers(navigation_layer_index)); ns->region_set_navigation_polygon(region, navigation_polygon); } else { @@ -1119,10 +1114,11 @@ void TileMapLayer::_navigation_update_cell(CellData &r_cell_data) { #ifdef DEBUG_ENABLED void TileMapLayer::_navigation_draw_cell_debug(const RID &p_canvas_item, const Vector2 &p_quadrant_pos, const CellData &r_cell_data) { // Draw the debug collision shapes. + const TileMap *tile_map_node = _fetch_tilemap(); bool show_navigation = false; switch (tile_map_node->get_navigation_visibility_mode()) { case TileMap::VISIBILITY_MODE_DEFAULT: - show_navigation = !Engine::get_singleton()->is_editor_hint() && tile_map_node->get_tree()->is_debugging_navigation_hint(); + show_navigation = !Engine::get_singleton()->is_editor_hint() && get_tree()->is_debugging_navigation_hint(); break; case TileMap::VISIBILITY_MODE_FORCE_HIDE: show_navigation = false; @@ -1140,7 +1136,7 @@ void TileMapLayer::_navigation_draw_cell_debug(const RID &p_canvas_item, const V return; } - const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); + const Ref<TileSet> &tile_set = _fetch_tileset(); RenderingServer *rs = RenderingServer::get_singleton(); const NavigationServer2D *ns2d = NavigationServer2D::get_singleton(); @@ -1170,7 +1166,7 @@ void TileMapLayer::_navigation_draw_cell_debug(const RID &p_canvas_item, const V } Transform2D cell_to_quadrant; - cell_to_quadrant.set_origin(tile_map_node->map_to_local(r_cell_data.coords) - p_quadrant_pos); + cell_to_quadrant.set_origin(tile_set->map_to_local(r_cell_data.coords) - p_quadrant_pos); rs->canvas_item_add_set_transform(p_canvas_item, cell_to_quadrant); for (int layer_index = 0; layer_index < tile_set->get_navigation_layers_count(); layer_index++) { @@ -1226,10 +1222,10 @@ void TileMapLayer::_navigation_draw_cell_debug(const RID &p_canvas_item, const V /////////////////////////////// Scenes ////////////////////////////////////// void TileMapLayer::_scenes_update() { - const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); + const Ref<TileSet> &tile_set = _fetch_tileset(); // Check if we should cleanup everything. - bool forced_cleanup = in_destructor || !enabled || !tile_map_node->is_inside_tree() || !tile_set.is_valid(); + bool forced_cleanup = in_destructor || !enabled || !is_inside_tree() || !tile_set.is_valid(); if (forced_cleanup) { // Clean everything. @@ -1237,7 +1233,7 @@ void TileMapLayer::_scenes_update() { _scenes_clear_cell(kv.value); } } else { - if (_scenes_was_cleaned_up || dirty.flags[DIRTY_FLAGS_TILE_MAP_TILE_SET]) { + if (_scenes_was_cleaned_up || dirty.flags[DIRTY_FLAGS_TILE_MAP_TILE_SET] || dirty.flags[DIRTY_FLAGS_LAYER_IN_TREE]) { // Update all cells. for (KeyValue<Vector2i, CellData> &kv : tile_map) { _scenes_update_cell(kv.value); @@ -1257,6 +1253,11 @@ void TileMapLayer::_scenes_update() { } void TileMapLayer::_scenes_clear_cell(CellData &r_cell_data) { + const TileMap *tile_map_node = _fetch_tilemap(); + if (!tile_map_node) { + return; + } + // Cleanup existing scene. Node *node = tile_map_node->get_node_or_null(r_cell_data.scene); if (node) { @@ -1266,7 +1267,8 @@ void TileMapLayer::_scenes_clear_cell(CellData &r_cell_data) { } void TileMapLayer::_scenes_update_cell(CellData &r_cell_data) { - const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); + TileMap *tile_map_node = _fetch_tilemap(); + const Ref<TileSet> &tile_set = _fetch_tileset(); // Clear the scene in any case. _scenes_clear_cell(r_cell_data); @@ -1287,10 +1289,10 @@ void TileMapLayer::_scenes_update_cell(CellData &r_cell_data) { 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(tile_map_node->map_to_local(r_cell_data.coords) + scene_as_control->get_position()); + scene_as_control->set_position(tile_set->map_to_local(r_cell_data.coords) + scene_as_control->get_position()); } else if (scene_as_node2d) { Transform2D xform; - xform.set_origin(tile_map_node->map_to_local(r_cell_data.coords)); + xform.set_origin(tile_set->map_to_local(r_cell_data.coords)); scene_as_node2d->set_transform(xform * scene_as_node2d->get_transform()); } tile_map_node->add_child(scene); @@ -1303,7 +1305,7 @@ void TileMapLayer::_scenes_update_cell(CellData &r_cell_data) { #ifdef DEBUG_ENABLED void TileMapLayer::_scenes_draw_cell_debug(const RID &p_canvas_item, const Vector2 &p_quadrant_pos, const CellData &r_cell_data) { - const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); + const Ref<TileSet> &tile_set = _fetch_tileset(); ERR_FAIL_COND(!tile_set.is_valid()); if (!Engine::get_singleton()->is_editor_hint()) { @@ -1341,7 +1343,7 @@ void TileMapLayer::_scenes_draw_cell_debug(const RID &p_canvas_item, const Vecto // Draw a placeholder tile. Transform2D cell_to_quadrant; - cell_to_quadrant.set_origin(tile_map_node->map_to_local(r_cell_data.coords) - p_quadrant_pos); + cell_to_quadrant.set_origin(tile_set->map_to_local(r_cell_data.coords) - p_quadrant_pos); rs->canvas_item_add_set_transform(p_canvas_item, cell_to_quadrant); rs->canvas_item_add_circle(p_canvas_item, Vector2(), MIN(tile_set->get_tile_size().x, tile_set->get_tile_size().y) / 4.0, color); } @@ -1353,10 +1355,11 @@ void TileMapLayer::_scenes_draw_cell_debug(const RID &p_canvas_item, const Vecto ///////////////////////////////////////////////////////////////////// void TileMapLayer::_build_runtime_update_tile_data() { - const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); + const TileMap *tile_map_node = _fetch_tilemap(); + const Ref<TileSet> &tile_set = _fetch_tileset(); // Check if we should cleanup everything. - bool forced_cleanup = in_destructor || !enabled || !tile_map_node->is_inside_tree() || !tile_set.is_valid() || !tile_map_node->is_visible_in_tree(); + bool forced_cleanup = in_destructor || !enabled || !tile_set.is_valid() || !is_visible_in_tree(); if (!forced_cleanup) { if (tile_map_node->GDVIRTUAL_IS_OVERRIDDEN(_use_tile_data_runtime_update) && tile_map_node->GDVIRTUAL_IS_OVERRIDDEN(_tile_data_runtime_update)) { if (_runtime_update_tile_data_was_cleaned_up || dirty.flags[DIRTY_FLAGS_TILE_MAP_TILE_SET]) { @@ -1382,7 +1385,8 @@ void TileMapLayer::_build_runtime_update_tile_data() { } void TileMapLayer::_build_runtime_update_tile_data_for_cell(CellData &r_cell_data, bool p_auto_add_to_dirty_list) { - const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); + TileMap *tile_map_node = _fetch_tilemap(); + const Ref<TileSet> &tile_set = _fetch_tileset(); TileMapCell &c = r_cell_data.cell; TileSetSource *source; @@ -1425,7 +1429,7 @@ void TileMapLayer::_clear_runtime_update_tile_data() { } 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(); + const Ref<TileSet> &tile_set = _fetch_tileset(); if (!tile_set.is_valid()) { return TileSet::TerrainsPattern(); } @@ -1437,7 +1441,7 @@ TileSet::TerrainsPattern TileMapLayer::_get_best_terrain_pattern_for_constraints int score = 0; // Check the center bit constraint. - TerrainConstraint terrain_constraint = TerrainConstraint(tile_map_node, p_position, terrain_pattern.get_terrain()); + TerrainConstraint terrain_constraint = TerrainConstraint(tile_set, 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()) { @@ -1453,7 +1457,7 @@ TileSet::TerrainsPattern TileMapLayer::_get_best_terrain_pattern_for_constraints 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(tile_map_node, p_position, bit, terrain_pattern.get_terrain_peering_bit(bit)); + TerrainConstraint terrain_bit_constraint = TerrainConstraint(tile_set, 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()) { @@ -1486,19 +1490,19 @@ TileSet::TerrainsPattern TileMapLayer::_get_best_terrain_pattern_for_constraints } 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(); + const Ref<TileSet> &tile_set = _fetch_tileset(); if (!tile_set.is_valid()) { return RBSet<TerrainConstraint>(); } // Compute the constraints needed from the surrounding tiles. RBSet<TerrainConstraint> output; - output.insert(TerrainConstraint(tile_map_node, p_position, p_terrains_pattern.get_terrain())); + output.insert(TerrainConstraint(tile_set, 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(tile_map_node, p_position, side, p_terrains_pattern.get_terrain_peering_bit(side)); + TerrainConstraint c = TerrainConstraint(tile_set, p_position, side, p_terrains_pattern.get_terrain_peering_bit(side)); output.insert(c); } } @@ -1507,7 +1511,7 @@ RBSet<TerrainConstraint> TileMapLayer::_get_terrain_constraints_from_added_patte } 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(); + const Ref<TileSet> &tile_set = _fetch_tileset(); if (!tile_set.is_valid()) { return RBSet<TerrainConstraint>(); } @@ -1520,7 +1524,7 @@ RBSet<TerrainConstraint> TileMapLayer::_get_terrain_constraints_from_painted_cel 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(tile_map_node, E, bit, -1)); + dummy_constraints.insert(TerrainConstraint(tile_set, E, bit, -1)); } } } @@ -1587,27 +1591,89 @@ RBSet<TerrainConstraint> TileMapLayer::_get_terrain_constraints_from_painted_cel 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(tile_map_node, E_coords, terrain)); + constraints.insert(TerrainConstraint(tile_set, E_coords, terrain)); } } return constraints; } -void TileMapLayer::set_tile_map(TileMap *p_tile_map) { - tile_map_node = p_tile_map; +void TileMapLayer::_renamed() { + TileMap *tile_map_node = _fetch_tilemap(); + tile_map_node->emit_signal(CoreStringNames::get_singleton()->changed); +} + +void TileMapLayer::_update_notify_local_transform() { + TileMap *tile_map_node = _fetch_tilemap(); + bool notify = tile_map_node->is_collision_animatable() || is_y_sort_enabled(); + if (!notify) { + if (is_y_sort_enabled()) { + notify = true; + } + } + set_notify_local_transform(notify); +} + +void TileMapLayer::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_POSTINITIALIZE: { + connect(SNAME("renamed"), callable_mp(this, &TileMapLayer::_renamed)); + break; + } + case NOTIFICATION_ENTER_TREE: { + _update_notify_local_transform(); + dirty.flags[DIRTY_FLAGS_LAYER_IN_TREE] = true; + TileMap *tile_map_node = _fetch_tilemap(); + tile_map_node->queue_internal_update(); + } break; + + case NOTIFICATION_EXIT_TREE: { + dirty.flags[DIRTY_FLAGS_LAYER_IN_TREE] = true; + TileMap *tile_map_node = _fetch_tilemap(); + tile_map_node->queue_internal_update(); + } break; + + case TileMap::NOTIFICATION_ENTER_CANVAS: { + dirty.flags[DIRTY_FLAGS_LAYER_IN_CANVAS] = true; + TileMap *tile_map_node = _fetch_tilemap(); + tile_map_node->queue_internal_update(); + } break; + + case TileMap::NOTIFICATION_EXIT_CANVAS: { + dirty.flags[DIRTY_FLAGS_LAYER_IN_CANVAS] = true; + TileMap *tile_map_node = _fetch_tilemap(); + tile_map_node->queue_internal_update(); + } break; + + case TileMap::NOTIFICATION_VISIBILITY_CHANGED: { + dirty.flags[DIRTY_FLAGS_LAYER_VISIBILITY] = true; + TileMap *tile_map_node = _fetch_tilemap(); + tile_map_node->queue_internal_update(); + } break; + } + + _rendering_notification(p_what); + _physics_notification(p_what); + _navigation_notification(p_what); } void TileMapLayer::set_layer_index_in_tile_map_node(int p_index) { if (p_index == layer_index_in_tile_map_node) { return; } + TileMap *tile_map_node = _fetch_tilemap(); layer_index_in_tile_map_node = p_index; dirty.flags[DIRTY_FLAGS_LAYER_INDEX_IN_TILE_MAP_NODE] = true; tile_map_node->queue_internal_update(); } Rect2 TileMapLayer::get_rect(bool &r_changed) const { + const Ref<TileSet> &tile_set = _fetch_tileset(); + if (tile_set.is_null()) { + r_changed = rect_cache != Rect2(); + return Rect2(); + } + // Compute the displayed area of the tilemap. r_changed = false; #ifdef DEBUG_ENABLED @@ -1617,7 +1683,7 @@ Rect2 TileMapLayer::get_rect(bool &r_changed) const { bool first = true; for (const KeyValue<Vector2i, CellData> &E : tile_map) { Rect2 r; - r.position = tile_map_node->map_to_local(E.key); + r.position = tile_set->map_to_local(E.key); r.size = Size2(); if (first) { r_total = r; @@ -1637,7 +1703,7 @@ Rect2 TileMapLayer::get_rect(bool &r_changed) const { } 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(); + const Ref<TileSet> &tile_set = _fetch_tileset(); if (!tile_set.is_valid()) { return HashMap<Vector2i, TileSet::TerrainsPattern>(); } @@ -1686,7 +1752,7 @@ HashMap<Vector2i, TileSet::TerrainsPattern> TileMapLayer::terrain_fill_constrain 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(); + const Ref<TileSet> &tile_set = _fetch_tileset(); ERR_FAIL_COND_V(!tile_set.is_valid(), output); ERR_FAIL_INDEX_V(p_terrain_set, tile_set->get_terrain_sets_count(), output); @@ -1704,8 +1770,8 @@ HashMap<Vector2i, TileSet::TerrainsPattern> TileMapLayer::terrain_fill_connect(c // Find the adequate neighbor. for (int j = 0; j < TileSet::CELL_NEIGHBOR_MAX; j++) { TileSet::CellNeighbor bit = TileSet::CellNeighbor(j); - if (tile_map_node->is_existing_neighbor(bit)) { - Vector2i neighbor = tile_map_node->get_neighbor_cell(coords, bit); + if (tile_set->is_existing_neighbor(bit)) { + Vector2i neighbor = tile_set->get_neighbor_cell(coords, bit); if (!can_modify_set.has(neighbor)) { can_modify_list.push_back(neighbor); can_modify_set.insert(neighbor); @@ -1746,7 +1812,7 @@ HashMap<Vector2i, TileSet::TerrainsPattern> TileMapLayer::terrain_fill_connect(c // Add new constraints from the path drawn. for (Vector2i coords : p_coords_array) { // Constraints on the center bit. - TerrainConstraint c = TerrainConstraint(tile_map_node, coords, p_terrain); + TerrainConstraint c = TerrainConstraint(tile_set, coords, p_terrain); c.set_priority(10); constraints.insert(c); @@ -1754,11 +1820,11 @@ HashMap<Vector2i, TileSet::TerrainsPattern> TileMapLayer::terrain_fill_connect(c 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(tile_map_node, coords, bit, p_terrain); + c = TerrainConstraint(tile_set, 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 = tile_map_node->get_neighbor_cell(coords, bit); + Vector2i neighbor = tile_set->get_neighbor_cell(coords, bit); if (cells_with_terrain_center_bit.has(neighbor)) { constraints.insert(c); } @@ -1792,7 +1858,7 @@ HashMap<Vector2i, TileSet::TerrainsPattern> TileMapLayer::terrain_fill_connect(c 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(); + const Ref<TileSet> &tile_set = _fetch_tileset(); ERR_FAIL_COND_V(!tile_set.is_valid(), output); ERR_FAIL_INDEX_V(p_terrain_set, tile_set->get_terrain_sets_count(), output); @@ -1803,8 +1869,8 @@ HashMap<Vector2i, TileSet::TerrainsPattern> TileMapLayer::terrain_fill_path(cons 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 (tile_map_node->is_existing_neighbor(bit)) { - if (tile_map_node->get_neighbor_cell(p_coords_array[i], bit) == p_coords_array[i + 1]) { + if (tile_set->is_existing_neighbor(bit)) { + if (tile_set->get_neighbor_cell(p_coords_array[i], bit) == p_coords_array[i + 1]) { found_bit = bit; break; } @@ -1829,7 +1895,7 @@ HashMap<Vector2i, TileSet::TerrainsPattern> TileMapLayer::terrain_fill_path(cons 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 = tile_map_node->get_neighbor_cell(coords, bit); + Vector2i neighbor = tile_set->get_neighbor_cell(coords, bit); if (!can_modify_set.has(neighbor)) { can_modify_list.push_back(neighbor); can_modify_set.insert(neighbor); @@ -1843,13 +1909,13 @@ HashMap<Vector2i, TileSet::TerrainsPattern> TileMapLayer::terrain_fill_path(cons // Add new constraints from the path drawn. for (Vector2i coords : p_coords_array) { // Constraints on the center bit. - TerrainConstraint c = TerrainConstraint(tile_map_node, coords, p_terrain); + TerrainConstraint c = TerrainConstraint(tile_set, coords, p_terrain); c.set_priority(10); constraints.insert(c); } for (int i = 0; i < p_coords_array.size() - 1; i++) { // Constraints on the peering bits. - TerrainConstraint c = TerrainConstraint(tile_map_node, p_coords_array[i], neighbor_list[i], p_terrain); + TerrainConstraint c = TerrainConstraint(tile_set, p_coords_array[i], neighbor_list[i], p_terrain); c.set_priority(10); constraints.insert(c); } @@ -1866,7 +1932,7 @@ HashMap<Vector2i, TileSet::TerrainsPattern> TileMapLayer::terrain_fill_path(cons 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(); + const Ref<TileSet> &tile_set = _fetch_tileset(); ERR_FAIL_COND_V(!tile_set.is_valid(), output); ERR_FAIL_INDEX_V(p_terrain_set, tile_set->get_terrain_sets_count(), output); @@ -1885,7 +1951,7 @@ HashMap<Vector2i, TileSet::TerrainsPattern> TileMapLayer::terrain_fill_pattern(c 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 = tile_map_node->get_neighbor_cell(coords, bit); + Vector2i neighbor = tile_set->get_neighbor_cell(coords, bit); if (!can_modify_set.has(neighbor)) { can_modify_list.push_back(neighbor); can_modify_set.insert(neighbor); @@ -1922,7 +1988,7 @@ TileMapCell TileMapLayer::get_cell(const Vector2i &p_coords, bool p_use_proxies) return TileMapCell(); } else { TileMapCell c = tile_map.find(p_coords)->value.cell; - const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); + const Ref<TileSet> &tile_set = _fetch_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]; @@ -1998,7 +2064,7 @@ void TileMapLayer::set_tile_data(TileMapDataFormat p_format, const Vector<int> & coord_y = decode_uint16(&local[10]); } - const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); + const Ref<TileSet> &tile_set = _fetch_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) { @@ -2039,9 +2105,9 @@ Vector<int> TileMapLayer::get_tile_data() const { } void TileMapLayer::notify_tile_map_change(DirtyFlags p_what) { + TileMap *tile_map_node = _fetch_tilemap(); dirty.flags[p_what] = true; tile_map_node->queue_internal_update(); - _physics_notify_tilemap_change(p_what); } void TileMapLayer::internal_update() { @@ -2124,7 +2190,10 @@ void TileMapLayer::set_cell(const Vector2i &p_coords, int p_source_id, const Vec if (!E->value.dirty_list_element.in_list()) { dirty.cell_list.add(&(E->value.dirty_list_element)); } - tile_map_node->queue_internal_update(); + TileMap *tile_map_node = _fetch_tilemap(); + if (tile_map_node) { // Needed to avoid crashes in destructor. + tile_map_node->queue_internal_update(); + } used_rect_cache_dirty = true; } @@ -2141,7 +2210,7 @@ int TileMapLayer::get_cell_source_id(const Vector2i &p_coords, bool p_use_proxie return TileSet::INVALID_SOURCE; } - const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); + const Ref<TileSet> &tile_set = _fetch_tileset(); if (p_use_proxies && tile_set.is_valid()) { Array proxyed = tile_set->map_tile_proxy(E->value.cell.source_id, E->value.cell.get_atlas_coords(), E->value.cell.alternative_tile); return proxyed[0]; @@ -2158,7 +2227,7 @@ Vector2i TileMapLayer::get_cell_atlas_coords(const Vector2i &p_coords, bool p_us return TileSetSource::INVALID_ATLAS_COORDS; } - const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); + const Ref<TileSet> &tile_set = _fetch_tileset(); if (p_use_proxies && tile_set.is_valid()) { Array proxyed = tile_set->map_tile_proxy(E->value.cell.source_id, E->value.cell.get_atlas_coords(), E->value.cell.alternative_tile); return proxyed[1]; @@ -2175,7 +2244,7 @@ int TileMapLayer::get_cell_alternative_tile(const Vector2i &p_coords, bool p_use return TileSetSource::INVALID_TILE_ALTERNATIVE; } - const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); + const Ref<TileSet> &tile_set = _fetch_tileset(); if (p_use_proxies && tile_set.is_valid()) { Array proxyed = tile_set->map_tile_proxy(E->value.cell.source_id, E->value.cell.get_atlas_coords(), E->value.cell.alternative_tile); return proxyed[2]; @@ -2190,7 +2259,7 @@ TileData *TileMapLayer::get_cell_tile_data(const Vector2i &p_coords, bool p_use_ return nullptr; } - const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); + const Ref<TileSet> &tile_set = _fetch_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)); @@ -2208,7 +2277,7 @@ void TileMapLayer::clear() { } Ref<TileMapPattern> TileMapLayer::get_pattern(TypedArray<Vector2i> p_coords_array) { - const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); + const Ref<TileSet> &tile_set = _fetch_tileset(); ERR_FAIL_COND_V(!tile_set.is_valid(), nullptr); Ref<TileMapPattern> output; @@ -2262,19 +2331,19 @@ Ref<TileMapPattern> TileMapLayer::get_pattern(TypedArray<Vector2i> p_coords_arra } void TileMapLayer::set_pattern(const Vector2i &p_position, const Ref<TileMapPattern> p_pattern) { - const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); + const Ref<TileSet> &tile_set = _fetch_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); + Vector2i coords = tile_set->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(); + const Ref<TileSet> &tile_set = _fetch_tileset(); ERR_FAIL_COND(!tile_set.is_valid()); ERR_FAIL_INDEX(p_terrain_set, tile_set->get_terrain_sets_count()); @@ -2314,7 +2383,7 @@ void TileMapLayer::set_cells_terrain_connect(TypedArray<Vector2i> p_cells, int p } 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(); + const Ref<TileSet> &tile_set = _fetch_tileset(); ERR_FAIL_COND(!tile_set.is_valid()); ERR_FAIL_INDEX(p_terrain_set, tile_set->get_terrain_sets_count()); @@ -2415,24 +2484,13 @@ Rect2i TileMapLayer::get_used_rect() const { return used_rect_cache; } -void TileMapLayer::set_name(String p_name) { - if (name == p_name) { - return; - } - name = p_name; - tile_map_node->emit_signal(CoreStringNames::get_singleton()->changed); -} - -String TileMapLayer::get_name() const { - return name; -} - void TileMapLayer::set_enabled(bool p_enabled) { if (enabled == p_enabled) { return; } enabled = p_enabled; dirty.flags[DIRTY_FLAGS_LAYER_ENABLED] = true; + TileMap *tile_map_node = _fetch_tilemap(); tile_map_node->queue_internal_update(); tile_map_node->emit_signal(CoreStringNames::get_singleton()->changed); @@ -2443,34 +2501,29 @@ bool TileMapLayer::is_enabled() const { return enabled; } -void TileMapLayer::set_modulate(Color p_modulate) { - if (modulate == p_modulate) { +void TileMapLayer::set_self_modulate(const Color &p_self_modulate) { + if (get_self_modulate() == p_self_modulate) { return; } - modulate = p_modulate; - dirty.flags[DIRTY_FLAGS_LAYER_MODULATE] = true; + CanvasItem::set_self_modulate(p_self_modulate); + dirty.flags[DIRTY_FLAGS_LAYER_SELF_MODULATE] = true; + TileMap *tile_map_node = _fetch_tilemap(); tile_map_node->queue_internal_update(); tile_map_node->emit_signal(CoreStringNames::get_singleton()->changed); } -Color TileMapLayer::get_modulate() const { - return modulate; -} - void TileMapLayer::set_y_sort_enabled(bool p_y_sort_enabled) { - if (y_sort_enabled == p_y_sort_enabled) { + if (is_y_sort_enabled() == p_y_sort_enabled) { return; } - y_sort_enabled = p_y_sort_enabled; + CanvasItem::set_y_sort_enabled(p_y_sort_enabled); dirty.flags[DIRTY_FLAGS_LAYER_Y_SORT_ENABLED] = true; + TileMap *tile_map_node = _fetch_tilemap(); tile_map_node->queue_internal_update(); tile_map_node->emit_signal(CoreStringNames::get_singleton()->changed); tile_map_node->update_configuration_warnings(); -} - -bool TileMapLayer::is_y_sort_enabled() const { - return y_sort_enabled; + _update_notify_local_transform(); } void TileMapLayer::set_y_sort_origin(int p_y_sort_origin) { @@ -2479,6 +2532,7 @@ void TileMapLayer::set_y_sort_origin(int p_y_sort_origin) { } y_sort_origin = p_y_sort_origin; dirty.flags[DIRTY_FLAGS_LAYER_Y_SORT_ORIGIN] = true; + TileMap *tile_map_node = _fetch_tilemap(); tile_map_node->queue_internal_update(); tile_map_node->emit_signal(CoreStringNames::get_singleton()->changed); } @@ -2488,19 +2542,28 @@ int TileMapLayer::get_y_sort_origin() const { } void TileMapLayer::set_z_index(int p_z_index) { - if (z_index == p_z_index) { + if (get_z_index() == p_z_index) { return; } - z_index = p_z_index; + CanvasItem::set_z_index(p_z_index); dirty.flags[DIRTY_FLAGS_LAYER_Z_INDEX] = true; + TileMap *tile_map_node = _fetch_tilemap(); tile_map_node->queue_internal_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_use_kinematic_bodies(bool p_use_kinematic_bodies) { + use_kinematic_bodies = p_use_kinematic_bodies; + dirty.flags[DIRTY_FLAGS_LAYER_USE_KINEMATIC_BODIES] = p_use_kinematic_bodies; + TileMap *tile_map_node = _fetch_tilemap(); + tile_map_node->queue_internal_update(); + tile_map_node->emit_signal(CoreStringNames::get_singleton()->changed); +} + +bool TileMapLayer::is_using_kinematic_bodies() const { + return use_kinematic_bodies; } void TileMapLayer::set_navigation_enabled(bool p_enabled) { @@ -2509,6 +2572,7 @@ void TileMapLayer::set_navigation_enabled(bool p_enabled) { } navigation_enabled = p_enabled; dirty.flags[DIRTY_FLAGS_LAYER_NAVIGATION_ENABLED] = true; + TileMap *tile_map_node = _fetch_tilemap(); tile_map_node->queue_internal_update(); tile_map_node->emit_signal(CoreStringNames::get_singleton()->changed); } @@ -2518,9 +2582,9 @@ bool TileMapLayer::is_navigation_enabled() const { } 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."); + ERR_FAIL_COND_MSG(!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(); + uses_world_navigation_map = p_map == get_world_2d()->get_navigation_map(); } RID TileMapLayer::get_navigation_map() const { @@ -2531,7 +2595,7 @@ RID TileMapLayer::get_navigation_map() const { } void TileMapLayer::fix_invalid_tiles() { - Ref<TileSet> tileset = tile_map_node->get_tileset(); + Ref<TileSet> tileset = _fetch_tileset(); ERR_FAIL_COND_MSG(tileset.is_null(), "Cannot call fix_invalid_tiles() on a TileMap without a valid TileSet."); RBSet<Vector2i> coords; @@ -2554,12 +2618,11 @@ Vector2i TileMapLayer::get_coords_for_body_rid(RID p_physics_body) const { return bodies_coords[p_physics_body]; } -TileMapLayer::~TileMapLayer() { - if (!tile_map_node) { - // Temporary layer. - return; - } +TileMapLayer::TileMapLayer() { + set_notify_transform(true); +} +TileMapLayer::~TileMapLayer() { in_destructor = true; clear(); internal_update(); @@ -2569,26 +2632,24 @@ HashMap<Vector2i, TileSet::CellNeighbor> TerrainConstraint::get_overlapping_coor HashMap<Vector2i, TileSet::CellNeighbor> output; ERR_FAIL_COND_V(is_center_bit(), output); + ERR_FAIL_COND_V(!tile_set.is_valid(), output); - Ref<TileSet> ts = tile_map->get_tileset(); - ERR_FAIL_COND_V(!ts.is_valid(), output); - - TileSet::TileShape shape = ts->get_tile_shape(); + TileSet::TileShape shape = tile_set->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; + output[tile_set->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; + output[tile_set->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER; + output[tile_set->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER; + output[tile_set->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; + output[tile_set->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_SIDE; break; default: ERR_FAIL_V(output); @@ -2597,47 +2658,47 @@ HashMap<Vector2i, TileSet::CellNeighbor> TerrainConstraint::get_overlapping_coor 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; + output[tile_set->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; + output[tile_set->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_LEFT_CORNER; + output[tile_set->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_CORNER)] = TileSet::CELL_NEIGHBOR_TOP_CORNER; + output[tile_set->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; + output[tile_set->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(); + TileSet::TileOffsetAxis offset_axis = tile_set->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; + output[tile_set->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; + output[tile_set->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER; + output[tile_set->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; + output[tile_set->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; + output[tile_set->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER; + output[tile_set->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; + output[tile_set->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE; break; default: ERR_FAIL_V(output); @@ -2646,25 +2707,25 @@ HashMap<Vector2i, TileSet::CellNeighbor> TerrainConstraint::get_overlapping_coor 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; + output[tile_set->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER; + output[tile_set->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; + output[tile_set->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; + output[tile_set->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_LEFT_CORNER; + output[tile_set->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; + output[tile_set->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; + output[tile_set->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE; break; default: ERR_FAIL_V(output); @@ -2674,25 +2735,20 @@ HashMap<Vector2i, TileSet::CellNeighbor> TerrainConstraint::get_overlapping_coor return output; } -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()); - +TerrainConstraint::TerrainConstraint(Ref<TileSet> p_tile_set, const Vector2i &p_position, int p_terrain) { + ERR_FAIL_COND(!p_tile_set.is_valid()); + tile_set = p_tile_set; 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) { +TerrainConstraint::TerrainConstraint(Ref<TileSet> p_tile_set, 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()); + ERR_FAIL_COND(!p_tile_set.is_valid()); + tile_set = p_tile_set; - TileSet::TileShape shape = ts->get_tile_shape(); + TileSet::TileShape shape = tile_set->get_tile_shape(); if (shape == TileSet::TILE_SHAPE_SQUARE) { switch (p_bit) { case TileSet::CELL_NEIGHBOR_RIGHT_SIDE: @@ -2709,23 +2765,23 @@ TerrainConstraint::TerrainConstraint(const TileMap *p_tile_map, const Vector2i & 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); + base_cell_coords = tile_set->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); + base_cell_coords = tile_set->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); + base_cell_coords = tile_set->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); + base_cell_coords = tile_set->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); + base_cell_coords = tile_set->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_SIDE); break; default: ERR_FAIL(); @@ -2735,7 +2791,7 @@ TerrainConstraint::TerrainConstraint(const TileMap *p_tile_map, const Vector2i & 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); + base_cell_coords = tile_set->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE); break; case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE: bit = 1; @@ -2751,19 +2807,19 @@ TerrainConstraint::TerrainConstraint(const TileMap *p_tile_map, const Vector2i & 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); + base_cell_coords = tile_set->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); + base_cell_coords = tile_set->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); + base_cell_coords = tile_set->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); + base_cell_coords = tile_set->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE); break; default: ERR_FAIL(); @@ -2771,7 +2827,7 @@ TerrainConstraint::TerrainConstraint(const TileMap *p_tile_map, const Vector2i & } } else { // Half-offset shapes. - TileSet::TileOffsetAxis offset_axis = ts->get_tile_offset_axis(); + TileSet::TileOffsetAxis offset_axis = tile_set->get_tile_offset_axis(); if (offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { switch (p_bit) { case TileSet::CELL_NEIGHBOR_RIGHT_SIDE: @@ -2796,31 +2852,31 @@ TerrainConstraint::TerrainConstraint(const TileMap *p_tile_map, const Vector2i & 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); + base_cell_coords = tile_set->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); + base_cell_coords = tile_set->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); + base_cell_coords = tile_set->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); + base_cell_coords = tile_set->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); + base_cell_coords = tile_set->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); + base_cell_coords = tile_set->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); + base_cell_coords = tile_set->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE); break; default: ERR_FAIL(); @@ -2846,7 +2902,7 @@ TerrainConstraint::TerrainConstraint(const TileMap *p_tile_map, const Vector2i & 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); + base_cell_coords = tile_set->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE); break; case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE: bit = 5; @@ -2854,27 +2910,27 @@ TerrainConstraint::TerrainConstraint(const TileMap *p_tile_map, const Vector2i & 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); + base_cell_coords = tile_set->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); + base_cell_coords = tile_set->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); + base_cell_coords = tile_set->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); + base_cell_coords = tile_set->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); + base_cell_coords = tile_set->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); + base_cell_coords = tile_set->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE); break; default: ERR_FAIL(); diff --git a/scene/2d/tile_map_layer.h b/scene/2d/tile_map_layer.h index f31c5406a4..2cc57f50d1 100644 --- a/scene/2d/tile_map_layer.h +++ b/scene/2d/tile_map_layer.h @@ -38,7 +38,7 @@ class TileSetAtlasSource; class TerrainConstraint { private: - const TileMap *tile_map = nullptr; + Ref<TileSet> tile_set; Vector2i base_cell_coords; int bit = -1; int terrain = -1; @@ -83,8 +83,8 @@ public: 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(Ref<TileSet> p_tile_set, const Vector2i &p_position, int p_terrain); // For the center terrain bit + TerrainConstraint(Ref<TileSet> p_tile_set, const Vector2i &p_position, const TileSet::CellNeighbor &p_bit, int p_terrain); // For peering bits TerrainConstraint(){}; }; @@ -214,23 +214,24 @@ public: } }; -class TileMapLayer : public RefCounted { - GDCLASS(TileMapLayer, RefCounted); +class TileMapLayer : public Node2D { + GDCLASS(TileMapLayer, Node2D); public: enum DirtyFlags { DIRTY_FLAGS_LAYER_ENABLED = 0, - DIRTY_FLAGS_LAYER_MODULATE, + DIRTY_FLAGS_LAYER_IN_TREE, + DIRTY_FLAGS_LAYER_IN_CANVAS, + DIRTY_FLAGS_LAYER_LOCAL_TRANSFORM, + DIRTY_FLAGS_LAYER_VISIBILITY, + DIRTY_FLAGS_LAYER_SELF_MODULATE, DIRTY_FLAGS_LAYER_Y_SORT_ENABLED, DIRTY_FLAGS_LAYER_Y_SORT_ORIGIN, DIRTY_FLAGS_LAYER_Z_INDEX, + DIRTY_FLAGS_LAYER_USE_KINEMATIC_BODIES, DIRTY_FLAGS_LAYER_NAVIGATION_ENABLED, DIRTY_FLAGS_LAYER_INDEX_IN_TILE_MAP_NODE, - DIRTY_FLAGS_TILE_MAP_IN_TREE, - DIRTY_FLAGS_TILE_MAP_IN_CANVAS, - DIRTY_FLAGS_TILE_MAP_VISIBILITY, - DIRTY_FLAGS_TILE_MAP_XFORM, - DIRTY_FLAGS_TILE_MAP_LOCAL_XFORM, + DIRTY_FLAGS_TILE_MAP_SELECTED_LAYER, DIRTY_FLAGS_TILE_MAP_LIGHT_MASK, DIRTY_FLAGS_TILE_MAP_MATERIAL, @@ -239,7 +240,6 @@ public: DIRTY_FLAGS_TILE_MAP_TEXTURE_REPEAT, DIRTY_FLAGS_TILE_MAP_TILE_SET, DIRTY_FLAGS_TILE_MAP_QUADRANT_SIZE, - DIRTY_FLAGS_TILE_MAP_COLLISION_ANIMATABLE, DIRTY_FLAGS_TILE_MAP_COLLISION_VISIBILITY_MODE, DIRTY_FLAGS_TILE_MAP_NAVIGATION_VISIBILITY_MODE, DIRTY_FLAGS_TILE_MAP_Y_SORT_ENABLED, @@ -249,20 +249,15 @@ public: 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; + bool use_kinematic_bodies = false; bool navigation_enabled = true; 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; HashMap<Vector2i, CellData> tile_map; // Dirty flag. Allows knowing what was modified since the last update. @@ -278,6 +273,10 @@ private: mutable Rect2i used_rect_cache; mutable bool used_rect_cache_dirty = true; + // Method to fetch the TileSet to use + TileMap *_fetch_tilemap() const; + Ref<TileSet> _fetch_tileset() const; + // Runtime tile data. bool _runtime_update_tile_data_was_cleaned_up = false; void _build_runtime_update_tile_data(); @@ -296,6 +295,7 @@ private: HashMap<Vector2i, Ref<RenderingQuadrant>> rendering_quadrant_map; bool _rendering_was_cleaned_up = false; void _rendering_update(); + void _rendering_notification(int p_what); void _rendering_quadrants_update_cell(CellData &r_cell_data, SelfList<RenderingQuadrant>::List &r_dirty_rendering_quadrant_list); void _rendering_occluders_clear_cell(CellData &r_cell_data); void _rendering_occluders_update_cell(CellData &r_cell_data); @@ -306,7 +306,7 @@ private: HashMap<RID, Vector2i> bodies_coords; // Mapping for RID to coords. bool _physics_was_cleaned_up = false; void _physics_update(); - void _physics_notify_tilemap_change(DirtyFlags p_what); + void _physics_notification(int p_what); void _physics_clear_cell(CellData &r_cell_data); void _physics_update_cell(CellData &r_cell_data); #ifdef DEBUG_ENABLED @@ -315,6 +315,7 @@ private: bool _navigation_was_cleaned_up = false; void _navigation_update(); + void _navigation_notification(int p_what); void _navigation_clear_cell(CellData &r_cell_data); void _navigation_update_cell(CellData &r_cell_data); #ifdef DEBUG_ENABLED @@ -334,9 +335,14 @@ private: 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; + void _renamed(); + void _update_notify_local_transform(); + +protected: + void _notification(int p_what); + public: // TileMap node. - void set_tile_map(TileMap *p_tile_map); void set_layer_index_in_tile_map_node(int p_index); // Rect caching. @@ -383,18 +389,15 @@ public: 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; + virtual void set_self_modulate(const Color &p_self_modulate) override; + virtual void set_y_sort_enabled(bool p_y_sort_enabled) override; 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; + virtual void set_z_index(int p_z_index) override; + void set_use_kinematic_bodies(bool p_use_kinematic_bodies); + bool is_using_kinematic_bodies() const; void set_navigation_enabled(bool p_enabled); bool is_navigation_enabled() const; void set_navigation_map(RID p_map); @@ -407,6 +410,7 @@ public: 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. + TileMapLayer(); ~TileMapLayer(); }; diff --git a/scene/3d/audio_stream_player_3d.cpp b/scene/3d/audio_stream_player_3d.cpp index bfdbd14cc9..b01be4dffb 100644 --- a/scene/3d/audio_stream_player_3d.cpp +++ b/scene/3d/audio_stream_player_3d.cpp @@ -34,10 +34,10 @@ #include "scene/3d/area_3d.h" #include "scene/3d/audio_listener_3d.h" #include "scene/3d/camera_3d.h" +#include "scene/3d/velocity_tracker_3d.h" +#include "scene/audio/audio_stream_player_internal.h" #include "scene/main/viewport.h" -#include "scene/scene_string_names.h" - -#define PARAM_PREFIX "parameters/" +#include "servers/audio/audio_stream.h" // Based on "A Novel Multichannel Panning Method for Standard and Arbitrary Loudspeaker Configurations" by Ramy Sadek and Chris Kyriakakis (2004) // Speaker-Placement Correction Amplitude Panning (SPCAP) @@ -231,7 +231,7 @@ float AudioStreamPlayer3D::_get_attenuation_db(float p_distance) const { } } - att += volume_db; + att += internal->volume_db; if (att > max_db) { att = max_db; } @@ -244,32 +244,12 @@ void AudioStreamPlayer3D::_notification(int p_what) { case NOTIFICATION_ENTER_TREE: { velocity_tracker->reset(get_global_transform().origin); AudioServer::get_singleton()->add_listener_changed_callback(_listener_changed_cb, this); - if (autoplay && !Engine::get_singleton()->is_editor_hint()) { - play(); - } - set_stream_paused(!can_process()); } break; case NOTIFICATION_EXIT_TREE: { - set_stream_paused(true); AudioServer::get_singleton()->remove_listener_changed_callback(_listener_changed_cb, this); } break; - case NOTIFICATION_PREDELETE: { - stop(); - } break; - - case NOTIFICATION_PAUSED: { - if (!can_process()) { - // Node can't process so we start fading out to silence. - set_stream_paused(true); - } - } break; - - case NOTIFICATION_UNPAUSED: { - set_stream_paused(false); - } break; - case NOTIFICATION_TRANSFORM_CHANGED: { if (doppler_tracking != DOPPLER_TRACKING_DISABLED) { velocity_tracker->update_position(get_global_transform().origin); @@ -279,13 +259,13 @@ void AudioStreamPlayer3D::_notification(int p_what) { case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { // Update anything related to position first, if possible of course. Vector<AudioFrame> volume_vector; - if (setplay.get() > 0 || (active.is_set() && last_mix_count != AudioServer::get_singleton()->get_mix_count()) || force_update_panning) { + if (setplay.get() > 0 || (internal->active.is_set() && last_mix_count != AudioServer::get_singleton()->get_mix_count()) || force_update_panning) { force_update_panning = false; volume_vector = _update_panning(); } if (setplayback.is_valid() && setplay.get() >= 0) { - active.set(); + internal->active.set(); HashMap<StringName, Vector<AudioFrame>> bus_map; bus_map[_get_actual_bus()] = volume_vector; AudioServer::get_singleton()->start_playback_stream(setplayback, bus_map, setplay.get(), actual_pitch_scale, linear_attenuation, attenuation_filter_cutoff_hz); @@ -293,32 +273,10 @@ void AudioStreamPlayer3D::_notification(int p_what) { setplay.set(-1); } - if (!stream_playbacks.is_empty() && active.is_set()) { - // Stop playing if no longer active. - Vector<Ref<AudioStreamPlayback>> playbacks_to_remove; - for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { - if (playback.is_valid() && !AudioServer::get_singleton()->is_playback_active(playback) && !AudioServer::get_singleton()->is_playback_paused(playback)) { - playbacks_to_remove.push_back(playback); - } - } - // Now go through and remove playbacks that have finished. Removing elements from a Vector in a range based for is asking for trouble. - for (Ref<AudioStreamPlayback> &playback : playbacks_to_remove) { - stream_playbacks.erase(playback); - } - if (!playbacks_to_remove.is_empty() && stream_playbacks.is_empty()) { - // This node is no longer actively playing audio. - active.clear(); - set_physics_process_internal(false); - } - if (!playbacks_to_remove.is_empty()) { - emit_signal(SNAME("finished")); - } - } - - while (stream_playbacks.size() > max_polyphony) { - AudioServer::get_singleton()->stop_playback_stream(stream_playbacks[0]); - stream_playbacks.remove_at(0); + if (!internal->stream_playbacks.is_empty() && internal->active.is_set()) { + internal->process(); } + internal->ensure_playback_limit(); } break; } } @@ -362,16 +320,16 @@ Area3D *AudioStreamPlayer3D::_get_overriding_area() { return nullptr; } -// Interacts with PhysicsServer3D, so can only be called during _physics_process +// Interacts with PhysicsServer3D, so can only be called during _physics_process. StringName AudioStreamPlayer3D::_get_actual_bus() { Area3D *overriding_area = _get_overriding_area(); if (overriding_area && overriding_area->is_overriding_audio_bus() && !overriding_area->is_using_reverb_bus()) { return overriding_area->get_audio_bus_name(); } - return bus; + return internal->bus; } -// Interacts with PhysicsServer3D, so can only be called during _physics_process +// Interacts with PhysicsServer3D, so can only be called during _physics_process. Vector<AudioFrame> AudioStreamPlayer3D::_update_panning() { Vector<AudioFrame> output_volume_vector; output_volume_vector.resize(4); @@ -379,7 +337,7 @@ Vector<AudioFrame> AudioStreamPlayer3D::_update_panning() { frame = AudioFrame(0, 0); } - if (!active.is_set() || stream.is_null()) { + if (!internal->active.is_set() || internal->stream.is_null()) { return output_volume_vector; } @@ -463,7 +421,7 @@ Vector<AudioFrame> AudioStreamPlayer3D::_update_panning() { } linear_attenuation = Math::db_to_linear(db_att); - for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { + for (Ref<AudioStreamPlayback> &playback : internal->stream_playbacks) { AudioServer::get_singleton()->set_playback_highshelf_params(playback, linear_attenuation, attenuation_filter_cutoff_hz); } // Bake in a constant factor here to allow the project setting defaults for 2d and 3d to be normalized to 1.0. @@ -489,10 +447,10 @@ Vector<AudioFrame> AudioStreamPlayer3D::_update_panning() { bus_volumes[reverb_bus_name] = reverb_vol; } } else { - bus_volumes[bus] = output_volume_vector; + bus_volumes[internal->bus] = output_volume_vector; } - for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { + for (Ref<AudioStreamPlayback> &playback : internal->stream_playbacks) { AudioServer::get_singleton()->set_playback_bus_volumes_linear(playback, bus_volumes); } @@ -510,64 +468,37 @@ Vector<AudioFrame> AudioStreamPlayer3D::_update_panning() { float velocity = local_velocity.length(); float speed_of_sound = 343.0; - float doppler_pitch_scale = pitch_scale * speed_of_sound / (speed_of_sound + velocity * approaching); + float doppler_pitch_scale = internal->pitch_scale * speed_of_sound / (speed_of_sound + velocity * approaching); doppler_pitch_scale = CLAMP(doppler_pitch_scale, (1 / 8.0), 8.0); //avoid crazy stuff actual_pitch_scale = doppler_pitch_scale; } else { - actual_pitch_scale = pitch_scale; + actual_pitch_scale = internal->pitch_scale; } } else { - actual_pitch_scale = pitch_scale; + actual_pitch_scale = internal->pitch_scale; } - for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { + for (Ref<AudioStreamPlayback> &playback : internal->stream_playbacks) { AudioServer::get_singleton()->set_playback_pitch_scale(playback, actual_pitch_scale); } } return output_volume_vector; } -void AudioStreamPlayer3D::_update_stream_parameters() { - if (stream.is_null()) { - return; - } - List<AudioStream::Parameter> parameters; - stream->get_parameter_list(¶meters); - for (const AudioStream::Parameter &K : parameters) { - const PropertyInfo &pi = K.property; - StringName key = PARAM_PREFIX + pi.name; - if (!playback_parameters.has(key)) { - ParameterData pd; - pd.path = pi.name; - pd.value = K.default_value; - playback_parameters.insert(key, pd); - } - } -} - void AudioStreamPlayer3D::set_stream(Ref<AudioStream> p_stream) { - if (stream.is_valid()) { - stream->disconnect(SNAME("parameter_list_changed"), callable_mp(this, &AudioStreamPlayer3D::_update_stream_parameters)); - } - stop(); - stream = p_stream; - _update_stream_parameters(); - if (stream.is_valid()) { - stream->connect(SNAME("parameter_list_changed"), callable_mp(this, &AudioStreamPlayer3D::_update_stream_parameters)); - } - notify_property_list_changed(); + internal->set_stream(p_stream); } Ref<AudioStream> AudioStreamPlayer3D::get_stream() const { - return stream; + return internal->stream; } void AudioStreamPlayer3D::set_volume_db(float p_volume) { - volume_db = p_volume; + internal->volume_db = p_volume; } float AudioStreamPlayer3D::get_volume_db() const { - return volume_db; + return internal->volume_db; } void AudioStreamPlayer3D::set_unit_size(float p_volume) { @@ -588,34 +519,20 @@ float AudioStreamPlayer3D::get_max_db() const { } void AudioStreamPlayer3D::set_pitch_scale(float p_pitch_scale) { - ERR_FAIL_COND(!(p_pitch_scale > 0.0)); - pitch_scale = p_pitch_scale; + internal->set_pitch_scale(p_pitch_scale); } float AudioStreamPlayer3D::get_pitch_scale() const { - return pitch_scale; + return internal->pitch_scale; } void AudioStreamPlayer3D::play(float p_from_pos) { - if (stream.is_null()) { + Ref<AudioStreamPlayback> stream_playback = internal->play_basic(); + if (stream_playback.is_null()) { return; } - ERR_FAIL_COND_MSG(!is_inside_tree(), "Playback can only happen when a node is inside the scene tree"); - if (stream->is_monophonic() && is_playing()) { - stop(); - } - Ref<AudioStreamPlayback> stream_playback = stream->instantiate_playback(); - ERR_FAIL_COND_MSG(stream_playback.is_null(), "Failed to instantiate playback."); - - for (const KeyValue<StringName, ParameterData> &K : playback_parameters) { - stream_playback->set_parameter(K.value.path, K.value.value); - } - - stream_playbacks.push_back(stream_playback); setplayback = stream_playback; setplay.set(p_from_pos); - active.set(); - set_physics_process_internal(true); } void AudioStreamPlayer3D::seek(float p_seconds) { @@ -627,83 +544,46 @@ void AudioStreamPlayer3D::seek(float p_seconds) { void AudioStreamPlayer3D::stop() { setplay.set(-1); - for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { - AudioServer::get_singleton()->stop_playback_stream(playback); - } - stream_playbacks.clear(); - active.clear(); - set_physics_process_internal(false); + internal->stop(); } bool AudioStreamPlayer3D::is_playing() const { - for (const Ref<AudioStreamPlayback> &playback : stream_playbacks) { - if (AudioServer::get_singleton()->is_playback_active(playback)) { - return true; - } - } if (setplay.get() >= 0) { return true; // play() has been called this frame, but no playback exists just yet. } - return false; + return internal->is_playing(); } float AudioStreamPlayer3D::get_playback_position() { - // Return the playback position of the most recently started playback stream. - if (!stream_playbacks.is_empty()) { - return AudioServer::get_singleton()->get_playback_position(stream_playbacks[stream_playbacks.size() - 1]); - } - return 0; + return internal->get_playback_position(); } void AudioStreamPlayer3D::set_bus(const StringName &p_bus) { - //if audio is active, must lock this - AudioServer::get_singleton()->lock(); - bus = p_bus; - AudioServer::get_singleton()->unlock(); + internal->bus = p_bus; // This will be pushed to the audio server during the next physics timestep, which is fast enough. } StringName AudioStreamPlayer3D::get_bus() const { - for (int i = 0; i < AudioServer::get_singleton()->get_bus_count(); i++) { - if (AudioServer::get_singleton()->get_bus_name(i) == bus) { - return bus; - } - } - return SceneStringNames::get_singleton()->Master; + return internal->get_bus(); } void AudioStreamPlayer3D::set_autoplay(bool p_enable) { - autoplay = p_enable; + internal->autoplay = p_enable; } bool AudioStreamPlayer3D::is_autoplay_enabled() { - return autoplay; + return internal->autoplay; } void AudioStreamPlayer3D::_set_playing(bool p_enable) { - if (p_enable) { - play(); - } else { - stop(); - } + internal->set_playing(p_enable); } bool AudioStreamPlayer3D::_is_active() const { - return active.is_set(); + return internal->is_active(); } void AudioStreamPlayer3D::_validate_property(PropertyInfo &p_property) const { - if (p_property.name == "bus") { - String options; - for (int i = 0; i < AudioServer::get_singleton()->get_bus_count(); i++) { - if (i > 0) { - options += ","; - } - String name = AudioServer::get_singleton()->get_bus_name(i); - options += name; - } - - p_property.hint_string = options; - } + internal->validate_property(p_property); } void AudioStreamPlayer3D::set_max_distance(float p_metres) { @@ -800,37 +680,27 @@ AudioStreamPlayer3D::DopplerTracking AudioStreamPlayer3D::get_doppler_tracking() } void AudioStreamPlayer3D::set_stream_paused(bool p_pause) { - // TODO this does not have perfect recall, fix that maybe? If there are zero playbacks registered with the AudioServer, this bool isn't persisted. - for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { - AudioServer::get_singleton()->set_playback_paused(playback, p_pause); - } + internal->set_stream_paused(p_pause); } bool AudioStreamPlayer3D::get_stream_paused() const { - // There's currently no way to pause some playback streams but not others. Check the first and don't bother looking at the rest. - if (!stream_playbacks.is_empty()) { - return AudioServer::get_singleton()->is_playback_paused(stream_playbacks[0]); - } - return false; + return internal->get_stream_paused(); } bool AudioStreamPlayer3D::has_stream_playback() { - return !stream_playbacks.is_empty(); + return internal->has_stream_playback(); } Ref<AudioStreamPlayback> AudioStreamPlayer3D::get_stream_playback() { - ERR_FAIL_COND_V_MSG(stream_playbacks.is_empty(), Ref<AudioStreamPlayback>(), "Player is inactive. Call play() before requesting get_stream_playback()."); - return stream_playbacks[stream_playbacks.size() - 1]; + return internal->get_stream_playback(); } void AudioStreamPlayer3D::set_max_polyphony(int p_max_polyphony) { - if (p_max_polyphony > 0) { - max_polyphony = p_max_polyphony; - } + internal->set_max_polyphony(p_max_polyphony); } int AudioStreamPlayer3D::get_max_polyphony() const { - return max_polyphony; + return internal->max_polyphony; } void AudioStreamPlayer3D::set_panning_strength(float p_panning_strength) { @@ -842,48 +712,16 @@ float AudioStreamPlayer3D::get_panning_strength() const { return panning_strength; } -void AudioStreamPlayer3D::_on_bus_layout_changed() { - notify_property_list_changed(); -} - -void AudioStreamPlayer3D::_on_bus_renamed(int p_bus_index, const StringName &p_old_name, const StringName &p_new_name) { - notify_property_list_changed(); -} - bool AudioStreamPlayer3D::_set(const StringName &p_name, const Variant &p_value) { - HashMap<StringName, ParameterData>::Iterator I = playback_parameters.find(p_name); - if (!I) { - return false; - } - ParameterData &pd = I->value; - pd.value = p_value; - for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { - playback->set_parameter(pd.path, pd.value); - } - return true; + return internal->set(p_name, p_value); } bool AudioStreamPlayer3D::_get(const StringName &p_name, Variant &r_ret) const { - HashMap<StringName, ParameterData>::ConstIterator I = playback_parameters.find(p_name); - if (!I) { - return false; - } - - r_ret = I->value.value; - return true; + return internal->get(p_name, r_ret); } void AudioStreamPlayer3D::_get_property_list(List<PropertyInfo> *p_list) const { - if (stream.is_null()) { - return; - } - List<AudioStream::Parameter> parameters; - stream->get_parameter_list(¶meters); - for (const AudioStream::Parameter &K : parameters) { - PropertyInfo pi = K.property; - pi.name = PARAM_PREFIX + pi.name; - p_list->push_back(pi); - } + internal->get_property_list(p_list); } void AudioStreamPlayer3D::_bind_methods() { @@ -994,12 +832,12 @@ void AudioStreamPlayer3D::_bind_methods() { } AudioStreamPlayer3D::AudioStreamPlayer3D() { + internal = memnew(AudioStreamPlayerInternal(this, callable_mp(this, &AudioStreamPlayer3D::play), true)); velocity_tracker.instantiate(); - AudioServer::get_singleton()->connect("bus_layout_changed", callable_mp(this, &AudioStreamPlayer3D::_on_bus_layout_changed)); - AudioServer::get_singleton()->connect("bus_renamed", callable_mp(this, &AudioStreamPlayer3D::_on_bus_renamed)); set_disable_scale(true); cached_global_panning_strength = GLOBAL_GET("audio/general/3d_panning_strength"); } AudioStreamPlayer3D::~AudioStreamPlayer3D() { + memdelete(internal); } diff --git a/scene/3d/audio_stream_player_3d.h b/scene/3d/audio_stream_player_3d.h index facded1b9c..3cc1efaf67 100644 --- a/scene/3d/audio_stream_player_3d.h +++ b/scene/3d/audio_stream_player_3d.h @@ -31,15 +31,16 @@ #ifndef AUDIO_STREAM_PLAYER_3D_H #define AUDIO_STREAM_PLAYER_3D_H -#include "core/os/mutex.h" -#include "scene/3d/area_3d.h" #include "scene/3d/node_3d.h" -#include "scene/3d/velocity_tracker_3d.h" -#include "servers/audio/audio_filter_sw.h" -#include "servers/audio/audio_stream.h" -#include "servers/audio_server.h" +class Area3D; +struct AudioFrame; +class AudioStream; +class AudioStreamPlayback; +class AudioStreamPlayerInternal; class Camera3D; +class VelocityTracker3D; + class AudioStreamPlayer3D : public Node3D { GDCLASS(AudioStreamPlayer3D, Node3D); @@ -64,23 +65,16 @@ private: }; - Vector<Ref<AudioStreamPlayback>> stream_playbacks; - Ref<AudioStream> stream; + AudioStreamPlayerInternal *internal = nullptr; - SafeFlag active{ false }; SafeNumeric<float> setplay{ -1.0 }; Ref<AudioStreamPlayback> setplayback; AttenuationModel attenuation_model = ATTENUATION_INVERSE_DISTANCE; - float volume_db = 0.0; float unit_size = 10.0; float max_db = 3.0; - float pitch_scale = 1.0; // Internally used to take doppler tracking into account. float actual_pitch_scale = 1.0; - bool autoplay = false; - StringName bus = SNAME("Master"); - int max_polyphony = 1; uint64_t last_mix_count = -1; bool force_update_panning = false; @@ -97,9 +91,6 @@ private: Area3D *_get_overriding_area(); Vector<AudioFrame> _update_panning(); - void _on_bus_layout_changed(); - void _on_bus_renamed(int p_bus_index, const StringName &p_old_name, const StringName &p_new_name); - uint32_t area_mask = 1; bool emission_angle_enabled = false; @@ -121,14 +112,6 @@ private: float panning_strength = 1.0f; float cached_global_panning_strength = 0.5f; - struct ParameterData { - StringName path; - Variant value; - }; - - HashMap<StringName, ParameterData> playback_parameters; - void _update_stream_parameters(); - protected: void _validate_property(PropertyInfo &p_property) const; void _notification(int p_what); diff --git a/scene/3d/collision_object_3d.cpp b/scene/3d/collision_object_3d.cpp index 97b1e282ad..0cfe0f8cb7 100644 --- a/scene/3d/collision_object_3d.cpp +++ b/scene/3d/collision_object_3d.cpp @@ -624,6 +624,7 @@ void CollisionObject3D::shape_owner_add_shape(uint32_t p_owner, const Ref<Shape3 total_subshapes++; _update_shape_data(p_owner); + update_gizmos(); } int CollisionObject3D::shape_owner_get_shape_count(uint32_t p_owner) const { @@ -687,6 +688,8 @@ void CollisionObject3D::shape_owner_clear_shapes(uint32_t p_owner) { while (shape_owner_get_shape_count(p_owner) > 0) { shape_owner_remove_shape(p_owner, 0); } + + update_gizmos(); } uint32_t CollisionObject3D::shape_find_owner(int p_shape_index) const { diff --git a/scene/audio/audio_stream_player.cpp b/scene/audio/audio_stream_player.cpp index bd4731d8dd..d7582526a3 100644 --- a/scene/audio/audio_stream_player.cpp +++ b/scene/audio/audio_stream_player.cpp @@ -30,253 +30,104 @@ #include "audio_stream_player.h" -#include "core/config/engine.h" -#include "core/math/audio_frame.h" -#include "servers/audio_server.h" - -#define PARAM_PREFIX "parameters/" +#include "scene/audio/audio_stream_player_internal.h" +#include "servers/audio/audio_stream.h" void AudioStreamPlayer::_notification(int p_what) { - switch (p_what) { - case NOTIFICATION_ENTER_TREE: { - if (autoplay && !Engine::get_singleton()->is_editor_hint()) { - play(); - } - set_stream_paused(!can_process()); - } break; - - case NOTIFICATION_INTERNAL_PROCESS: { - Vector<Ref<AudioStreamPlayback>> playbacks_to_remove; - for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { - if (playback.is_valid() && !AudioServer::get_singleton()->is_playback_active(playback) && !AudioServer::get_singleton()->is_playback_paused(playback)) { - playbacks_to_remove.push_back(playback); - } - } - // Now go through and remove playbacks that have finished. Removing elements from a Vector in a range based for is asking for trouble. - for (Ref<AudioStreamPlayback> &playback : playbacks_to_remove) { - stream_playbacks.erase(playback); - } - if (!playbacks_to_remove.is_empty() && stream_playbacks.is_empty()) { - // This node is no longer actively playing audio. - active.clear(); - set_process_internal(false); - } - if (!playbacks_to_remove.is_empty()) { - emit_signal(SNAME("finished")); - } - } break; - - case NOTIFICATION_EXIT_TREE: { - set_stream_paused(true); - } break; - - case NOTIFICATION_PREDELETE: { - for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { - AudioServer::get_singleton()->stop_playback_stream(playback); - } - stream_playbacks.clear(); - } break; - - case NOTIFICATION_PAUSED: { - if (!can_process()) { - // Node can't process so we start fading out to silence - set_stream_paused(true); - } - } break; - - case NOTIFICATION_UNPAUSED: { - set_stream_paused(false); - } break; - } -} - -void AudioStreamPlayer::_update_stream_parameters() { - if (stream.is_null()) { - return; - } - List<AudioStream::Parameter> parameters; - stream->get_parameter_list(¶meters); - for (const AudioStream::Parameter &K : parameters) { - const PropertyInfo &pi = K.property; - StringName key = PARAM_PREFIX + pi.name; - if (!playback_parameters.has(key)) { - ParameterData pd; - pd.path = pi.name; - pd.value = K.default_value; - playback_parameters.insert(key, pd); - } - } + internal->notification(p_what); } void AudioStreamPlayer::set_stream(Ref<AudioStream> p_stream) { - if (stream.is_valid()) { - stream->disconnect(SNAME("parameter_list_changed"), callable_mp(this, &AudioStreamPlayer::_update_stream_parameters)); - } - stop(); - stream = p_stream; - _update_stream_parameters(); - if (stream.is_valid()) { - stream->connect(SNAME("parameter_list_changed"), callable_mp(this, &AudioStreamPlayer::_update_stream_parameters)); - } - notify_property_list_changed(); + internal->set_stream(p_stream); } bool AudioStreamPlayer::_set(const StringName &p_name, const Variant &p_value) { - HashMap<StringName, ParameterData>::Iterator I = playback_parameters.find(p_name); - if (!I) { - return false; - } - ParameterData &pd = I->value; - pd.value = p_value; - for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { - playback->set_parameter(pd.path, pd.value); - } - return true; + return internal->set(p_name, p_value); } bool AudioStreamPlayer::_get(const StringName &p_name, Variant &r_ret) const { - HashMap<StringName, ParameterData>::ConstIterator I = playback_parameters.find(p_name); - if (!I) { - return false; - } - - r_ret = I->value.value; - return true; + return internal->get(p_name, r_ret); } void AudioStreamPlayer::_get_property_list(List<PropertyInfo> *p_list) const { - if (stream.is_null()) { - return; - } - List<AudioStream::Parameter> parameters; - stream->get_parameter_list(¶meters); - for (const AudioStream::Parameter &K : parameters) { - PropertyInfo pi = K.property; - pi.name = PARAM_PREFIX + pi.name; - p_list->push_back(pi); - } + internal->get_property_list(p_list); } Ref<AudioStream> AudioStreamPlayer::get_stream() const { - return stream; + return internal->stream; } void AudioStreamPlayer::set_volume_db(float p_volume) { - volume_db = p_volume; + internal->volume_db = p_volume; Vector<AudioFrame> volume_vector = _get_volume_vector(); - for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { + for (Ref<AudioStreamPlayback> &playback : internal->stream_playbacks) { AudioServer::get_singleton()->set_playback_all_bus_volumes_linear(playback, volume_vector); } } float AudioStreamPlayer::get_volume_db() const { - return volume_db; + return internal->volume_db; } void AudioStreamPlayer::set_pitch_scale(float p_pitch_scale) { - ERR_FAIL_COND(!(p_pitch_scale > 0.0)); - pitch_scale = p_pitch_scale; - - for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { - AudioServer::get_singleton()->set_playback_pitch_scale(playback, pitch_scale); - } + internal->set_pitch_scale(p_pitch_scale); } float AudioStreamPlayer::get_pitch_scale() const { - return pitch_scale; + return internal->pitch_scale; } void AudioStreamPlayer::set_max_polyphony(int p_max_polyphony) { - if (p_max_polyphony > 0) { - max_polyphony = p_max_polyphony; - } + internal->set_max_polyphony(p_max_polyphony); } int AudioStreamPlayer::get_max_polyphony() const { - return max_polyphony; + return internal->max_polyphony; } void AudioStreamPlayer::play(float p_from_pos) { - if (stream.is_null()) { + Ref<AudioStreamPlayback> stream_playback = internal->play_basic(); + if (stream_playback.is_null()) { return; } - ERR_FAIL_COND_MSG(!is_inside_tree(), "Playback can only happen when a node is inside the scene tree"); - if (stream->is_monophonic() && is_playing()) { - stop(); - } - Ref<AudioStreamPlayback> stream_playback = stream->instantiate_playback(); - ERR_FAIL_COND_MSG(stream_playback.is_null(), "Failed to instantiate playback."); - - for (const KeyValue<StringName, ParameterData> &K : playback_parameters) { - stream_playback->set_parameter(K.value.path, K.value.value); - } - - AudioServer::get_singleton()->start_playback_stream(stream_playback, bus, _get_volume_vector(), p_from_pos, pitch_scale); - stream_playbacks.push_back(stream_playback); - active.set(); - set_process_internal(true); - while (stream_playbacks.size() > max_polyphony) { - AudioServer::get_singleton()->stop_playback_stream(stream_playbacks[0]); - stream_playbacks.remove_at(0); - } + AudioServer::get_singleton()->start_playback_stream(stream_playback, internal->bus, _get_volume_vector(), p_from_pos, internal->pitch_scale); + internal->ensure_playback_limit(); } void AudioStreamPlayer::seek(float p_seconds) { - if (is_playing()) { - stop(); - play(p_seconds); - } + internal->seek(p_seconds); } void AudioStreamPlayer::stop() { - for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { - AudioServer::get_singleton()->stop_playback_stream(playback); - } - stream_playbacks.clear(); - active.clear(); - set_process_internal(false); + internal->stop(); } bool AudioStreamPlayer::is_playing() const { - for (const Ref<AudioStreamPlayback> &playback : stream_playbacks) { - if (AudioServer::get_singleton()->is_playback_active(playback)) { - return true; - } - } - return false; + return internal->is_playing(); } float AudioStreamPlayer::get_playback_position() { - // Return the playback position of the most recently started playback stream. - if (!stream_playbacks.is_empty()) { - return AudioServer::get_singleton()->get_playback_position(stream_playbacks[stream_playbacks.size() - 1]); - } - return 0; + return internal->get_playback_position(); } void AudioStreamPlayer::set_bus(const StringName &p_bus) { - bus = p_bus; - for (const Ref<AudioStreamPlayback> &playback : stream_playbacks) { + internal->bus = p_bus; + for (const Ref<AudioStreamPlayback> &playback : internal->stream_playbacks) { AudioServer::get_singleton()->set_playback_bus_exclusive(playback, p_bus, _get_volume_vector()); } } StringName AudioStreamPlayer::get_bus() const { - for (int i = 0; i < AudioServer::get_singleton()->get_bus_count(); i++) { - if (AudioServer::get_singleton()->get_bus_name(i) == String(bus)) { - return bus; - } - } - return SceneStringNames::get_singleton()->Master; + return internal->get_bus(); } void AudioStreamPlayer::set_autoplay(bool p_enable) { - autoplay = p_enable; + internal->autoplay = p_enable; } bool AudioStreamPlayer::is_autoplay_enabled() { - return autoplay; + return internal->autoplay; } void AudioStreamPlayer::set_mix_target(MixTarget p_target) { @@ -288,43 +139,19 @@ AudioStreamPlayer::MixTarget AudioStreamPlayer::get_mix_target() const { } void AudioStreamPlayer::_set_playing(bool p_enable) { - if (p_enable) { - play(); - } else { - stop(); - } + internal->set_playing(p_enable); } bool AudioStreamPlayer::_is_active() const { - for (const Ref<AudioStreamPlayback> &playback : stream_playbacks) { - if (AudioServer::get_singleton()->is_playback_active(playback)) { - return true; - } - } - return false; -} - -void AudioStreamPlayer::_on_bus_layout_changed() { - notify_property_list_changed(); -} - -void AudioStreamPlayer::_on_bus_renamed(int p_bus_index, const StringName &p_old_name, const StringName &p_new_name) { - notify_property_list_changed(); + return internal->is_active(); } void AudioStreamPlayer::set_stream_paused(bool p_pause) { - // TODO this does not have perfect recall, fix that maybe? If there are zero playbacks registered with the AudioServer, this bool isn't persisted. - for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { - AudioServer::get_singleton()->set_playback_paused(playback, p_pause); - } + internal->set_stream_paused(p_pause); } bool AudioStreamPlayer::get_stream_paused() const { - // There's currently no way to pause some playback streams but not others. Check the first and don't bother looking at the rest. - if (!stream_playbacks.is_empty()) { - return AudioServer::get_singleton()->is_playback_paused(stream_playbacks[0]); - } - return false; + return internal->get_stream_paused(); } Vector<AudioFrame> AudioStreamPlayer::_get_volume_vector() { @@ -337,7 +164,7 @@ Vector<AudioFrame> AudioStreamPlayer::_get_volume_vector() { channel_volume_db = AudioFrame(0, 0); } - float volume_linear = Math::db_to_linear(volume_db); + float volume_linear = Math::db_to_linear(internal->volume_db); // Set the volume vector up according to the speaker mode and mix target. // TODO do we need to scale the volume down when we output to more channels? @@ -365,27 +192,15 @@ Vector<AudioFrame> AudioStreamPlayer::_get_volume_vector() { } void AudioStreamPlayer::_validate_property(PropertyInfo &p_property) const { - if (p_property.name == "bus") { - String options; - for (int i = 0; i < AudioServer::get_singleton()->get_bus_count(); i++) { - if (i > 0) { - options += ","; - } - String name = AudioServer::get_singleton()->get_bus_name(i); - options += name; - } - - p_property.hint_string = options; - } + internal->validate_property(p_property); } bool AudioStreamPlayer::has_stream_playback() { - return !stream_playbacks.is_empty(); + return internal->has_stream_playback(); } Ref<AudioStreamPlayback> AudioStreamPlayer::get_stream_playback() { - ERR_FAIL_COND_V_MSG(stream_playbacks.is_empty(), Ref<AudioStreamPlayback>(), "Player is inactive. Call play() before requesting get_stream_playback()."); - return stream_playbacks[stream_playbacks.size() - 1]; + return internal->get_stream_playback(); } void AudioStreamPlayer::_bind_methods() { @@ -444,9 +259,9 @@ void AudioStreamPlayer::_bind_methods() { } AudioStreamPlayer::AudioStreamPlayer() { - AudioServer::get_singleton()->connect("bus_layout_changed", callable_mp(this, &AudioStreamPlayer::_on_bus_layout_changed)); - AudioServer::get_singleton()->connect("bus_renamed", callable_mp(this, &AudioStreamPlayer::_on_bus_renamed)); + internal = memnew(AudioStreamPlayerInternal(this, callable_mp(this, &AudioStreamPlayer::play), false)); } AudioStreamPlayer::~AudioStreamPlayer() { + memdelete(internal); } diff --git a/scene/audio/audio_stream_player.h b/scene/audio/audio_stream_player.h index 404d6fbebf..754e670553 100644 --- a/scene/audio/audio_stream_player.h +++ b/scene/audio/audio_stream_player.h @@ -31,10 +31,12 @@ #ifndef AUDIO_STREAM_PLAYER_H #define AUDIO_STREAM_PLAYER_H -#include "core/templates/safe_refcount.h" #include "scene/main/node.h" -#include "scene/scene_string_names.h" -#include "servers/audio/audio_stream.h" + +struct AudioFrame; +class AudioStream; +class AudioStreamPlayback; +class AudioStreamPlayerInternal; class AudioStreamPlayer : public Node { GDCLASS(AudioStreamPlayer, Node); @@ -47,35 +49,15 @@ public: }; private: - Vector<Ref<AudioStreamPlayback>> stream_playbacks; - Ref<AudioStream> stream; - - SafeFlag active; - - float pitch_scale = 1.0; - float volume_db = 0.0; - bool autoplay = false; - StringName bus = SceneStringNames::get_singleton()->Master; - int max_polyphony = 1; + AudioStreamPlayerInternal *internal = nullptr; MixTarget mix_target = MIX_TARGET_STEREO; void _set_playing(bool p_enable); bool _is_active() const; - void _on_bus_layout_changed(); - void _on_bus_renamed(int p_bus_index, const StringName &p_old_name, const StringName &p_new_name); - Vector<AudioFrame> _get_volume_vector(); - struct ParameterData { - StringName path; - Variant value; - }; - - HashMap<StringName, ParameterData> playback_parameters; - void _update_stream_parameters(); - protected: void _validate_property(PropertyInfo &p_property) const; void _notification(int p_what); diff --git a/scene/audio/audio_stream_player_internal.cpp b/scene/audio/audio_stream_player_internal.cpp new file mode 100644 index 0000000000..d5f6f91c88 --- /dev/null +++ b/scene/audio/audio_stream_player_internal.cpp @@ -0,0 +1,321 @@ +/**************************************************************************/ +/* audio_stream_player_internal.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 "audio_stream_player_internal.h" + +#include "scene/main/node.h" +#include "scene/scene_string_names.h" +#include "servers/audio/audio_stream.h" + +void AudioStreamPlayerInternal::_set_process(bool p_enabled) { + if (physical) { + node->set_physics_process_internal(p_enabled); + } else { + node->set_process(p_enabled); + } +} + +void AudioStreamPlayerInternal::_update_stream_parameters() { + if (stream.is_null()) { + return; + } + + List<AudioStream::Parameter> parameters; + stream->get_parameter_list(¶meters); + for (const AudioStream::Parameter &K : parameters) { + const PropertyInfo &pi = K.property; + StringName key = PARAM_PREFIX + pi.name; + if (!playback_parameters.has(key)) { + ParameterData pd; + pd.path = pi.name; + pd.value = K.default_value; + playback_parameters.insert(key, pd); + } + } +} + +void AudioStreamPlayerInternal::process() { + Vector<Ref<AudioStreamPlayback>> playbacks_to_remove; + for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { + if (playback.is_valid() && !AudioServer::get_singleton()->is_playback_active(playback) && !AudioServer::get_singleton()->is_playback_paused(playback)) { + playbacks_to_remove.push_back(playback); + } + } + // Now go through and remove playbacks that have finished. Removing elements from a Vector in a range based for is asking for trouble. + for (Ref<AudioStreamPlayback> &playback : playbacks_to_remove) { + stream_playbacks.erase(playback); + } + if (!playbacks_to_remove.is_empty() && stream_playbacks.is_empty()) { + // This node is no longer actively playing audio. + active.clear(); + _set_process(false); + } + if (!playbacks_to_remove.is_empty()) { + node->emit_signal(SNAME("finished")); + } +} + +void AudioStreamPlayerInternal::ensure_playback_limit() { + while (stream_playbacks.size() > max_polyphony) { + AudioServer::get_singleton()->stop_playback_stream(stream_playbacks[0]); + stream_playbacks.remove_at(0); + } +} + +void AudioStreamPlayerInternal::notification(int p_what) { + switch (p_what) { + case Node::NOTIFICATION_ENTER_TREE: { + if (autoplay && !Engine::get_singleton()->is_editor_hint()) { + play_callable.call(0.0); + } + set_stream_paused(!node->can_process()); + } break; + + case Node::NOTIFICATION_EXIT_TREE: { + set_stream_paused(true); + } break; + + case Node::NOTIFICATION_INTERNAL_PROCESS: { + process(); + } break; + + case Node::NOTIFICATION_PREDELETE: { + for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { + AudioServer::get_singleton()->stop_playback_stream(playback); + } + stream_playbacks.clear(); + } break; + + case Node::NOTIFICATION_PAUSED: { + if (!node->can_process()) { + // Node can't process so we start fading out to silence + set_stream_paused(true); + } + } break; + + case Node::NOTIFICATION_UNPAUSED: { + set_stream_paused(false); + } break; + } +} + +Ref<AudioStreamPlayback> AudioStreamPlayerInternal::play_basic() { + Ref<AudioStreamPlayback> stream_playback; + if (stream.is_null()) { + return stream_playback; + } + ERR_FAIL_COND_V_MSG(!node->is_inside_tree(), stream_playback, "Playback can only happen when a node is inside the scene tree"); + if (stream->is_monophonic() && is_playing()) { + stop(); + } + stream_playback = stream->instantiate_playback(); + ERR_FAIL_COND_V_MSG(stream_playback.is_null(), stream_playback, "Failed to instantiate playback."); + + for (const KeyValue<StringName, ParameterData> &K : playback_parameters) { + stream_playback->set_parameter(K.value.path, K.value.value); + } + + stream_playbacks.push_back(stream_playback); + active.set(); + _set_process(true); + return stream_playback; +} + +void AudioStreamPlayerInternal::set_stream_paused(bool p_pause) { + // TODO this does not have perfect recall, fix that maybe? If there are zero playbacks registered with the AudioServer, this bool isn't persisted. + for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { + AudioServer::get_singleton()->set_playback_paused(playback, p_pause); + } +} + +bool AudioStreamPlayerInternal::get_stream_paused() const { + // There's currently no way to pause some playback streams but not others. Check the first and don't bother looking at the rest. + if (!stream_playbacks.is_empty()) { + return AudioServer::get_singleton()->is_playback_paused(stream_playbacks[0]); + } + return false; +} + +void AudioStreamPlayerInternal::validate_property(PropertyInfo &p_property) const { + if (p_property.name == "bus") { + String options; + for (int i = 0; i < AudioServer::get_singleton()->get_bus_count(); i++) { + if (i > 0) { + options += ","; + } + String name = AudioServer::get_singleton()->get_bus_name(i); + options += name; + } + + p_property.hint_string = options; + } +} + +bool AudioStreamPlayerInternal::set(const StringName &p_name, const Variant &p_value) { + ParameterData *pd = playback_parameters.getptr(p_name); + if (!pd) { + return false; + } + pd->value = p_value; + for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { + playback->set_parameter(pd->path, pd->value); + } + return true; +} + +bool AudioStreamPlayerInternal::get(const StringName &p_name, Variant &r_ret) const { + const ParameterData *pd = playback_parameters.getptr(p_name); + if (!pd) { + return false; + } + r_ret = pd->value; + return true; +} + +void AudioStreamPlayerInternal::get_property_list(List<PropertyInfo> *p_list) const { + if (stream.is_null()) { + return; + } + List<AudioStream::Parameter> parameters; + stream->get_parameter_list(¶meters); + for (const AudioStream::Parameter &K : parameters) { + PropertyInfo pi = K.property; + pi.name = PARAM_PREFIX + pi.name; + + const ParameterData *pd = playback_parameters.getptr(pi.name); + if (pd && pd->value == K.default_value) { + pi.usage &= ~PROPERTY_USAGE_STORAGE; + } + + p_list->push_back(pi); + } +} + +void AudioStreamPlayerInternal::set_stream(Ref<AudioStream> p_stream) { + if (stream.is_valid()) { + stream->disconnect(SNAME("parameter_list_changed"), callable_mp(this, &AudioStreamPlayerInternal::_update_stream_parameters)); + } + stop(); + stream = p_stream; + _update_stream_parameters(); + if (stream.is_valid()) { + stream->connect(SNAME("parameter_list_changed"), callable_mp(this, &AudioStreamPlayerInternal::_update_stream_parameters)); + } + node->notify_property_list_changed(); +} + +void AudioStreamPlayerInternal::seek(float p_seconds) { + if (is_playing()) { + stop(); + play_callable.call(p_seconds); + } +} + +void AudioStreamPlayerInternal::stop() { + for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { + AudioServer::get_singleton()->stop_playback_stream(playback); + } + stream_playbacks.clear(); + active.clear(); + _set_process(false); +} + +bool AudioStreamPlayerInternal::is_playing() const { + for (const Ref<AudioStreamPlayback> &playback : stream_playbacks) { + if (AudioServer::get_singleton()->is_playback_active(playback)) { + return true; + } + } + return false; +} + +float AudioStreamPlayerInternal::get_playback_position() { + // Return the playback position of the most recently started playback stream. + if (!stream_playbacks.is_empty()) { + return AudioServer::get_singleton()->get_playback_position(stream_playbacks[stream_playbacks.size() - 1]); + } + return 0; +} + +void AudioStreamPlayerInternal::set_playing(bool p_enable) { + if (p_enable) { + play_callable.call(0.0); + } else { + stop(); + } +} + +bool AudioStreamPlayerInternal::is_active() const { + return active.is_set(); +} + +void AudioStreamPlayerInternal::set_pitch_scale(float p_pitch_scale) { + ERR_FAIL_COND(p_pitch_scale <= 0.0); + pitch_scale = p_pitch_scale; + + for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { + AudioServer::get_singleton()->set_playback_pitch_scale(playback, pitch_scale); + } +} + +void AudioStreamPlayerInternal::set_max_polyphony(int p_max_polyphony) { + if (p_max_polyphony > 0) { + max_polyphony = p_max_polyphony; + } +} + +bool AudioStreamPlayerInternal::has_stream_playback() { + return !stream_playbacks.is_empty(); +} + +Ref<AudioStreamPlayback> AudioStreamPlayerInternal::get_stream_playback() { + ERR_FAIL_COND_V_MSG(stream_playbacks.is_empty(), Ref<AudioStreamPlayback>(), "Player is inactive. Call play() before requesting get_stream_playback()."); + return stream_playbacks[stream_playbacks.size() - 1]; +} + +StringName AudioStreamPlayerInternal::get_bus() const { + const String bus_name = bus; + for (int i = 0; i < AudioServer::get_singleton()->get_bus_count(); i++) { + if (AudioServer::get_singleton()->get_bus_name(i) == bus_name) { + return bus; + } + } + return SceneStringNames::get_singleton()->Master; +} + +AudioStreamPlayerInternal::AudioStreamPlayerInternal(Node *p_node, const Callable &p_play_callable, bool p_physical) { + node = p_node; + play_callable = p_play_callable; + physical = p_physical; + bus = SceneStringNames::get_singleton()->Master; + + AudioServer::get_singleton()->connect("bus_layout_changed", callable_mp((Object *)node, &Object::notify_property_list_changed)); + AudioServer::get_singleton()->connect("bus_renamed", callable_mp((Object *)node, &Object::notify_property_list_changed).unbind(3)); +} diff --git a/scene/audio/audio_stream_player_internal.h b/scene/audio/audio_stream_player_internal.h new file mode 100644 index 0000000000..3662752441 --- /dev/null +++ b/scene/audio/audio_stream_player_internal.h @@ -0,0 +1,105 @@ +/**************************************************************************/ +/* audio_stream_player_internal.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 AUDIO_STREAM_PLAYER_INTERNAL_H +#define AUDIO_STREAM_PLAYER_INTERNAL_H + +#include "core/object/ref_counted.h" +#include "core/templates/safe_refcount.h" + +class AudioStream; +class AudioStreamPlayback; +class Node; + +class AudioStreamPlayerInternal : public Object { + GDCLASS(AudioStreamPlayerInternal, Object); + + struct ParameterData { + StringName path; + Variant value; + }; + + static inline const String PARAM_PREFIX = "parameters/"; + + Node *node = nullptr; + Callable play_callable; + bool physical = false; + + HashMap<StringName, ParameterData> playback_parameters; + + void _set_process(bool p_enabled); + void _update_stream_parameters(); + +public: + Vector<Ref<AudioStreamPlayback>> stream_playbacks; + Ref<AudioStream> stream; + + SafeFlag active; + + float pitch_scale = 1.0; + float volume_db = 0.0; + bool autoplay = false; + StringName bus; + int max_polyphony = 1; + + void process(); + void ensure_playback_limit(); + + void notification(int p_what); + void validate_property(PropertyInfo &p_property) const; + bool set(const StringName &p_name, const Variant &p_value); + bool get(const StringName &p_name, Variant &r_ret) const; + void get_property_list(List<PropertyInfo> *p_list) const; + + void set_stream(Ref<AudioStream> p_stream); + void set_pitch_scale(float p_pitch_scale); + void set_max_polyphony(int p_max_polyphony); + + StringName get_bus() const; + + Ref<AudioStreamPlayback> play_basic(); + void seek(float p_seconds); + void stop(); + bool is_playing() const; + float get_playback_position(); + + void set_playing(bool p_enable); + bool is_active() const; + + void set_stream_paused(bool p_pause); + bool get_stream_paused() const; + + bool has_stream_playback(); + Ref<AudioStreamPlayback> get_stream_playback(); + + AudioStreamPlayerInternal(Node *p_node, const Callable &p_play_callable, bool p_physical); +}; + +#endif // AUDIO_STREAM_PLAYER_INTERNAL_H diff --git a/scene/main/canvas_item.cpp b/scene/main/canvas_item.cpp index a35ee17868..093b52b650 100644 --- a/scene/main/canvas_item.cpp +++ b/scene/main/canvas_item.cpp @@ -314,6 +314,7 @@ void CanvasItem::_notification(int p_what) { } } + _set_global_invalid(true); _enter_canvas(); RenderingServer::get_singleton()->canvas_item_set_visible(canvas_item, is_visible_in_tree()); // The visibility of the parent may change. diff --git a/scene/main/canvas_item.h b/scene/main/canvas_item.h index 36f0e17924..ce1dbce6c3 100644 --- a/scene/main/canvas_item.h +++ b/scene/main/canvas_item.h @@ -237,7 +237,7 @@ public: Color get_modulate() const; Color get_modulate_in_tree() const; - void set_self_modulate(const Color &p_self_modulate); + virtual void set_self_modulate(const Color &p_self_modulate); Color get_self_modulate() const; void set_visibility_layer(uint32_t p_visibility_layer); @@ -248,7 +248,7 @@ public: /* ORDERING */ - void set_z_index(int p_z); + virtual void set_z_index(int p_z); int get_z_index() const; int get_effective_z_index() const; diff --git a/scene/main/http_request.cpp b/scene/main/http_request.cpp index 1972e62659..fa14ad5b3c 100644 --- a/scene/main/http_request.cpp +++ b/scene/main/http_request.cpp @@ -503,7 +503,9 @@ void HTTPRequest::_notification(int p_what) { void HTTPRequest::set_use_threads(bool p_use) { ERR_FAIL_COND(get_http_client_status() != HTTPClient::STATUS_DISCONNECTED); +#ifdef THREADS_ENABLED use_threads.set_to(p_use); +#endif } bool HTTPRequest::is_using_threads() const { diff --git a/scene/main/node.cpp b/scene/main/node.cpp index a6b7ca8188..f7d695bf31 100644 --- a/scene/main/node.cpp +++ b/scene/main/node.cpp @@ -3088,6 +3088,10 @@ static void _add_nodes_to_options(const Node *p_base, const Node *p_node, List<S if (p_node != p_base && !p_node->get_owner()) { return; } + if (p_node->is_unique_name_in_owner() && p_node->get_owner() == p_base) { + String n = "%" + p_node->get_name(); + r_options->push_back(n.quote()); + } String n = p_base->get_path_to(p_node); r_options->push_back(n.quote()); for (int i = 0; i < p_node->get_child_count(); i++) { diff --git a/scene/resources/tile_set.cpp b/scene/resources/tile_set.cpp index a99102e847..66e1b7bcf9 100644 --- a/scene/resources/tile_set.cpp +++ b/scene/resources/tile_set.cpp @@ -1563,6 +1563,632 @@ void TileSet::draw_tile_shape(CanvasItem *p_canvas_item, Transform2D p_transform } } +Vector2 TileSet::map_to_local(const Vector2i &p_pos) const { + // SHOULD RETURN THE CENTER OF THE CELL. + Vector2 ret = p_pos; + + 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. + if (tile_offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { + switch (tile_layout) { + case TileSet::TILE_LAYOUT_STACKED: + ret = Vector2(ret.x + (Math::posmod(ret.y, 2) == 0 ? 0.0 : 0.5), ret.y); + break; + case TileSet::TILE_LAYOUT_STACKED_OFFSET: + ret = Vector2(ret.x + (Math::posmod(ret.y, 2) == 1 ? 0.0 : 0.5), ret.y); + break; + case TileSet::TILE_LAYOUT_STAIRS_RIGHT: + ret = Vector2(ret.x + ret.y / 2, ret.y); + break; + case TileSet::TILE_LAYOUT_STAIRS_DOWN: + ret = Vector2(ret.x / 2, ret.y * 2 + ret.x); + break; + case TileSet::TILE_LAYOUT_DIAMOND_RIGHT: + ret = Vector2((ret.x + ret.y) / 2, ret.y - ret.x); + break; + case TileSet::TILE_LAYOUT_DIAMOND_DOWN: + ret = Vector2((ret.x - ret.y) / 2, ret.y + ret.x); + break; + } + } else { // TILE_OFFSET_AXIS_VERTICAL. + switch (tile_layout) { + case TileSet::TILE_LAYOUT_STACKED: + ret = Vector2(ret.x, ret.y + (Math::posmod(ret.x, 2) == 0 ? 0.0 : 0.5)); + break; + case TileSet::TILE_LAYOUT_STACKED_OFFSET: + ret = Vector2(ret.x, ret.y + (Math::posmod(ret.x, 2) == 1 ? 0.0 : 0.5)); + break; + case TileSet::TILE_LAYOUT_STAIRS_RIGHT: + ret = Vector2(ret.x * 2 + ret.y, ret.y / 2); + break; + case TileSet::TILE_LAYOUT_STAIRS_DOWN: + ret = Vector2(ret.x, ret.y + ret.x / 2); + break; + case TileSet::TILE_LAYOUT_DIAMOND_RIGHT: + ret = Vector2(ret.x + ret.y, (ret.y - ret.x) / 2); + break; + case TileSet::TILE_LAYOUT_DIAMOND_DOWN: + ret = Vector2(ret.x - ret.y, (ret.y + ret.x) / 2); + break; + } + } + } + + // 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) { + overlapping_ratio = 0.5; + } else if (tile_shape == TileSet::TILE_SHAPE_HEXAGON) { + overlapping_ratio = 0.75; + } + ret.y *= overlapping_ratio; + } else { // TILE_OFFSET_AXIS_VERTICAL. + if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC) { + overlapping_ratio = 0.5; + } else if (tile_shape == TileSet::TILE_SHAPE_HEXAGON) { + overlapping_ratio = 0.75; + } + ret.x *= overlapping_ratio; + } + + return (ret + Vector2(0.5, 0.5)) * tile_size; +} + +Vector2i TileSet::local_to_map(const Vector2 &p_local_position) const { + Vector2 ret = p_local_position; + ret /= tile_size; + + // 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) { + overlapping_ratio = 0.5; + } else if (tile_shape == TileSet::TILE_SHAPE_HEXAGON) { + overlapping_ratio = 0.75; + } + ret.y /= overlapping_ratio; + } else { // TILE_OFFSET_AXIS_VERTICAL. + if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC) { + overlapping_ratio = 0.5; + } else if (tile_shape == TileSet::TILE_SHAPE_HEXAGON) { + overlapping_ratio = 0.75; + } + ret.x /= overlapping_ratio; + } + + // 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. + if (tile_offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { + // Smart floor of the position + Vector2 raw_pos = ret; + if (Math::posmod(Math::floor(ret.y), 2) ^ (tile_layout == TileSet::TILE_LAYOUT_STACKED_OFFSET)) { + ret = Vector2(Math::floor(ret.x + 0.5) - 0.5, Math::floor(ret.y)); + } else { + ret = ret.floor(); + } + + // 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; + + switch (tile_layout) { + case TileSet::TILE_LAYOUT_STACKED: + ret = ret.floor(); + if (in_top_left_triangle) { + ret += Vector2i(Math::posmod(Math::floor(ret.y), 2) ? 0 : -1, -1); + } else if (in_top_right_triangle) { + ret += Vector2i(Math::posmod(Math::floor(ret.y), 2) ? 1 : 0, -1); + } + break; + case TileSet::TILE_LAYOUT_STACKED_OFFSET: + ret = ret.floor(); + if (in_top_left_triangle) { + ret += Vector2i(Math::posmod(Math::floor(ret.y), 2) ? -1 : 0, -1); + } else if (in_top_right_triangle) { + ret += Vector2i(Math::posmod(Math::floor(ret.y), 2) ? 0 : 1, -1); + } + break; + case TileSet::TILE_LAYOUT_STAIRS_RIGHT: + ret = Vector2(ret.x - ret.y / 2, ret.y).floor(); + if (in_top_left_triangle) { + ret += Vector2i(0, -1); + } else if (in_top_right_triangle) { + ret += Vector2i(1, -1); + } + break; + case TileSet::TILE_LAYOUT_STAIRS_DOWN: + ret = Vector2(ret.x * 2, ret.y / 2 - ret.x).floor(); + if (in_top_left_triangle) { + ret += Vector2i(-1, 0); + } else if (in_top_right_triangle) { + ret += Vector2i(1, -1); + } + break; + case TileSet::TILE_LAYOUT_DIAMOND_RIGHT: + ret = Vector2(ret.x - ret.y / 2, ret.y / 2 + ret.x).floor(); + if (in_top_left_triangle) { + ret += Vector2i(0, -1); + } else if (in_top_right_triangle) { + ret += Vector2i(1, 0); + } + break; + case TileSet::TILE_LAYOUT_DIAMOND_DOWN: + ret = Vector2(ret.x + ret.y / 2, ret.y / 2 - ret.x).floor(); + if (in_top_left_triangle) { + ret += Vector2i(-1, 0); + } else if (in_top_right_triangle) { + ret += Vector2i(0, -1); + } + break; + } + } 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); + } else { + ret = ret.floor(); + } + + // 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; + + switch (tile_layout) { + case TileSet::TILE_LAYOUT_STACKED: + ret = ret.floor(); + if (in_top_left_triangle) { + ret += Vector2i(-1, Math::posmod(Math::floor(ret.x), 2) ? 0 : -1); + } else if (in_bottom_left_triangle) { + ret += Vector2i(-1, Math::posmod(Math::floor(ret.x), 2) ? 1 : 0); + } + break; + case TileSet::TILE_LAYOUT_STACKED_OFFSET: + ret = ret.floor(); + if (in_top_left_triangle) { + ret += Vector2i(-1, Math::posmod(Math::floor(ret.x), 2) ? -1 : 0); + } else if (in_bottom_left_triangle) { + ret += Vector2i(-1, Math::posmod(Math::floor(ret.x), 2) ? 0 : 1); + } + break; + case TileSet::TILE_LAYOUT_STAIRS_RIGHT: + ret = Vector2(ret.x / 2 - ret.y, ret.y * 2).floor(); + if (in_top_left_triangle) { + ret += Vector2i(0, -1); + } else if (in_bottom_left_triangle) { + ret += Vector2i(-1, 1); + } + break; + case TileSet::TILE_LAYOUT_STAIRS_DOWN: + ret = Vector2(ret.x, ret.y - ret.x / 2).floor(); + if (in_top_left_triangle) { + ret += Vector2i(-1, 0); + } else if (in_bottom_left_triangle) { + ret += Vector2i(-1, 1); + } + break; + case TileSet::TILE_LAYOUT_DIAMOND_RIGHT: + ret = Vector2(ret.x / 2 - ret.y, ret.y + ret.x / 2).floor(); + if (in_top_left_triangle) { + ret += Vector2i(0, -1); + } else if (in_bottom_left_triangle) { + ret += Vector2i(-1, 0); + } + break; + case TileSet::TILE_LAYOUT_DIAMOND_DOWN: + ret = Vector2(ret.x / 2 + ret.y, ret.y - ret.x / 2).floor(); + if (in_top_left_triangle) { + ret += Vector2i(-1, 0); + } else if (in_bottom_left_triangle) { + ret += Vector2i(0, 1); + } + break; + } + } + } else { + ret = (ret + Vector2(0.00005, 0.00005)).floor(); + } + return Vector2i(ret); +} + +bool TileSet::is_existing_neighbor(TileSet::CellNeighbor p_cell_neighbor) const { + if (tile_shape == TileSet::TILE_SHAPE_SQUARE) { + return p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_SIDE || + p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER || + p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_SIDE || + p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER || + p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_SIDE || + p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER || + p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_SIDE || + p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER; + + } else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC) { + return p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_CORNER || + p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE || + p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_CORNER || + p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE || + p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_CORNER || + p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE || + p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_CORNER || + p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE; + } else { + if (tile_offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { + return p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_SIDE || + p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE || + p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE || + p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_SIDE || + p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE || + p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE; + } else { + return p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE || + p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_SIDE || + p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE || + p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE || + p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_SIDE || + p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE; + } + } +} + +Vector2i TileSet::get_neighbor_cell(const Vector2i &p_coords, TileSet::CellNeighbor p_cell_neighbor) const { + if (tile_shape == TileSet::TILE_SHAPE_SQUARE) { + switch (p_cell_neighbor) { + case TileSet::CELL_NEIGHBOR_RIGHT_SIDE: + return p_coords + Vector2i(1, 0); + case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER: + return p_coords + Vector2i(1, 1); + case TileSet::CELL_NEIGHBOR_BOTTOM_SIDE: + return p_coords + Vector2i(0, 1); + case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER: + return p_coords + Vector2i(-1, 1); + case TileSet::CELL_NEIGHBOR_LEFT_SIDE: + return p_coords + Vector2i(-1, 0); + case TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER: + return p_coords + Vector2i(-1, -1); + case TileSet::CELL_NEIGHBOR_TOP_SIDE: + return p_coords + Vector2i(0, -1); + case TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER: + return p_coords + Vector2i(1, -1); + default: + ERR_FAIL_V(p_coords); + } + } else { // Half-offset shapes (square and hexagon). + switch (tile_layout) { + case TileSet::TILE_LAYOUT_STACKED: { + if (tile_offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { + bool is_offset = p_coords.y % 2; + if ((tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_CORNER) || + (tile_shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_SIDE)) { + return p_coords + Vector2i(1, 0); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE) { + return p_coords + Vector2i(is_offset ? 1 : 0, 1); + } else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_CORNER) { + return p_coords + Vector2i(0, 2); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE) { + return p_coords + Vector2i(is_offset ? 0 : -1, 1); + } else if ((tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_CORNER) || + (tile_shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_SIDE)) { + return p_coords + Vector2i(-1, 0); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE) { + return p_coords + Vector2i(is_offset ? 0 : -1, -1); + } else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_CORNER) { + return p_coords + Vector2i(0, -2); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE) { + return p_coords + Vector2i(is_offset ? 1 : 0, -1); + } else { + ERR_FAIL_V(p_coords); + } + } else { + bool is_offset = p_coords.x % 2; + + if ((tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_CORNER) || + (tile_shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)) { + return p_coords + Vector2i(0, 1); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE) { + return p_coords + Vector2i(1, is_offset ? 1 : 0); + } else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_CORNER) { + return p_coords + Vector2i(2, 0); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE) { + return p_coords + Vector2i(1, is_offset ? 0 : -1); + } else if ((tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_CORNER) || + (tile_shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_SIDE)) { + return p_coords + Vector2i(0, -1); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE) { + return p_coords + Vector2i(-1, is_offset ? 0 : -1); + } else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_CORNER) { + return p_coords + Vector2i(-2, 0); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE) { + return p_coords + Vector2i(-1, is_offset ? 1 : 0); + } else { + ERR_FAIL_V(p_coords); + } + } + } break; + case TileSet::TILE_LAYOUT_STACKED_OFFSET: { + if (tile_offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { + bool is_offset = p_coords.y % 2; + + if ((tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_CORNER) || + (tile_shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_SIDE)) { + return p_coords + Vector2i(1, 0); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE) { + return p_coords + Vector2i(is_offset ? 0 : 1, 1); + } else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_CORNER) { + return p_coords + Vector2i(0, 2); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE) { + return p_coords + Vector2i(is_offset ? -1 : 0, 1); + } else if ((tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_CORNER) || + (tile_shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_SIDE)) { + return p_coords + Vector2i(-1, 0); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE) { + return p_coords + Vector2i(is_offset ? -1 : 0, -1); + } else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_CORNER) { + return p_coords + Vector2i(0, -2); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE) { + return p_coords + Vector2i(is_offset ? 0 : 1, -1); + } else { + ERR_FAIL_V(p_coords); + } + } else { + bool is_offset = p_coords.x % 2; + + if ((tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_CORNER) || + (tile_shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)) { + return p_coords + Vector2i(0, 1); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE) { + return p_coords + Vector2i(1, is_offset ? 0 : 1); + } else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_CORNER) { + return p_coords + Vector2i(2, 0); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE) { + return p_coords + Vector2i(1, is_offset ? -1 : 0); + } else if ((tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_CORNER) || + (tile_shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_SIDE)) { + return p_coords + Vector2i(0, -1); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE) { + return p_coords + Vector2i(-1, is_offset ? -1 : 0); + } else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_CORNER) { + return p_coords + Vector2i(-2, 0); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE) { + return p_coords + Vector2i(-1, is_offset ? 0 : 1); + } else { + ERR_FAIL_V(p_coords); + } + } + } break; + case TileSet::TILE_LAYOUT_STAIRS_RIGHT: + case TileSet::TILE_LAYOUT_STAIRS_DOWN: { + if ((tile_layout == TileSet::TILE_LAYOUT_STAIRS_RIGHT) ^ (tile_offset_axis == TileSet::TILE_OFFSET_AXIS_VERTICAL)) { + if (tile_offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { + if ((tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_CORNER) || + (tile_shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_SIDE)) { + return p_coords + Vector2i(1, 0); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE) { + return p_coords + Vector2i(0, 1); + } else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_CORNER) { + return p_coords + Vector2i(-1, 2); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE) { + return p_coords + Vector2i(-1, 1); + } else if ((tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_CORNER) || + (tile_shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_SIDE)) { + return p_coords + Vector2i(-1, 0); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE) { + return p_coords + Vector2i(0, -1); + } else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_CORNER) { + return p_coords + Vector2i(1, -2); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE) { + return p_coords + Vector2i(1, -1); + } else { + ERR_FAIL_V(p_coords); + } + + } else { + if ((tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_CORNER) || + (tile_shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)) { + return p_coords + Vector2i(0, 1); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE) { + return p_coords + Vector2i(1, 0); + } else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_CORNER) { + return p_coords + Vector2i(2, -1); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE) { + return p_coords + Vector2i(1, -1); + } else if ((tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_CORNER) || + (tile_shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_SIDE)) { + return p_coords + Vector2i(0, -1); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE) { + return p_coords + Vector2i(-1, 0); + } else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_CORNER) { + return p_coords + Vector2i(-2, 1); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE) { + return p_coords + Vector2i(-1, 1); + } else { + ERR_FAIL_V(p_coords); + } + } + } else { + if (tile_offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { + if ((tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_CORNER) || + (tile_shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_SIDE)) { + return p_coords + Vector2i(2, -1); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE) { + return p_coords + Vector2i(1, 0); + } else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_CORNER) { + return p_coords + Vector2i(0, 1); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE) { + return p_coords + Vector2i(-1, 1); + } else if ((tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_CORNER) || + (tile_shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_SIDE)) { + return p_coords + Vector2i(-2, 1); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE) { + return p_coords + Vector2i(-1, 0); + } else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_CORNER) { + return p_coords + Vector2i(0, -1); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE) { + return p_coords + Vector2i(1, -1); + } else { + ERR_FAIL_V(p_coords); + } + + } else { + if ((tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_CORNER) || + (tile_shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)) { + return p_coords + Vector2i(-1, 2); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE) { + return p_coords + Vector2i(0, 1); + } else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_CORNER) { + return p_coords + Vector2i(1, 0); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE) { + return p_coords + Vector2i(1, -1); + } else if ((tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_CORNER) || + (tile_shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_SIDE)) { + return p_coords + Vector2i(1, -2); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE) { + return p_coords + Vector2i(0, -1); + } else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_CORNER) { + return p_coords + Vector2i(-1, 0); + + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE) { + return p_coords + Vector2i(-1, 1); + } else { + ERR_FAIL_V(p_coords); + } + } + } + } break; + case TileSet::TILE_LAYOUT_DIAMOND_RIGHT: + case TileSet::TILE_LAYOUT_DIAMOND_DOWN: { + if ((tile_layout == TileSet::TILE_LAYOUT_DIAMOND_RIGHT) ^ (tile_offset_axis == TileSet::TILE_OFFSET_AXIS_VERTICAL)) { + if (tile_offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { + if ((tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_CORNER) || + (tile_shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_SIDE)) { + return p_coords + Vector2i(1, 1); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE) { + return p_coords + Vector2i(0, 1); + } else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_CORNER) { + return p_coords + Vector2i(-1, 1); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE) { + return p_coords + Vector2i(-1, 0); + } else if ((tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_CORNER) || + (tile_shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_SIDE)) { + return p_coords + Vector2i(-1, -1); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE) { + return p_coords + Vector2i(0, -1); + } else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_CORNER) { + return p_coords + Vector2i(1, -1); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE) { + return p_coords + Vector2i(1, 0); + } else { + ERR_FAIL_V(p_coords); + } + + } else { + if ((tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_CORNER) || + (tile_shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)) { + return p_coords + Vector2i(1, 1); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE) { + return p_coords + Vector2i(1, 0); + } else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_CORNER) { + return p_coords + Vector2i(1, -1); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE) { + return p_coords + Vector2i(0, -1); + } else if ((tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_CORNER) || + (tile_shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_SIDE)) { + return p_coords + Vector2i(-1, -1); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE) { + return p_coords + Vector2i(-1, 0); + } else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_CORNER) { + return p_coords + Vector2i(-1, 1); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE) { + return p_coords + Vector2i(0, 1); + } else { + ERR_FAIL_V(p_coords); + } + } + } else { + if (tile_offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { + if ((tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_CORNER) || + (tile_shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_SIDE)) { + return p_coords + Vector2i(1, -1); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE) { + return p_coords + Vector2i(1, 0); + } else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_CORNER) { + return p_coords + Vector2i(1, 1); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE) { + return p_coords + Vector2i(0, 1); + } else if ((tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_CORNER) || + (tile_shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_SIDE)) { + return p_coords + Vector2i(-1, 1); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE) { + return p_coords + Vector2i(-1, 0); + } else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_CORNER) { + return p_coords + Vector2i(-1, -1); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE) { + return p_coords + Vector2i(0, -1); + } else { + ERR_FAIL_V(p_coords); + } + + } else { + if ((tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_CORNER) || + (tile_shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)) { + return p_coords + Vector2i(-1, 1); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE) { + return p_coords + Vector2i(0, 1); + } else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_CORNER) { + return p_coords + Vector2i(1, 1); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE) { + return p_coords + Vector2i(1, 0); + } else if ((tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_CORNER) || + (tile_shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_SIDE)) { + return p_coords + Vector2i(1, -1); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE) { + return p_coords + Vector2i(0, -1); + } else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_CORNER) { + return p_coords + Vector2i(-1, -1); + + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE) { + return p_coords + Vector2i(-1, 0); + } else { + ERR_FAIL_V(p_coords); + } + } + } + } break; + default: + ERR_FAIL_V(p_coords); + } + } +} + +Vector2i TileSet::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_shape != TileSet::TILE_SHAPE_SQUARE) { + if (tile_layout == TileSet::TILE_LAYOUT_STACKED) { + if (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_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_layout == TileSet::TILE_LAYOUT_STACKED_OFFSET) { + if (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_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; +} + Vector<Point2> TileSet::get_terrain_polygon(int p_terrain_set) { if (tile_shape == TileSet::TILE_SHAPE_SQUARE) { return _get_square_terrain_polygon(tile_size); diff --git a/scene/resources/tile_set.h b/scene/resources/tile_set.h index a71982cd56..0a6d879047 100644 --- a/scene/resources/tile_set.h +++ b/scene/resources/tile_set.h @@ -530,6 +530,13 @@ public: Vector<Vector2> get_tile_shape_polygon(); void draw_tile_shape(CanvasItem *p_canvas_item, Transform2D p_transform, Color p_color, bool p_filled = false, Ref<Texture2D> p_texture = Ref<Texture2D>()); + // Used by TileMap/TileMapLayer + Vector2 map_to_local(const Vector2i &p_pos) const; + Vector2i local_to_map(const Vector2 &p_pos) const; + bool is_existing_neighbor(TileSet::CellNeighbor p_cell_neighbor) const; + Vector2i get_neighbor_cell(const Vector2i &p_coords, TileSet::CellNeighbor p_cell_neighbor) const; + Vector2i map_pattern(const Vector2i &p_position_in_tilemap, const Vector2i &p_coords_in_pattern, Ref<TileMapPattern> p_pattern); + Vector<Point2> get_terrain_polygon(int p_terrain_set); Vector<Point2> get_terrain_peering_bit_polygon(int p_terrain_set, TileSet::CellNeighbor p_bit); void draw_terrains(CanvasItem *p_canvas_item, Transform2D p_transform, const TileData *p_tile_data); diff --git a/servers/register_server_types.cpp b/servers/register_server_types.cpp index 2beab44f91..0ee2984a8c 100644 --- a/servers/register_server_types.cpp +++ b/servers/register_server_types.cpp @@ -88,7 +88,11 @@ ShaderTypes *shader_types = nullptr; static PhysicsServer3D *_createGodotPhysics3DCallback() { +#ifdef THREADS_ENABLED bool using_threads = GLOBAL_GET("physics/3d/run_on_separate_thread"); +#else + bool using_threads = false; +#endif PhysicsServer3D *physics_server_3d = memnew(GodotPhysicsServer3D(using_threads)); @@ -96,7 +100,11 @@ static PhysicsServer3D *_createGodotPhysics3DCallback() { } static PhysicsServer2D *_createGodotPhysics2DCallback() { +#ifdef THREADS_ENABLED bool using_threads = GLOBAL_GET("physics/2d/run_on_separate_thread"); +#else + bool using_threads = false; +#endif PhysicsServer2D *physics_server_2d = memnew(GodotPhysicsServer2D(using_threads)); diff --git a/servers/rendering/rendering_server_default.cpp b/servers/rendering/rendering_server_default.cpp index f7620ad5dc..bf8ab27722 100644 --- a/servers/rendering/rendering_server_default.cpp +++ b/servers/rendering/rendering_server_default.cpp @@ -395,15 +395,19 @@ RenderingServerDefault::RenderingServerDefault(bool p_create_thread) : command_queue(p_create_thread) { RenderingServer::init(); +#ifdef THREADS_ENABLED create_thread = p_create_thread; - - if (!p_create_thread) { + if (!create_thread) { server_thread = Thread::get_caller_id(); } else { server_thread = 0; } +#else + create_thread = false; + server_thread = Thread::get_main_id(); +#endif + RSG::threaded = create_thread; - RSG::threaded = p_create_thread; RSG::canvas = memnew(RendererCanvasCull); RSG::viewport = memnew(RendererViewport); RendererSceneCull *sr = memnew(RendererSceneCull); diff --git a/servers/rendering/rendering_server_default.h b/servers/rendering/rendering_server_default.h index 11ec670280..e8b20692f0 100644 --- a/servers/rendering/rendering_server_default.h +++ b/servers/rendering/rendering_server_default.h @@ -78,7 +78,7 @@ class RenderingServerDefault : public RenderingServer { static void _thread_callback(void *_instance); void _thread_loop(); - Thread::ID server_thread; + Thread::ID server_thread = 0; SafeFlag exit; Thread thread; SafeFlag draw_thread_up; diff --git a/tests/scene/test_audio_stream_wav.h b/tests/scene/test_audio_stream_wav.h index e36f049136..ed1697929e 100644 --- a/tests/scene/test_audio_stream_wav.h +++ b/tests/scene/test_audio_stream_wav.h @@ -148,7 +148,7 @@ void run_test(String file_name, AudioStreamWAV::Format data_format, bool stereo, Ref<FileAccess> wav_file = FileAccess::open(save_path, FileAccess::READ, &error); REQUIRE(error == OK); -#if TOOLS_ENABLED +#ifdef TOOLS_ENABLED // The WAV importer can be used if enabled to check that the saved file is valid. Ref<ResourceImporterWAV> wav_importer = memnew(ResourceImporterWAV); diff --git a/tests/scene/test_node_2d.h b/tests/scene/test_node_2d.h index 7e93c77e22..8cf6408438 100644 --- a/tests/scene/test_node_2d.h +++ b/tests/scene/test_node_2d.h @@ -32,6 +32,7 @@ #define TEST_NODE_2D_H #include "scene/2d/node_2d.h" +#include "scene/main/window.h" #include "tests/test_macros.h" @@ -56,6 +57,33 @@ TEST_CASE("[SceneTree][Node2D]") { memdelete(test_child); memdelete(test_node); } + + SUBCASE("[Node2D][Global Transform] Global Transform should be correct after inserting node from detached tree into SceneTree.") { // GH-86841 + Node2D *main = memnew(Node2D); + Node2D *outer = memnew(Node2D); + Node2D *inner = memnew(Node2D); + SceneTree::get_singleton()->get_root()->add_child(main); + + main->set_position(Point2(100, 100)); + outer->set_position(Point2(10, 0)); + inner->set_position(Point2(0, 10)); + + outer->add_child(inner); + // `inner` is still detached. + CHECK_EQ(inner->get_global_position(), Point2(10, 10)); + + main->add_child(outer); + // `inner` is in scene tree. + CHECK_EQ(inner->get_global_position(), Point2(110, 110)); + + main->remove_child(outer); + // `inner` is detached again. + CHECK_EQ(inner->get_global_position(), Point2(10, 10)); + + memdelete(inner); + memdelete(outer); + memdelete(main); + } } } // namespace TestNode2D |