diff options
185 files changed, 12369 insertions, 2465 deletions
diff --git a/.github/workflows/static_checks.yml b/.github/workflows/static_checks.yml index 086be5e015..f35206ab57 100644 --- a/.github/workflows/static_checks.yml +++ b/.github/workflows/static_checks.yml @@ -102,9 +102,9 @@ jobs: fi - name: Spell checks via codespell - if: github.event_name == 'pull_request' + if: github.event_name == 'pull_request' && env.CHANGED_FILES != '' uses: codespell-project/actions-codespell@v1 with: - skip: "*.desktop,*.gen.*,*.po,*.pot,*.rc,./AUTHORS.md,./COPYRIGHT.txt,./DONORS.md,./core/input/gamecontrollerdb.txt,./core/string/locales.h,./editor/project_converter_3_to_4.cpp,./misc/scripts/codespell.sh,./platform/android/java/lib/src/com,./platform/web/node_modules,./platform/web/package-lock.json" + skip: "./bin,./thirdparty,*.desktop,*.gen.*,*.po,*.pot,*.rc,./AUTHORS.md,./COPYRIGHT.txt,./DONORS.md,./core/input/gamecontrollerdb.txt,./core/string/locales.h,./editor/project_converter_3_to_4.cpp,./misc/scripts/codespell.sh,./platform/android/java/lib/src/com,./platform/web/node_modules,./platform/web/package-lock.json" ignore_words_list: "curvelinear,doubleclick,expct,findn,gird,hel,inout,lod,nd,numer,ot,te,vai" path: ${{ env.CHANGED_FILES }} diff --git a/SConstruct b/SConstruct index e5421b7887..f4d27a2134 100644 --- a/SConstruct +++ b/SConstruct @@ -241,7 +241,8 @@ opts.Add(BoolVariable("builtin_miniupnpc", "Use the built-in miniupnpc library", opts.Add(BoolVariable("builtin_pcre2", "Use the built-in PCRE2 library", True)) opts.Add(BoolVariable("builtin_pcre2_with_jit", "Use JIT compiler for the built-in PCRE2 library", True)) opts.Add(BoolVariable("builtin_recastnavigation", "Use the built-in Recast navigation library", True)) -opts.Add(BoolVariable("builtin_rvo2", "Use the built-in RVO2 library", True)) +opts.Add(BoolVariable("builtin_rvo2_2d", "Use the built-in RVO2 2D library", True)) +opts.Add(BoolVariable("builtin_rvo2_3d", "Use the built-in RVO2 3D library", True)) opts.Add(BoolVariable("builtin_squish", "Use the built-in squish library", True)) opts.Add(BoolVariable("builtin_xatlas", "Use the built-in xatlas library", True)) opts.Add(BoolVariable("builtin_zlib", "Use the built-in zlib library", True)) diff --git a/core/core_constants.cpp b/core/core_constants.cpp index d88dda6609..2332bc235b 100644 --- a/core/core_constants.cpp +++ b/core/core_constants.cpp @@ -645,6 +645,7 @@ void register_global_constants() { BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_LAYERS_3D_RENDER); BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_LAYERS_3D_PHYSICS); BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_LAYERS_3D_NAVIGATION); + BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_LAYERS_AVOIDANCE); BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_FILE); BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_DIR); @@ -704,6 +705,7 @@ void register_global_constants() { BIND_CORE_BITFIELD_FLAG(PROPERTY_USAGE_EDITOR_INSTANTIATE_OBJECT); BIND_CORE_BITFIELD_FLAG(PROPERTY_USAGE_EDITOR_BASIC_SETTING); BIND_CORE_BITFIELD_FLAG(PROPERTY_USAGE_READ_ONLY); + BIND_CORE_BITFIELD_FLAG(PROPERTY_USAGE_SECRET); BIND_CORE_BITFIELD_FLAG(PROPERTY_USAGE_DEFAULT); BIND_CORE_BITFIELD_FLAG(PROPERTY_USAGE_NO_EDITOR); diff --git a/core/crypto/SCsub b/core/crypto/SCsub index 9b7953fdc5..ac79e10d19 100644 --- a/core/crypto/SCsub +++ b/core/crypto/SCsub @@ -20,12 +20,13 @@ if is_builtin or not has_module: # Only if the module is not enabled, we must compile here the required sources # to make a "light" build with only the necessary mbedtls files. if not has_module: - env_thirdparty = env_crypto.Clone() - env_thirdparty.disable_warnings() - # Custom config file - env_thirdparty.Append( + # Minimal mbedTLS config file + env_crypto.Append( CPPDEFINES=[("MBEDTLS_CONFIG_FILE", '\\"thirdparty/mbedtls/include/godot_core_mbedtls_config.h\\"')] ) + # Build minimal mbedTLS library (MD5/SHA/Base64/AES). + env_thirdparty = env_crypto.Clone() + env_thirdparty.disable_warnings() thirdparty_mbedtls_dir = "#thirdparty/mbedtls/library/" thirdparty_mbedtls_sources = [ "aes.c", @@ -40,8 +41,16 @@ if not has_module: ] thirdparty_mbedtls_sources = [thirdparty_mbedtls_dir + file for file in thirdparty_mbedtls_sources] env_thirdparty.add_source_files(thirdparty_obj, thirdparty_mbedtls_sources) + # Needed to force rebuilding the library when the configuration file is updated. + env_thirdparty.Depends(thirdparty_obj, "#thirdparty/mbedtls/include/godot_core_mbedtls_config.h") env.core_sources += thirdparty_obj - +elif is_builtin: + # Module mbedTLS config file + env_crypto.Append( + CPPDEFINES=[("MBEDTLS_CONFIG_FILE", '\\"thirdparty/mbedtls/include/godot_module_mbedtls_config.h\\"')] + ) + # Needed to force rebuilding the core files when the configuration file is updated. + thirdparty_obj = ["#thirdparty/mbedtls/include/godot_module_mbedtls_config.h"] # Godot source files diff --git a/core/io/resource_format_binary.cpp b/core/io/resource_format_binary.cpp index 38f41d645c..b4da314e96 100644 --- a/core/io/resource_format_binary.cpp +++ b/core/io/resource_format_binary.cpp @@ -445,13 +445,12 @@ Error ResourceLoaderBinary::parse_variant(Variant &r_v) { WARN_PRINT("Broken external resource! (index out of size)"); r_v = Variant(); } else { - if (external_resources[erindex].cache.is_null()) { - //cache not here yet, wait for it? - if (use_sub_threads) { - Error err; - external_resources.write[erindex].cache = ResourceLoader::load_threaded_get(external_resources[erindex].path, &err); - - if (err != OK || external_resources[erindex].cache.is_null()) { + Ref<ResourceLoader::LoadToken> &load_token = external_resources.write[erindex].load_token; + if (load_token.is_valid()) { // If not valid, it's OK since then we know this load accepts broken dependencies. + Error err; + Ref<Resource> res = ResourceLoader::_load_complete(*load_token.ptr(), &err); + if (res.is_null()) { + if (!ResourceLoader::is_cleaning_tasks()) { if (!ResourceLoader::get_abort_on_missing_resources()) { ResourceLoader::notify_dependency_error(local_path, external_resources[erindex].path, external_resources[erindex].type); } else { @@ -459,12 +458,11 @@ Error ResourceLoaderBinary::parse_variant(Variant &r_v) { ERR_FAIL_V_MSG(error, "Can't load dependency: " + external_resources[erindex].path + "."); } } + } else { + r_v = res; } } - - r_v = external_resources[erindex].cache; } - } break; default: { ERR_FAIL_V(ERR_FILE_CORRUPT); @@ -684,28 +682,13 @@ Error ResourceLoaderBinary::load() { } external_resources.write[i].path = path; //remap happens here, not on load because on load it can actually be used for filesystem dock resource remap - - if (!use_sub_threads) { - external_resources.write[i].cache = ResourceLoader::load(path, external_resources[i].type); - - if (external_resources[i].cache.is_null()) { - if (!ResourceLoader::get_abort_on_missing_resources()) { - ResourceLoader::notify_dependency_error(local_path, path, external_resources[i].type); - } else { - error = ERR_FILE_MISSING_DEPENDENCIES; - ERR_FAIL_V_MSG(error, "Can't load dependency: " + path + "."); - } - } - - } else { - Error err = ResourceLoader::load_threaded_request(path, external_resources[i].type, use_sub_threads, ResourceFormatLoader::CACHE_MODE_REUSE, local_path); - if (err != OK) { - if (!ResourceLoader::get_abort_on_missing_resources()) { - ResourceLoader::notify_dependency_error(local_path, path, external_resources[i].type); - } else { - error = ERR_FILE_MISSING_DEPENDENCIES; - ERR_FAIL_V_MSG(error, "Can't load dependency: " + path + "."); - } + external_resources.write[i].load_token = ResourceLoader::_load_start(path, external_resources[i].type, use_sub_threads ? ResourceLoader::LOAD_THREAD_DISTRIBUTE : ResourceLoader::LOAD_THREAD_FROM_CURRENT, ResourceFormatLoader::CACHE_MODE_REUSE); + if (!external_resources[i].load_token.is_valid()) { + if (!ResourceLoader::get_abort_on_missing_resources()) { + ResourceLoader::notify_dependency_error(local_path, path, external_resources[i].type); + } else { + error = ERR_FILE_MISSING_DEPENDENCIES; + ERR_FAIL_V_MSG(error, "Can't load dependency: " + path + "."); } } } diff --git a/core/io/resource_format_binary.h b/core/io/resource_format_binary.h index add7cdf297..30f1664983 100644 --- a/core/io/resource_format_binary.h +++ b/core/io/resource_format_binary.h @@ -60,7 +60,7 @@ class ResourceLoaderBinary { String path; String type; ResourceUID::ID uid = ResourceUID::INVALID_ID; - Ref<Resource> cache; + Ref<ResourceLoader::LoadToken> load_token; }; bool using_named_scene_ids = false; diff --git a/core/io/resource_loader.cpp b/core/io/resource_loader.cpp index 9af3a7daed..a27341dd2c 100644 --- a/core/io/resource_loader.cpp +++ b/core/io/resource_loader.cpp @@ -202,20 +202,71 @@ void ResourceFormatLoader::_bind_methods() { /////////////////////////////////// +// This should be robust enough to be called redundantly without issues. +void ResourceLoader::LoadToken::clear() { + thread_load_mutex.lock(); + + WorkerThreadPool::TaskID task_to_await = 0; + + if (!local_path.is_empty()) { // Empty is used for the special case where the load task is not registered. + DEV_ASSERT(thread_load_tasks.has(local_path)); + ThreadLoadTask &load_task = thread_load_tasks[local_path]; + if (!load_task.awaited) { + task_to_await = load_task.task_id; + load_task.awaited = true; + } + thread_load_tasks.erase(local_path); + local_path.clear(); + } + + if (!user_path.is_empty()) { + DEV_ASSERT(user_load_tokens.has(user_path)); + user_load_tokens.erase(user_path); + user_path.clear(); + } + + thread_load_mutex.unlock(); + + // If task is unused, await it here, locally, now the token data is consistent. + if (task_to_await) { + WorkerThreadPool::get_singleton()->wait_for_task_completion(task_to_await); + } +} + +ResourceLoader::LoadToken::~LoadToken() { + clear(); +} + Ref<Resource> ResourceLoader::_load(const String &p_path, const String &p_original_path, const String &p_type_hint, ResourceFormatLoader::CacheMode p_cache_mode, Error *r_error, bool p_use_sub_threads, float *r_progress) { - bool found = false; + load_nesting++; + if (load_paths_stack.size()) { + thread_load_mutex.lock(); + HashMap<String, ThreadLoadTask>::Iterator E = thread_load_tasks.find(load_paths_stack[load_paths_stack.size() - 1]); + if (E) { + E->value.sub_tasks.insert(p_path); + } + thread_load_mutex.unlock(); + } + load_paths_stack.push_back(p_path); // Try all loaders and pick the first match for the type hint + bool found = false; + Ref<Resource> res; for (int i = 0; i < loader_count; i++) { if (!loader[i]->recognize_path(p_path, p_type_hint)) { continue; } found = true; - Ref<Resource> res = loader[i]->load(p_path, !p_original_path.is_empty() ? p_original_path : p_path, r_error, p_use_sub_threads, r_progress, p_cache_mode); - if (res.is_null()) { - continue; + res = loader[i]->load(p_path, !p_original_path.is_empty() ? p_original_path : p_path, r_error, p_use_sub_threads, r_progress, p_cache_mode); + if (!res.is_null()) { + break; } + } + + load_paths_stack.resize(load_paths_stack.size() - 1); + load_nesting--; + if (!res.is_null()) { return res; } @@ -232,47 +283,60 @@ Ref<Resource> ResourceLoader::_load(const String &p_path, const String &p_origin void ResourceLoader::_thread_load_function(void *p_userdata) { ThreadLoadTask &load_task = *(ThreadLoadTask *)p_userdata; - load_task.loader_id = Thread::get_caller_id(); - if (load_task.cond_var) { - //this is an actual thread, so wait for Ok from semaphore - thread_load_semaphore->wait(); //wait until its ok to start loading + thread_load_mutex.lock(); + caller_task_id = load_task.task_id; + if (cleaning_tasks) { + load_task.status = THREAD_LOAD_FAILED; + thread_load_mutex.unlock(); + return; } - load_task.resource = _load(load_task.remapped_path, load_task.remapped_path != load_task.local_path ? load_task.local_path : String(), load_task.type_hint, load_task.cache_mode, &load_task.error, load_task.use_sub_threads, &load_task.progress); + thread_load_mutex.unlock(); - load_task.progress = 1.0; //it was fully loaded at this point, so force progress to 1.0 + // Thread-safe either if it's the current thread or a brand new one. + CallQueue *mq_override = nullptr; + if (load_nesting == 0) { + if (!load_task.dependent_path.is_empty()) { + load_paths_stack.push_back(load_task.dependent_path); + } + if (!Thread::is_main_thread()) { + mq_override = memnew(CallQueue); + MessageQueue::set_thread_singleton_override(mq_override); + } + } else { + DEV_ASSERT(load_task.dependent_path.is_empty()); + } + // -- - thread_load_mutex->lock(); + Ref<Resource> res = _load(load_task.remapped_path, load_task.remapped_path != load_task.local_path ? load_task.local_path : String(), load_task.type_hint, load_task.cache_mode, &load_task.error, load_task.use_sub_threads, &load_task.progress); + + thread_load_mutex.lock(); + + load_task.resource = res; + + load_task.progress = 1.0; //it was fully loaded at this point, so force progress to 1.0 if (load_task.error != OK) { load_task.status = THREAD_LOAD_FAILED; } else { load_task.status = THREAD_LOAD_LOADED; } - if (load_task.cond_var) { - if (load_task.start_next && thread_waiting_count > 0) { - thread_waiting_count--; - //thread loading count remains constant, this ends but another one begins - thread_load_semaphore->post(); - } else { - thread_loading_count--; //no threads waiting, just reduce loading count - } - - print_lt("END: load count: " + itos(thread_loading_count) + " / wait count: " + itos(thread_waiting_count) + " / suspended count: " + itos(thread_suspended_count) + " / active: " + itos(thread_loading_count - thread_suspended_count)); + if (load_task.cond_var) { load_task.cond_var->notify_all(); memdelete(load_task.cond_var); load_task.cond_var = nullptr; } if (load_task.resource.is_valid()) { - load_task.resource->set_path(load_task.local_path); + if (load_task.cache_mode != ResourceFormatLoader::CACHE_MODE_IGNORE) { + load_task.resource->set_path(load_task.local_path); + } if (load_task.xl_remapped) { load_task.resource->set_as_translation_remapped(true); } #ifdef TOOLS_ENABLED - load_task.resource->set_edited(false); if (timestamp_on_load) { uint64_t mt = FileAccess::get_modified_time(load_task.remapped_path); @@ -286,7 +350,12 @@ void ResourceLoader::_thread_load_function(void *p_userdata) { } } - thread_load_mutex->unlock(); + thread_load_mutex.unlock(); + + if (load_nesting == 0 && mq_override) { + memdelete(mq_override); + MessageQueue::set_thread_singleton_override(nullptr); + } } static String _validate_local_path(const String &p_path) { @@ -299,91 +368,127 @@ static String _validate_local_path(const String &p_path) { return ProjectSettings::get_singleton()->localize_path(p_path); } } -Error ResourceLoader::load_threaded_request(const String &p_path, const String &p_type_hint, bool p_use_sub_threads, ResourceFormatLoader::CacheMode p_cache_mode, const String &p_source_resource) { - String local_path = _validate_local_path(p_path); - thread_load_mutex->lock(); +Error ResourceLoader::load_threaded_request(const String &p_path, const String &p_type_hint, bool p_use_sub_threads, ResourceFormatLoader::CacheMode p_cache_mode) { + thread_load_mutex.lock(); + if (user_load_tokens.has(p_path)) { + print_verbose("load_threaded_request(): Another threaded load for resource path '" + p_path + "' has been initiated. Not an error."); + user_load_tokens[p_path]->reference(); // Additional request. + thread_load_mutex.unlock(); + return OK; + } + user_load_tokens[p_path] = nullptr; + thread_load_mutex.unlock(); + + Ref<ResourceLoader::LoadToken> token = _load_start(p_path, p_type_hint, p_use_sub_threads ? LOAD_THREAD_DISTRIBUTE : LOAD_THREAD_SPAWN_SINGLE, p_cache_mode); + if (token.is_valid()) { + thread_load_mutex.lock(); + token->user_path = p_path; + token->reference(); // First request. + user_load_tokens[p_path] = token.ptr(); + print_lt("REQUEST: user load tokens: " + itos(user_load_tokens.size())); + thread_load_mutex.unlock(); + return OK; + } else { + return FAILED; + } +} - if (!p_source_resource.is_empty()) { - //must be loading from this resource - if (!thread_load_tasks.has(p_source_resource)) { - thread_load_mutex->unlock(); - ERR_FAIL_V_MSG(ERR_INVALID_PARAMETER, "There is no thread loading source resource '" + p_source_resource + "'."); - } - //must not be already added as s sub tasks - if (thread_load_tasks[p_source_resource].sub_tasks.has(local_path)) { - thread_load_mutex->unlock(); - ERR_FAIL_V_MSG(ERR_INVALID_PARAMETER, "Thread loading source resource '" + p_source_resource + "' already is loading '" + local_path + "'."); - } +Ref<Resource> ResourceLoader::load(const String &p_path, const String &p_type_hint, ResourceFormatLoader::CacheMode p_cache_mode, Error *r_error) { + if (r_error) { + *r_error = OK; } - if (thread_load_tasks.has(local_path)) { - thread_load_tasks[local_path].requests++; - if (!p_source_resource.is_empty()) { - thread_load_tasks[p_source_resource].sub_tasks.insert(local_path); + Ref<LoadToken> load_token = _load_start(p_path, p_type_hint, LOAD_THREAD_FROM_CURRENT, p_cache_mode); + if (!load_token.is_valid()) { + if (r_error) { + *r_error = FAILED; } - thread_load_mutex->unlock(); - return OK; + return Ref<Resource>(); } - { - //create load task - - ThreadLoadTask load_task; + Ref<Resource> res = _load_complete(*load_token.ptr(), r_error); + return res; +} - load_task.requests = 1; - load_task.remapped_path = _path_remap(local_path, &load_task.xl_remapped); - load_task.local_path = local_path; - load_task.type_hint = p_type_hint; - load_task.cache_mode = p_cache_mode; - load_task.use_sub_threads = p_use_sub_threads; +Ref<ResourceLoader::LoadToken> ResourceLoader::_load_start(const String &p_path, const String &p_type_hint, LoadThreadMode p_thread_mode, ResourceFormatLoader::CacheMode p_cache_mode) { + String local_path = _validate_local_path(p_path); - { //must check if resource is already loaded before attempting to load it in a thread + Ref<LoadToken> load_token; + bool must_not_register = false; + ThreadLoadTask unregistered_load_task; // Once set, must be valid up to the call to do the load. + ThreadLoadTask *load_task_ptr = nullptr; + bool run_on_current_thread = false; + { + MutexLock thread_load_lock(thread_load_mutex); - if (load_task.loader_id == Thread::get_caller_id()) { - thread_load_mutex->unlock(); - ERR_FAIL_V_MSG(ERR_INVALID_PARAMETER, "Attempted to load a resource already being loaded from this thread, cyclic reference?"); + if (thread_load_tasks.has(local_path)) { + load_token = Ref<LoadToken>(thread_load_tasks[local_path].load_token); + if (!load_token.is_valid()) { + // The token is dying (reached 0 on another thread). + // Ensure it's killed now so the path can be safely reused right away. + thread_load_tasks[local_path].load_token->clear(); + } else { + if (p_cache_mode != ResourceFormatLoader::CACHE_MODE_IGNORE) { + return load_token; + } } + } - Ref<Resource> existing = ResourceCache::get_ref(local_path); + load_token.instantiate(); + load_token->local_path = local_path; - if (existing.is_valid()) { - //referencing is fine - load_task.resource = existing; - load_task.status = THREAD_LOAD_LOADED; - load_task.progress = 1.0; + //create load task + { + ThreadLoadTask load_task; + + load_task.remapped_path = _path_remap(local_path, &load_task.xl_remapped); + load_task.load_token = load_token.ptr(); + load_task.local_path = local_path; + load_task.type_hint = p_type_hint; + load_task.cache_mode = p_cache_mode; + load_task.use_sub_threads = p_thread_mode == LOAD_THREAD_DISTRIBUTE; + if (p_cache_mode != ResourceFormatLoader::CACHE_MODE_IGNORE) { + Ref<Resource> existing = ResourceCache::get_ref(local_path); + if (existing.is_valid()) { + //referencing is fine + load_task.resource = existing; + load_task.status = THREAD_LOAD_LOADED; + load_task.progress = 1.0; + thread_load_tasks[local_path] = load_task; + return load_token; + } } - } - - if (!p_source_resource.is_empty()) { - thread_load_tasks[p_source_resource].sub_tasks.insert(local_path); - } - thread_load_tasks[local_path] = load_task; - } + // If we want to ignore cache, but there's another task loading it, we can't add this one to the map and we also have to finish unconditionally synchronously. + must_not_register = thread_load_tasks.has(local_path) && p_cache_mode == ResourceFormatLoader::CACHE_MODE_IGNORE; + if (must_not_register) { + load_token->local_path.clear(); + unregistered_load_task = load_task; + } else { + thread_load_tasks[local_path] = load_task; + } - ThreadLoadTask &load_task = thread_load_tasks[local_path]; + load_task_ptr = must_not_register ? &unregistered_load_task : &thread_load_tasks[local_path]; + } - if (load_task.resource.is_null()) { //needs to be loaded in thread + run_on_current_thread = must_not_register || p_thread_mode == LOAD_THREAD_FROM_CURRENT; - load_task.cond_var = memnew(ConditionVariable); - if (thread_loading_count < thread_load_max) { - thread_loading_count++; - thread_load_semaphore->post(); //we have free threads, so allow one + if (run_on_current_thread) { + load_task_ptr->thread_id = Thread::get_caller_id(); + if (must_not_register) { + load_token->res_if_unregistered = load_task_ptr->resource; + } } else { - thread_waiting_count++; + load_task_ptr->task_id = WorkerThreadPool::get_singleton()->add_native_task(&ResourceLoader::_thread_load_function, load_task_ptr); } - - print_lt("REQUEST: load count: " + itos(thread_loading_count) + " / wait count: " + itos(thread_waiting_count) + " / suspended count: " + itos(thread_suspended_count) + " / active: " + itos(thread_loading_count - thread_suspended_count)); - - load_task.thread = memnew(Thread); - load_task.thread->start(_thread_load_function, &thread_load_tasks[local_path]); - load_task.loader_id = load_task.thread->get_id(); } - thread_load_mutex->unlock(); + if (run_on_current_thread) { + _thread_load_function(load_task_ptr); + } - return OK; + return load_token; } float ResourceLoader::_dependency_get_progress(const String &p_path) { @@ -409,13 +514,22 @@ float ResourceLoader::_dependency_get_progress(const String &p_path) { } ResourceLoader::ThreadLoadStatus ResourceLoader::load_threaded_get_status(const String &p_path, float *r_progress) { - String local_path = _validate_local_path(p_path); + MutexLock thread_load_lock(thread_load_mutex); + + if (!user_load_tokens.has(p_path)) { + print_verbose("load_threaded_get_status(): No threaded load for resource path '" + p_path + "' has been initiated or its result has already been collected."); + return THREAD_LOAD_INVALID_RESOURCE; + } - thread_load_mutex->lock(); + String local_path = _validate_local_path(p_path); if (!thread_load_tasks.has(local_path)) { - thread_load_mutex->unlock(); +#ifdef DEV_ENABLED + CRASH_NOW(); +#endif + // On non-dev, be defensive and at least avoid crashing (at this point at least). return THREAD_LOAD_INVALID_RESOURCE; } + ThreadLoadTask &load_task = thread_load_tasks[local_path]; ThreadLoadStatus status; status = load_task.status; @@ -423,198 +537,120 @@ ResourceLoader::ThreadLoadStatus ResourceLoader::load_threaded_get_status(const *r_progress = _dependency_get_progress(local_path); } - thread_load_mutex->unlock(); - return status; } Ref<Resource> ResourceLoader::load_threaded_get(const String &p_path, Error *r_error) { - String local_path = _validate_local_path(p_path); - - MutexLock thread_load_lock(*thread_load_mutex); - if (!thread_load_tasks.has(local_path)) { - if (r_error) { - *r_error = ERR_INVALID_PARAMETER; - } - return Ref<Resource>(); + if (r_error) { + *r_error = OK; } - ThreadLoadTask &load_task = thread_load_tasks[local_path]; + Ref<Resource> res; + { + MutexLock thread_load_lock(thread_load_mutex); - if (load_task.status == THREAD_LOAD_IN_PROGRESS) { - if (load_task.loader_id == Thread::get_caller_id()) { - // Load is in progress, but it's precisely this thread the one in charge. - // That means this is a cyclic load. + if (!user_load_tokens.has(p_path)) { + print_verbose("load_threaded_get(): No threaded load for resource path '" + p_path + "' has been initiated or its result has already been collected."); if (r_error) { - *r_error = ERR_BUSY; + *r_error = ERR_INVALID_PARAMETER; } return Ref<Resource>(); - } else if (!load_task.cond_var) { - // Load is in progress, but a condition variable was never created for it. - // That happens when a load has been initiated with subthreads disabled, - // but now another load thread needs to interact with this one (either - // because of subthreads being used this time, or because it's simply a - // threaded load running on a different thread). - // Since we want to be notified when the load ends, we must create the - // condition variable now. - load_task.cond_var = memnew(ConditionVariable); } - } - - //cond var still exists, meaning it's still loading, request poll - if (load_task.cond_var) { - { - // As we got a cond var, this means we are going to have to wait - // until the sub-resource is done loading - // - // As this thread will become 'blocked' we should "exchange" its - // active status with a waiting one, to ensure load continues. - // - // This ensures loading is never blocked and that is also within - // the maximum number of active threads. - - if (thread_waiting_count > 0) { - thread_waiting_count--; - thread_loading_count++; - thread_load_semaphore->post(); - - load_task.start_next = false; //do not start next since we are doing it here - } - - thread_suspended_count++; - - print_lt("GET: load count: " + itos(thread_loading_count) + " / wait count: " + itos(thread_waiting_count) + " / suspended count: " + itos(thread_suspended_count) + " / active: " + itos(thread_loading_count - thread_suspended_count)); - } - - bool still_valid = true; - bool was_thread = load_task.thread; - do { - load_task.cond_var->wait(thread_load_lock); - if (!thread_load_tasks.has(local_path)) { //may have been erased during unlock and this was always an invalid call - still_valid = false; - break; - } - } while (load_task.cond_var); // In case of spurious wakeup. - if (was_thread) { - thread_suspended_count--; - } - - if (!still_valid) { + LoadToken *load_token = user_load_tokens[p_path]; + if (!load_token) { + // This happens if requested from one thread and rapidly querying from another. if (r_error) { - *r_error = ERR_INVALID_PARAMETER; + *r_error = ERR_BUSY; } return Ref<Resource>(); } + res = _load_complete_inner(*load_token, r_error, thread_load_lock); + if (load_token->unreference()) { + memdelete(load_token); + } } - Ref<Resource> resource = load_task.resource; - if (r_error) { - *r_error = load_task.error; - } - - load_task.requests--; + print_lt("GET: user load tokens: " + itos(user_load_tokens.size())); - if (load_task.requests == 0) { - if (load_task.thread) { //thread may not have been used - load_task.thread->wait_to_finish(); - memdelete(load_task.thread); - } - thread_load_tasks.erase(local_path); - } + return res; +} - return resource; +Ref<Resource> ResourceLoader::_load_complete(LoadToken &p_load_token, Error *r_error) { + MutexLock thread_load_lock(thread_load_mutex); + return _load_complete_inner(p_load_token, r_error, thread_load_lock); } -Ref<Resource> ResourceLoader::load(const String &p_path, const String &p_type_hint, ResourceFormatLoader::CacheMode p_cache_mode, Error *r_error) { +Ref<Resource> ResourceLoader::_load_complete_inner(LoadToken &p_load_token, Error *r_error, MutexLock<SafeBinaryMutex<BINARY_MUTEX_TAG>> &p_thread_load_lock) { if (r_error) { - *r_error = ERR_CANT_OPEN; + *r_error = OK; } - String local_path = _validate_local_path(p_path); + if (!p_load_token.local_path.is_empty()) { + if (!thread_load_tasks.has(p_load_token.local_path)) { +#ifdef DEV_ENABLED + CRASH_NOW(); +#endif + // On non-dev, be defensive and at least avoid crashing (at this point at least). + if (r_error) { + *r_error = ERR_BUG; + } + return Ref<Resource>(); + } - if (p_cache_mode != ResourceFormatLoader::CACHE_MODE_IGNORE) { - thread_load_mutex->lock(); + ThreadLoadTask &load_task = thread_load_tasks[p_load_token.local_path]; - //Is it already being loaded? poll until done - if (thread_load_tasks.has(local_path)) { - Error err = load_threaded_request(p_path, p_type_hint); - if (err != OK) { + if (load_task.status == THREAD_LOAD_IN_PROGRESS) { + DEV_ASSERT((load_task.task_id == 0) != (load_task.thread_id == 0)); + + if ((load_task.task_id != 0 && load_task.task_id == caller_task_id) || + (load_task.thread_id != 0 && load_task.thread_id == Thread::get_caller_id())) { + // Load is in progress, but it's precisely this thread the one in charge. + // That means this is a cyclic load. if (r_error) { - *r_error = err; + *r_error = ERR_BUSY; } - thread_load_mutex->unlock(); return Ref<Resource>(); } - thread_load_mutex->unlock(); - return load_threaded_get(p_path, r_error); - } - - //Is it cached? - - Ref<Resource> existing = ResourceCache::get_ref(local_path); - - if (existing.is_valid()) { - thread_load_mutex->unlock(); - - if (r_error) { - *r_error = OK; + if (load_task.task_id != 0 && !load_task.awaited) { + // Loading thread is in the worker pool and still not awaited. + load_task.awaited = true; + thread_load_mutex.unlock(); + WorkerThreadPool::get_singleton()->wait_for_task_completion(load_task.task_id); + thread_load_mutex.lock(); + } else { + // Loading thread is main or user thread, or in the worker pool, but already awaited by some other thread. + if (!load_task.cond_var) { + load_task.cond_var = memnew(ConditionVariable); + } + do { + load_task.cond_var->wait(p_thread_load_lock); + DEV_ASSERT(thread_load_tasks.has(p_load_token.local_path) && p_load_token.get_reference_count()); + } while (load_task.cond_var); } - - return existing; //use cached } - //load using task (but this thread) - ThreadLoadTask load_task; - - load_task.requests = 1; - load_task.local_path = local_path; - load_task.remapped_path = _path_remap(local_path, &load_task.xl_remapped); - load_task.type_hint = p_type_hint; - load_task.cache_mode = p_cache_mode; //ignore - load_task.loader_id = Thread::get_caller_id(); - - thread_load_tasks[local_path] = load_task; - - thread_load_mutex->unlock(); - - _thread_load_function(&thread_load_tasks[local_path]); - - return load_threaded_get(p_path, r_error); - - } else { - bool xl_remapped = false; - String path = _path_remap(local_path, &xl_remapped); - - if (path.is_empty()) { - ERR_FAIL_V_MSG(Ref<Resource>(), "Remapping '" + local_path + "' failed."); - } - - print_verbose("Loading resource: " + path); - float p; - Ref<Resource> res = _load(path, local_path, p_type_hint, p_cache_mode, r_error, false, &p); - - if (res.is_null()) { - print_verbose("Failed loading resource: " + path); - return Ref<Resource>(); + if (cleaning_tasks) { + load_task.resource = Ref<Resource>(); + load_task.error = FAILED; } - if (xl_remapped) { - res->set_as_translation_remapped(true); + Ref<Resource> resource = load_task.resource; + if (r_error) { + *r_error = load_task.error; } - -#ifdef TOOLS_ENABLED - - res->set_edited(false); - if (timestamp_on_load) { - uint64_t mt = FileAccess::get_modified_time(path); - //printf("mt %s: %lli\n",remapped_path.utf8().get_data(),mt); - res->set_last_modified_time(mt); + return resource; + } else { + // Special case of an unregistered task. + // The resource should have been loaded by now. + Ref<Resource> resource = p_load_token.res_if_unregistered; + if (!resource.is_valid()) { + if (r_error) { + *r_error = FAILED; + } } -#endif - - return res; + return resource; } } @@ -958,32 +994,42 @@ void ResourceLoader::clear_translation_remaps() { } void ResourceLoader::clear_thread_load_tasks() { - thread_load_mutex->lock(); - - for (KeyValue<String, ResourceLoader::ThreadLoadTask> &E : thread_load_tasks) { - switch (E.value.status) { - case ResourceLoader::ThreadLoadStatus::THREAD_LOAD_LOADED: { - E.value.resource = Ref<Resource>(); - } break; - - case ResourceLoader::ThreadLoadStatus::THREAD_LOAD_IN_PROGRESS: { - if (E.value.thread != nullptr) { - E.value.thread->wait_to_finish(); - memdelete(E.value.thread); - E.value.thread = nullptr; + // Bring the thing down as quickly as possible without causing deadlocks or leaks. + + thread_load_mutex.lock(); + cleaning_tasks = true; + + while (true) { + bool none_running = true; + if (thread_load_tasks.size()) { + for (KeyValue<String, ResourceLoader::ThreadLoadTask> &E : thread_load_tasks) { + if (E.value.status == THREAD_LOAD_IN_PROGRESS) { + if (E.value.cond_var) { + E.value.cond_var->notify_all(); + memdelete(E.value.cond_var); + E.value.cond_var = nullptr; + } + none_running = false; } - E.value.resource = Ref<Resource>(); - } break; - - case ResourceLoader::ThreadLoadStatus::THREAD_LOAD_FAILED: - default: { - // do nothing } } + if (none_running) { + break; + } + thread_load_mutex.unlock(); + OS::get_singleton()->delay_usec(1000); + thread_load_mutex.lock(); + } + + for (KeyValue<String, LoadToken *> &E : user_load_tokens) { + memdelete(E.value); } + user_load_tokens.clear(); + thread_load_tasks.clear(); - thread_load_mutex->unlock(); + cleaning_tasks = false; + thread_load_mutex.unlock(); } void ResourceLoader::load_path_remaps() { @@ -1080,20 +1126,14 @@ void ResourceLoader::remove_custom_loaders() { } } -void ResourceLoader::initialize() { - thread_load_mutex = memnew(SafeBinaryMutex<BINARY_MUTEX_TAG>); - thread_load_max = OS::get_singleton()->get_processor_count(); - thread_loading_count = 0; - thread_waiting_count = 0; - thread_suspended_count = 0; - thread_load_semaphore = memnew(Semaphore); +bool ResourceLoader::is_cleaning_tasks() { + MutexLock lock(thread_load_mutex); + return cleaning_tasks; } -void ResourceLoader::finalize() { - clear_thread_load_tasks(); - memdelete(thread_load_mutex); - memdelete(thread_load_semaphore); -} +void ResourceLoader::initialize() {} + +void ResourceLoader::finalize() {} ResourceLoadErrorNotify ResourceLoader::err_notify = nullptr; void *ResourceLoader::err_notify_ud = nullptr; @@ -1105,16 +1145,17 @@ bool ResourceLoader::create_missing_resources_if_class_unavailable = false; bool ResourceLoader::abort_on_missing_resource = true; bool ResourceLoader::timestamp_on_load = false; +thread_local int ResourceLoader::load_nesting = 0; +thread_local WorkerThreadPool::TaskID ResourceLoader::caller_task_id = 0; +thread_local Vector<String> ResourceLoader::load_paths_stack; + template <> thread_local uint32_t SafeBinaryMutex<ResourceLoader::BINARY_MUTEX_TAG>::count = 0; -SafeBinaryMutex<ResourceLoader::BINARY_MUTEX_TAG> *ResourceLoader::thread_load_mutex = nullptr; +SafeBinaryMutex<ResourceLoader::BINARY_MUTEX_TAG> ResourceLoader::thread_load_mutex; HashMap<String, ResourceLoader::ThreadLoadTask> ResourceLoader::thread_load_tasks; -Semaphore *ResourceLoader::thread_load_semaphore = nullptr; +bool ResourceLoader::cleaning_tasks = false; -int ResourceLoader::thread_loading_count = 0; -int ResourceLoader::thread_waiting_count = 0; -int ResourceLoader::thread_suspended_count = 0; -int ResourceLoader::thread_load_max = 0; +HashMap<String, ResourceLoader::LoadToken *> ResourceLoader::user_load_tokens; SelfList<Resource>::List ResourceLoader::remapped_list; HashMap<String, Vector<String>> ResourceLoader::translation_remaps; diff --git a/core/io/resource_loader.h b/core/io/resource_loader.h index 72c1f90653..ffe9d5de9a 100644 --- a/core/io/resource_loader.h +++ b/core/io/resource_loader.h @@ -34,6 +34,7 @@ #include "core/io/resource.h" #include "core/object/gdvirtual.gen.inc" #include "core/object/script_language.h" +#include "core/object/worker_thread_pool.h" #include "core/os/semaphore.h" #include "core/os/thread.h" @@ -107,9 +108,30 @@ public: THREAD_LOAD_LOADED }; + enum LoadThreadMode { + LOAD_THREAD_FROM_CURRENT, + LOAD_THREAD_SPAWN_SINGLE, + LOAD_THREAD_DISTRIBUTE, + }; + + struct LoadToken : public RefCounted { + String local_path; + String user_path; + Ref<Resource> res_if_unregistered; + + void clear(); + + virtual ~LoadToken(); + }; + static const int BINARY_MUTEX_TAG = 1; + static Ref<LoadToken> _load_start(const String &p_path, const String &p_type_hint, LoadThreadMode p_thread_mode, ResourceFormatLoader::CacheMode p_cache_mode); + static Ref<Resource> _load_complete(LoadToken &p_load_token, Error *r_error); + private: + static Ref<Resource> _load_complete_inner(LoadToken &p_load_token, Error *r_error, MutexLock<SafeBinaryMutex<BINARY_MUTEX_TAG>> &p_thread_load_lock); + static Ref<ResourceFormatLoader> loader[MAX_LOADERS]; static int loader_count; static bool timestamp_on_load; @@ -129,8 +151,7 @@ private: static SelfList<Resource>::List remapped_list; friend class ResourceFormatImporter; - friend class ResourceInteractiveLoader; - // Internal load function. + static Ref<Resource> _load(const String &p_path, const String &p_original_path, const String &p_type_hint, ResourceFormatLoader::CacheMode p_cache_mode, Error *r_error, bool p_use_sub_threads, float *r_progress); static ResourceLoadedCallback _loaded_callback; @@ -138,11 +159,14 @@ private: static Ref<ResourceFormatLoader> _find_custom_resource_format_loader(String path); struct ThreadLoadTask { - Thread *thread = nullptr; - Thread::ID loader_id = 0; - ConditionVariable *cond_var = nullptr; + WorkerThreadPool::TaskID task_id = 0; // Used if run on a worker thread from the pool. + Thread::ID thread_id = 0; // Used if running on an user thread (e.g., simple non-threaded load). + bool awaited = false; // If it's in the pool, this helps not awaiting from more than one dependent thread. + ConditionVariable *cond_var = nullptr; // In not in the worker pool or already awaiting, this is used as a secondary awaiting mechanism. + LoadToken *load_token = nullptr; String local_path; String remapped_path; + String dependent_path; String type_hint; float progress = 0.0; ThreadLoadStatus status = THREAD_LOAD_IN_PROGRESS; @@ -151,27 +175,29 @@ private: Ref<Resource> resource; bool xl_remapped = false; bool use_sub_threads = false; - bool start_next = true; - int requests = 0; HashSet<String> sub_tasks; }; static void _thread_load_function(void *p_userdata); - static SafeBinaryMutex<BINARY_MUTEX_TAG> *thread_load_mutex; + + static thread_local int load_nesting; + static thread_local WorkerThreadPool::TaskID caller_task_id; + static thread_local Vector<String> load_paths_stack; + static SafeBinaryMutex<BINARY_MUTEX_TAG> thread_load_mutex; static HashMap<String, ThreadLoadTask> thread_load_tasks; - static Semaphore *thread_load_semaphore; - static int thread_waiting_count; - static int thread_loading_count; - static int thread_suspended_count; - static int thread_load_max; + static bool cleaning_tasks; + + static HashMap<String, LoadToken *> user_load_tokens; static float _dependency_get_progress(const String &p_path); public: - static Error load_threaded_request(const String &p_path, const String &p_type_hint = "", bool p_use_sub_threads = false, ResourceFormatLoader::CacheMode p_cache_mode = ResourceFormatLoader::CACHE_MODE_REUSE, const String &p_source_resource = String()); + static Error load_threaded_request(const String &p_path, const String &p_type_hint = "", bool p_use_sub_threads = false, ResourceFormatLoader::CacheMode p_cache_mode = ResourceFormatLoader::CACHE_MODE_REUSE); static ThreadLoadStatus load_threaded_get_status(const String &p_path, float *r_progress = nullptr); static Ref<Resource> load_threaded_get(const String &p_path, Error *r_error = nullptr); + static bool is_within_load() { return load_nesting > 0; }; + static Ref<Resource> load(const String &p_path, const String &p_type_hint = "", ResourceFormatLoader::CacheMode p_cache_mode = ResourceFormatLoader::CACHE_MODE_REUSE, Error *r_error = nullptr); static bool exists(const String &p_path, const String &p_type_hint = ""); @@ -237,6 +263,8 @@ public: static void set_create_missing_resources_if_class_unavailable(bool p_enable); _FORCE_INLINE_ static bool is_creating_missing_resources_if_class_unavailable_enabled() { return create_missing_resources_if_class_unavailable; } + static bool is_cleaning_tasks(); + static void initialize(); static void finalize(); }; diff --git a/core/object/message_queue.cpp b/core/object/message_queue.cpp index cfd8904af6..55ea5f5ecd 100644 --- a/core/object/message_queue.cpp +++ b/core/object/message_queue.cpp @@ -35,14 +35,23 @@ #include "core/object/class_db.h" #include "core/object/script_language.h" +#define LOCK_MUTEX \ + if (this != MessageQueue::thread_singleton) { \ + mutex.lock(); \ + } + +#define UNLOCK_MUTEX \ + if (this != MessageQueue::thread_singleton) { \ + mutex.unlock(); \ + } + void CallQueue::_add_page() { - if (pages_used == page_messages.size()) { + if (pages_used == page_bytes.size()) { pages.push_back(allocator->alloc()); - page_messages.push_back(0); + page_bytes.push_back(0); } - page_messages[pages_used] = 0; + page_bytes[pages_used] = 0; pages_used++; - page_offset = 0; } Error CallQueue::push_callp(ObjectID p_id, const StringName &p_method, const Variant **p_args, int p_argcount, bool p_show_error) { @@ -66,15 +75,15 @@ Error CallQueue::push_callablep(const Callable &p_callable, const Variant **p_ar ERR_FAIL_COND_V_MSG(room_needed > uint32_t(PAGE_SIZE_BYTES), ERR_INVALID_PARAMETER, "Message is too large to fit on a page (" + itos(PAGE_SIZE_BYTES) + " bytes), consider passing less arguments."); - mutex.lock(); + LOCK_MUTEX; _ensure_first_page(); - if ((page_offset + room_needed) > uint32_t(PAGE_SIZE_BYTES)) { + if ((page_bytes[pages_used - 1] + room_needed) > uint32_t(PAGE_SIZE_BYTES)) { if (pages_used == max_pages) { ERR_PRINT("Failed method: " + p_callable + ". Message queue out of memory. " + error_text); statistics(); - mutex.unlock(); + UNLOCK_MUTEX; return ERR_OUT_OF_MEMORY; } _add_page(); @@ -82,7 +91,7 @@ Error CallQueue::push_callablep(const Callable &p_callable, const Variant **p_ar Page *page = pages[pages_used - 1]; - uint8_t *buffer_end = &page->data[page_offset]; + uint8_t *buffer_end = &page->data[page_bytes[pages_used - 1]]; Message *msg = memnew_placement(buffer_end, Message); msg->args = p_argcount; @@ -104,21 +113,20 @@ Error CallQueue::push_callablep(const Callable &p_callable, const Variant **p_ar *v = *p_args[i]; } - page_messages[pages_used - 1]++; - page_offset += room_needed; + page_bytes[pages_used - 1] += room_needed; - mutex.unlock(); + UNLOCK_MUTEX; return OK; } Error CallQueue::push_set(ObjectID p_id, const StringName &p_prop, const Variant &p_value) { - mutex.lock(); + LOCK_MUTEX; uint32_t room_needed = sizeof(Message) + sizeof(Variant); _ensure_first_page(); - if ((page_offset + room_needed) > uint32_t(PAGE_SIZE_BYTES)) { + if ((page_bytes[pages_used - 1] + room_needed) > uint32_t(PAGE_SIZE_BYTES)) { if (pages_used == max_pages) { String type; if (ObjectDB::get_instance(p_id)) { @@ -127,14 +135,14 @@ Error CallQueue::push_set(ObjectID p_id, const StringName &p_prop, const Variant ERR_PRINT("Failed set: " + type + ":" + p_prop + " target ID: " + itos(p_id) + ". Message queue out of memory. " + error_text); statistics(); - mutex.unlock(); + UNLOCK_MUTEX; return ERR_OUT_OF_MEMORY; } _add_page(); } Page *page = pages[pages_used - 1]; - uint8_t *buffer_end = &page->data[page_offset]; + uint8_t *buffer_end = &page->data[page_bytes[pages_used - 1]]; Message *msg = memnew_placement(buffer_end, Message); msg->args = 1; @@ -146,32 +154,31 @@ Error CallQueue::push_set(ObjectID p_id, const StringName &p_prop, const Variant Variant *v = memnew_placement(buffer_end, Variant); *v = p_value; - page_messages[pages_used - 1]++; - page_offset += room_needed; - mutex.unlock(); + page_bytes[pages_used - 1] += room_needed; + UNLOCK_MUTEX; return OK; } Error CallQueue::push_notification(ObjectID p_id, int p_notification) { ERR_FAIL_COND_V(p_notification < 0, ERR_INVALID_PARAMETER); - mutex.lock(); + LOCK_MUTEX; uint32_t room_needed = sizeof(Message); _ensure_first_page(); - if ((page_offset + room_needed) > uint32_t(PAGE_SIZE_BYTES)) { + if ((page_bytes[pages_used - 1] + room_needed) > uint32_t(PAGE_SIZE_BYTES)) { if (pages_used == max_pages) { ERR_PRINT("Failed notification: " + itos(p_notification) + " target ID: " + itos(p_id) + ". Message queue out of memory. " + error_text); statistics(); - mutex.unlock(); + UNLOCK_MUTEX; return ERR_OUT_OF_MEMORY; } _add_page(); } Page *page = pages[pages_used - 1]; - uint8_t *buffer_end = &page->data[page_offset]; + uint8_t *buffer_end = &page->data[page_bytes[pages_used - 1]]; Message *msg = memnew_placement(buffer_end, Message); @@ -180,9 +187,8 @@ Error CallQueue::push_notification(ObjectID p_id, int p_notification) { //msg->target; msg->notification = p_notification; - page_messages[pages_used - 1]++; - page_offset += room_needed; - mutex.unlock(); + page_bytes[pages_used - 1] += room_needed; + UNLOCK_MUTEX; return OK; } @@ -205,26 +211,77 @@ void CallQueue::_call_function(const Callable &p_callable, const Variant *p_args } Error CallQueue::flush() { - mutex.lock(); + LOCK_MUTEX; + + // Non-main threads are not meant to be flushed, but appended to the main one. + if (this != MessageQueue::main_singleton) { + if (pages.size() == 0) { + return OK; + } + + CallQueue *mq = MessageQueue::main_singleton; + DEV_ASSERT(!mq->allocator_is_custom && !allocator_is_custom); // Transferring pages is only safe if using the same alloator parameters. + + mq->mutex.lock(); + + // Here we're transferring the data from this queue to the main one. + // However, it's very unlikely big amounts of messages will be queued here, + // so PagedArray/Pool would be overkill. Also, in most cases the data will fit + // an already existing page of the main queue. + + // Let's see if our first (likely only) page fits the current target queue page. + uint32_t src_page = 0; + { + if (mq->pages_used) { + uint32_t dst_page = mq->pages_used - 1; + uint32_t dst_offset = mq->page_bytes[dst_page]; + if (dst_offset + page_bytes[0] < uint32_t(PAGE_SIZE_BYTES)) { + memcpy(mq->pages[dst_page] + dst_offset, pages[0], page_bytes[0]); + src_page++; + } + } + } + + // Any other possibly existing source page needs to be added. + + if (mq->pages_used + (pages_used - src_page) > mq->max_pages) { + ERR_PRINT("Failed appending thread queue. Message queue out of memory. " + mq->error_text); + mq->statistics(); + mq->mutex.unlock(); + return ERR_OUT_OF_MEMORY; + } + + for (; src_page < pages_used; src_page++) { + mq->_add_page(); + memcpy(mq->pages[mq->pages_used - 1], pages[src_page], page_bytes[src_page]); + mq->page_bytes[mq->pages_used - 1] = page_bytes[src_page]; + } + + mq->mutex.unlock(); + + page_bytes[0] = 0; + pages_used = 1; + + return OK; + } if (pages.size() == 0) { // Never allocated - mutex.unlock(); + UNLOCK_MUTEX; return OK; // Do nothing. } if (flushing) { - mutex.unlock(); + UNLOCK_MUTEX; return ERR_BUSY; } flushing = true; uint32_t i = 0; - uint32_t j = 0; uint32_t offset = 0; - while (i < pages_used && j < page_messages[i]) { + while (i < pages_used && offset < page_bytes[i]) { Page *page = pages[i]; //lock on each iteration, so a call can re-add itself to the message queue @@ -241,7 +298,7 @@ Error CallQueue::flush() { Object *target = message->callable.get_object(); - mutex.unlock(); + UNLOCK_MUTEX; switch (message->type & FLAG_MASK) { case TYPE_CALL: { @@ -272,35 +329,32 @@ Error CallQueue::flush() { message->~Message(); - mutex.lock(); - j++; - if (j == page_messages[i]) { - j = 0; + LOCK_MUTEX; + if (offset == page_bytes[i]) { i++; offset = 0; } } - page_messages[0] = 0; - page_offset = 0; + page_bytes[0] = 0; pages_used = 1; flushing = false; - mutex.unlock(); + UNLOCK_MUTEX; return OK; } void CallQueue::clear() { - mutex.lock(); + LOCK_MUTEX; if (pages.size() == 0) { - mutex.unlock(); + UNLOCK_MUTEX; return; // Nothing to clear. } for (uint32_t i = 0; i < pages_used; i++) { uint32_t offset = 0; - for (uint32_t j = 0; j < page_messages[i]; j++) { + while (offset < page_bytes[i]) { Page *page = pages[i]; //lock on each iteration, so a call can re-add itself to the message queue @@ -312,7 +366,6 @@ void CallQueue::clear() { advance += sizeof(Variant) * message->args; } - //pre-advance so this function is reentrant offset += advance; if ((message->type & FLAG_MASK) != TYPE_NOTIFICATION) { @@ -327,14 +380,13 @@ void CallQueue::clear() { } pages_used = 1; - page_offset = 0; - page_messages[0] = 0; + page_bytes[0] = 0; - mutex.unlock(); + UNLOCK_MUTEX; } void CallQueue::statistics() { - mutex.lock(); + LOCK_MUTEX; HashMap<StringName, int> set_count; HashMap<int, int> notify_count; HashMap<Callable, int> call_count; @@ -342,7 +394,7 @@ void CallQueue::statistics() { for (uint32_t i = 0; i < pages_used; i++) { uint32_t offset = 0; - for (uint32_t j = 0; j < page_messages[i]; j++) { + while (offset < page_bytes[i]) { Page *page = pages[i]; //lock on each iteration, so a call can re-add itself to the message queue @@ -397,7 +449,6 @@ void CallQueue::statistics() { null_count++; } - //pre-advance so this function is reentrant offset += advance; if ((message->type & FLAG_MASK) != TYPE_NOTIFICATION) { @@ -426,13 +477,24 @@ void CallQueue::statistics() { print_line("NOTIFY " + itos(E.key) + ": " + itos(E.value)); } - mutex.unlock(); + UNLOCK_MUTEX; } bool CallQueue::is_flushing() const { return flushing; } +bool CallQueue::has_messages() const { + if (pages_used == 0) { + return false; + } + if (pages_used == 1 && page_bytes[0] == 0) { + return false; + } + + return true; +} + int CallQueue::get_max_buffer_usage() const { return pages.size() * PAGE_SIZE_BYTES; } @@ -462,16 +524,21 @@ CallQueue::~CallQueue() { ////////////////////// -MessageQueue *MessageQueue::singleton = nullptr; +CallQueue *MessageQueue::main_singleton = nullptr; +thread_local CallQueue *MessageQueue::thread_singleton = nullptr; + +void MessageQueue::set_thread_singleton_override(CallQueue *p_thread_singleton) { + thread_singleton = p_thread_singleton; +} MessageQueue::MessageQueue() : CallQueue(nullptr, int(GLOBAL_DEF_RST(PropertyInfo(Variant::INT, "memory/limits/message_queue/max_size_mb", PROPERTY_HINT_RANGE, "1,512,1,or_greater"), 32)) * 1024 * 1024 / PAGE_SIZE_BYTES, "Message queue out of memory. Try increasing 'memory/limits/message_queue/max_size_mb' in project settings.") { - ERR_FAIL_COND_MSG(singleton != nullptr, "A MessageQueue singleton already exists."); - singleton = this; + ERR_FAIL_COND_MSG(main_singleton != nullptr, "A MessageQueue singleton already exists."); + main_singleton = this; } MessageQueue::~MessageQueue() { - singleton = nullptr; + main_singleton = nullptr; } diff --git a/core/object/message_queue.h b/core/object/message_queue.h index 2349c6d869..c6fcccbd58 100644 --- a/core/object/message_queue.h +++ b/core/object/message_queue.h @@ -45,6 +45,14 @@ public: PAGE_SIZE_BYTES = 4096 }; + struct Page { + uint8_t data[PAGE_SIZE_BYTES]; + }; + + // Needs to be public to be able to define it outside the class. + // Needs to lock because there can be multiple of these allocators in several threads. + typedef PagedAllocator<Page, true> Allocator; + private: enum { TYPE_CALL, @@ -56,21 +64,15 @@ private: FLAG_MASK = FLAG_NULL_IS_OK - 1, }; - struct Page { - uint8_t data[PAGE_SIZE_BYTES]; - }; - Mutex mutex; - typedef PagedAllocator<Page, false> Allocator; Allocator *allocator = nullptr; bool allocator_is_custom = false; LocalVector<Page *> pages; - LocalVector<uint32_t> page_messages; + LocalVector<uint32_t> page_bytes; uint32_t max_pages = 0; uint32_t pages_used = 0; - uint32_t page_offset = 0; bool flushing = false; struct Message { @@ -85,7 +87,7 @@ private: _FORCE_INLINE_ void _ensure_first_page() { if (unlikely(pages.is_empty())) { pages.push_back(allocator->alloc()); - page_messages.push_back(0); + page_bytes.push_back(0); pages_used = 1; } } @@ -140,6 +142,8 @@ public: void clear(); void statistics(); + bool has_messages() const; + bool is_flushing() const; int get_max_buffer_usage() const; @@ -148,10 +152,15 @@ public: }; class MessageQueue : public CallQueue { - static MessageQueue *singleton; + static CallQueue *main_singleton; + static thread_local CallQueue *thread_singleton; + friend class CallQueue; public: - _FORCE_INLINE_ static MessageQueue *get_singleton() { return singleton; } + _FORCE_INLINE_ static CallQueue *get_singleton() { return thread_singleton ? thread_singleton : main_singleton; } + + static void set_thread_singleton_override(CallQueue *p_thread_singleton); + MessageQueue(); ~MessageQueue(); }; diff --git a/core/object/object.cpp b/core/object/object.cpp index e0b1a5cf9c..5a34328ec4 100644 --- a/core/object/object.cpp +++ b/core/object/object.cpp @@ -201,6 +201,10 @@ bool Object::_predelete() { return _predelete_ok; } +void Object::cancel_free() { + _predelete_ok = false; +} + void Object::_postinitialize() { _class_name_ptr = _get_class_namev(); // Set the direct pointer, which is much faster to obtain, but can only happen after postinitialize. _initialize_classv(); @@ -1570,6 +1574,7 @@ void Object::_bind_methods() { ClassDB::bind_method(D_METHOD("tr_n", "message", "plural_message", "n", "context"), &Object::tr_n, DEFVAL("")); ClassDB::bind_method(D_METHOD("is_queued_for_deletion"), &Object::is_queued_for_deletion); + ClassDB::bind_method(D_METHOD("cancel_free"), &Object::cancel_free); ClassDB::add_virtual_method("Object", MethodInfo("free"), false); diff --git a/core/object/object.h b/core/object/object.h index ed2c625417..c633208d7c 100644 --- a/core/object/object.h +++ b/core/object/object.h @@ -61,6 +61,7 @@ enum PropertyHint { PROPERTY_HINT_LAYERS_3D_RENDER, PROPERTY_HINT_LAYERS_3D_PHYSICS, PROPERTY_HINT_LAYERS_3D_NAVIGATION, + PROPERTY_HINT_LAYERS_AVOIDANCE, PROPERTY_HINT_FILE, ///< a file path must be passed, hint_text (optionally) is a filter "*.png,*.wav,*.doc," PROPERTY_HINT_DIR, ///< a directory path must be passed PROPERTY_HINT_GLOBAL_FILE, ///< a file path must be passed, hint_text (optionally) is a filter "*.png,*.wav,*.doc," @@ -118,6 +119,7 @@ enum PropertyUsageFlags { PROPERTY_USAGE_EDITOR_INSTANTIATE_OBJECT = 1 << 26, // For Object properties, instantiate them when creating in editor. PROPERTY_USAGE_EDITOR_BASIC_SETTING = 1 << 27, //for project or editor settings, show when basic settings are selected. PROPERTY_USAGE_READ_ONLY = 1 << 28, // Mark a property as read-only in the inspector. + PROPERTY_USAGE_SECRET = 1 << 29, // Export preset credentials that should be stored separately from the rest of the export config. PROPERTY_USAGE_DEFAULT = PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_EDITOR, PROPERTY_USAGE_NO_EDITOR = PROPERTY_USAGE_STORAGE, @@ -923,6 +925,8 @@ public: _ALWAYS_INLINE_ bool is_ref_counted() const { return type_is_reference; } + void cancel_free(); + Object(); virtual ~Object(); }; diff --git a/core/os/mutex.h b/core/os/mutex.h index 90cc1632e8..cee0f8af74 100644 --- a/core/os/mutex.h +++ b/core/os/mutex.h @@ -119,8 +119,25 @@ class MutexLock { public: _ALWAYS_INLINE_ explicit MutexLock(const MutexT &p_mutex) : + lock(p_mutex.mutex){}; +}; + +// This specialization is needed so manual locking and MutexLock can be used +// at the same time on a SafeBinaryMutex. +template <int Tag> +class MutexLock<SafeBinaryMutex<Tag>> { + friend class ConditionVariable; + + std::unique_lock<std::mutex> lock; + +public: + _ALWAYS_INLINE_ explicit MutexLock(const SafeBinaryMutex<Tag> &p_mutex) : lock(p_mutex.mutex) { - } + SafeBinaryMutex<Tag>::count++; + }; + _ALWAYS_INLINE_ ~MutexLock() { + SafeBinaryMutex<Tag>::count--; + }; }; using Mutex = MutexImpl<std::recursive_mutex>; // Recursive, for general use diff --git a/core/os/thread.cpp b/core/os/thread.cpp index 502f82aaef..c067ad1a6a 100644 --- a/core/os/thread.cpp +++ b/core/os/thread.cpp @@ -66,11 +66,12 @@ void Thread::callback(ID p_caller_id, const Settings &p_settings, Callback p_cal } } -void Thread::start(Thread::Callback p_callback, void *p_user, const Settings &p_settings) { - ERR_FAIL_COND_MSG(id != UNASSIGNED_ID, "A Thread object has been re-started without wait_to_finish() having been called on it."); +Thread::ID Thread::start(Thread::Callback p_callback, void *p_user, const Settings &p_settings) { + ERR_FAIL_COND_V_MSG(id != UNASSIGNED_ID, UNASSIGNED_ID, "A Thread object has been re-started without wait_to_finish() having been called on it."); id = id_counter.increment(); std::thread new_thread(&Thread::callback, id, p_settings, p_callback, p_user); thread.swap(new_thread); + return id; } bool Thread::is_started() const { diff --git a/core/os/thread.h b/core/os/thread.h index 19e1376ca8..3e307adfff 100644 --- a/core/os/thread.h +++ b/core/os/thread.h @@ -105,9 +105,11 @@ public: // get the ID of the main thread _FORCE_INLINE_ static ID get_main_id() { return MAIN_ID; } + _FORCE_INLINE_ static bool is_main_thread() { return caller_id == MAIN_ID; } // Gain a tiny bit of perf here because there is no need to validate caller_id here, because only main thread will be set as 1. + static Error set_name(const String &p_name); - void start(Thread::Callback p_callback, void *p_user, const Settings &p_settings = Settings()); + ID start(Thread::Callback p_callback, void *p_user, const Settings &p_settings = Settings()); bool is_started() const; ///< waits until thread is finished, and deallocates it. void wait_to_finish(); diff --git a/core/templates/hash_map.h b/core/templates/hash_map.h index 0b846dfaba..4da73f1cfb 100644 --- a/core/templates/hash_map.h +++ b/core/templates/hash_map.h @@ -97,7 +97,7 @@ private: } bool _lookup_pos(const TKey &p_key, uint32_t &r_pos) const { - if (elements == nullptr) { + if (elements == nullptr || num_elements == 0) { return false; // Failed lookups, no elements } @@ -252,7 +252,7 @@ public: } void clear() { - if (elements == nullptr) { + if (elements == nullptr || num_elements == 0) { return; } uint32_t capacity = hash_table_size_primes[capacity_index]; diff --git a/core/templates/hash_set.h b/core/templates/hash_set.h index 97f1b460aa..00f4acbc9c 100644 --- a/core/templates/hash_set.h +++ b/core/templates/hash_set.h @@ -80,7 +80,7 @@ private: } bool _lookup_pos(const TKey &p_key, uint32_t &r_pos) const { - if (keys == nullptr) { + if (keys == nullptr || num_elements == 0) { return false; // Failed lookups, no elements } @@ -237,7 +237,7 @@ public: } void clear() { - if (keys == nullptr) { + if (keys == nullptr || num_elements == 0) { return; } uint32_t capacity = hash_table_size_primes[capacity_index]; diff --git a/core/templates/local_vector.h b/core/templates/local_vector.h index 22db3a5744..b454821a8f 100644 --- a/core/templates/local_vector.h +++ b/core/templates/local_vector.h @@ -95,11 +95,13 @@ public: } } - void erase(const T &p_val) { + _FORCE_INLINE_ bool erase(const T &p_val) { int64_t idx = find(p_val); if (idx >= 0) { remove_at(idx); + return true; } + return false; } void invert() { diff --git a/core/templates/vector.h b/core/templates/vector.h index ae58eb8b16..d8bac0870f 100644 --- a/core/templates/vector.h +++ b/core/templates/vector.h @@ -71,12 +71,15 @@ public: void fill(T p_elem); void remove_at(int p_index) { _cowdata.remove_at(p_index); } - void erase(const T &p_val) { + _FORCE_INLINE_ bool erase(const T &p_val) { int idx = find(p_val); if (idx >= 0) { remove_at(idx); + return true; } + return false; } + void reverse(); _FORCE_INLINE_ T *ptrw() { return _cowdata.ptrw(); } diff --git a/doc/classes/@GlobalScope.xml b/doc/classes/@GlobalScope.xml index 86f9e5ebb3..1ca44db11c 100644 --- a/doc/classes/@GlobalScope.xml +++ b/doc/classes/@GlobalScope.xml @@ -2695,36 +2695,39 @@ <constant name="PROPERTY_HINT_LAYERS_3D_NAVIGATION" value="12" enum="PropertyHint"> Hints that an [int] property is a bitmask using the optionally named 3D navigation layers. </constant> - <constant name="PROPERTY_HINT_FILE" value="13" enum="PropertyHint"> + <constant name="PROPERTY_HINT_LAYERS_AVOIDANCE" value="13" enum="PropertyHint"> + Hints that an integer property is a bitmask using the optionally named avoidance layers. + </constant> + <constant name="PROPERTY_HINT_FILE" value="14" enum="PropertyHint"> Hints that a [String] property is a path to a file. Editing it will show a file dialog for picking the path. The hint string can be a set of filters with wildcards like [code]"*.png,*.jpg"[/code]. </constant> - <constant name="PROPERTY_HINT_DIR" value="14" enum="PropertyHint"> + <constant name="PROPERTY_HINT_DIR" value="15" enum="PropertyHint"> Hints that a [String] property is a path to a directory. Editing it will show a file dialog for picking the path. </constant> - <constant name="PROPERTY_HINT_GLOBAL_FILE" value="15" enum="PropertyHint"> + <constant name="PROPERTY_HINT_GLOBAL_FILE" value="16" enum="PropertyHint"> Hints that a [String] property is an absolute path to a file outside the project folder. Editing it will show a file dialog for picking the path. The hint string can be a set of filters with wildcards, like [code]"*.png,*.jpg"[/code]. </constant> - <constant name="PROPERTY_HINT_GLOBAL_DIR" value="16" enum="PropertyHint"> + <constant name="PROPERTY_HINT_GLOBAL_DIR" value="17" enum="PropertyHint"> Hints that a [String] property is an absolute path to a directory outside the project folder. Editing it will show a file dialog for picking the path. </constant> - <constant name="PROPERTY_HINT_RESOURCE_TYPE" value="17" enum="PropertyHint"> + <constant name="PROPERTY_HINT_RESOURCE_TYPE" value="18" enum="PropertyHint"> Hints that a property is an instance of a [Resource]-derived type, optionally specified via the hint string (e.g. [code]"Texture2D"[/code]). Editing it will show a popup menu of valid resource types to instantiate. </constant> - <constant name="PROPERTY_HINT_MULTILINE_TEXT" value="18" enum="PropertyHint"> + <constant name="PROPERTY_HINT_MULTILINE_TEXT" value="19" enum="PropertyHint"> Hints that a [String] property is text with line breaks. Editing it will show a text input field where line breaks can be typed. </constant> - <constant name="PROPERTY_HINT_EXPRESSION" value="19" enum="PropertyHint"> + <constant name="PROPERTY_HINT_EXPRESSION" value="20" enum="PropertyHint"> Hints that a [String] property is an [Expression]. </constant> - <constant name="PROPERTY_HINT_PLACEHOLDER_TEXT" value="20" enum="PropertyHint"> + <constant name="PROPERTY_HINT_PLACEHOLDER_TEXT" value="21" enum="PropertyHint"> Hints that a [String] property should show a placeholder text on its input field, if empty. The hint string is the placeholder text to use. </constant> - <constant name="PROPERTY_HINT_COLOR_NO_ALPHA" value="21" enum="PropertyHint"> + <constant name="PROPERTY_HINT_COLOR_NO_ALPHA" value="22" enum="PropertyHint"> Hints that a [Color] property should be edited without affecting its transparency ([member Color.a] is not editable). </constant> - <constant name="PROPERTY_HINT_OBJECT_ID" value="22" enum="PropertyHint"> + <constant name="PROPERTY_HINT_OBJECT_ID" value="23" enum="PropertyHint"> </constant> - <constant name="PROPERTY_HINT_TYPE_STRING" value="23" enum="PropertyHint"> + <constant name="PROPERTY_HINT_TYPE_STRING" value="24" enum="PropertyHint"> Hint that a property represents a particular type. If a property is [constant TYPE_STRING], allows to set a type from the create dialog. If you need to create an [Array] to contain elements of a specific type, the [code]hint_string[/code] must encode nested types using [code]":"[/code] and [code]"/"[/code] for specifying [Resource] types. For instance: [codeblock] hint_string = "%s:" % [TYPE_INT] # Array of integers. @@ -2734,37 +2737,37 @@ [/codeblock] [b]Note:[/b] The final colon is required for properly detecting built-in types. </constant> - <constant name="PROPERTY_HINT_NODE_PATH_TO_EDITED_NODE" value="24" enum="PropertyHint"> + <constant name="PROPERTY_HINT_NODE_PATH_TO_EDITED_NODE" value="25" enum="PropertyHint"> </constant> - <constant name="PROPERTY_HINT_OBJECT_TOO_BIG" value="25" enum="PropertyHint"> + <constant name="PROPERTY_HINT_OBJECT_TOO_BIG" value="26" enum="PropertyHint"> </constant> - <constant name="PROPERTY_HINT_NODE_PATH_VALID_TYPES" value="26" enum="PropertyHint"> + <constant name="PROPERTY_HINT_NODE_PATH_VALID_TYPES" value="27" enum="PropertyHint"> </constant> - <constant name="PROPERTY_HINT_SAVE_FILE" value="27" enum="PropertyHint"> + <constant name="PROPERTY_HINT_SAVE_FILE" value="28" enum="PropertyHint"> </constant> - <constant name="PROPERTY_HINT_GLOBAL_SAVE_FILE" value="28" enum="PropertyHint"> + <constant name="PROPERTY_HINT_GLOBAL_SAVE_FILE" value="29" enum="PropertyHint"> </constant> - <constant name="PROPERTY_HINT_INT_IS_OBJECTID" value="29" enum="PropertyHint"> + <constant name="PROPERTY_HINT_INT_IS_OBJECTID" value="30" enum="PropertyHint"> </constant> - <constant name="PROPERTY_HINT_INT_IS_POINTER" value="30" enum="PropertyHint"> + <constant name="PROPERTY_HINT_INT_IS_POINTER" value="31" enum="PropertyHint"> </constant> - <constant name="PROPERTY_HINT_ARRAY_TYPE" value="31" enum="PropertyHint"> + <constant name="PROPERTY_HINT_ARRAY_TYPE" value="32" enum="PropertyHint"> </constant> - <constant name="PROPERTY_HINT_LOCALE_ID" value="32" enum="PropertyHint"> + <constant name="PROPERTY_HINT_LOCALE_ID" value="33" enum="PropertyHint"> Hints that a string property is a locale code. Editing it will show a locale dialog for picking language and country. </constant> - <constant name="PROPERTY_HINT_LOCALIZABLE_STRING" value="33" enum="PropertyHint"> + <constant name="PROPERTY_HINT_LOCALIZABLE_STRING" value="34" enum="PropertyHint"> Hints that a dictionary property is string translation map. Dictionary keys are locale codes and, values are translated strings. </constant> - <constant name="PROPERTY_HINT_NODE_TYPE" value="34" enum="PropertyHint"> + <constant name="PROPERTY_HINT_NODE_TYPE" value="35" enum="PropertyHint"> </constant> - <constant name="PROPERTY_HINT_HIDE_QUATERNION_EDIT" value="35" enum="PropertyHint"> + <constant name="PROPERTY_HINT_HIDE_QUATERNION_EDIT" value="36" enum="PropertyHint"> Hints that a quaternion property should disable the temporary euler editor. </constant> - <constant name="PROPERTY_HINT_PASSWORD" value="36" enum="PropertyHint"> + <constant name="PROPERTY_HINT_PASSWORD" value="37" enum="PropertyHint"> Hints that a string property is a password, and every character is replaced with the secret character. </constant> - <constant name="PROPERTY_HINT_MAX" value="37" enum="PropertyHint"> + <constant name="PROPERTY_HINT_MAX" value="38" enum="PropertyHint"> Represents the size of the [enum PropertyHint] enum. </constant> <constant name="PROPERTY_USAGE_NONE" value="0" enum="PropertyUsageFlags" is_bitfield="true"> @@ -2842,6 +2845,9 @@ <constant name="PROPERTY_USAGE_READ_ONLY" value="268435456" enum="PropertyUsageFlags" is_bitfield="true"> The property is read-only in the [EditorInspector]. </constant> + <constant name="PROPERTY_USAGE_SECRET" value="536870912" enum="PropertyUsageFlags" is_bitfield="true"> + An export preset property with this flag contains confidential information and is stored separately from the rest of the export preset configuration. + </constant> <constant name="PROPERTY_USAGE_DEFAULT" value="6" enum="PropertyUsageFlags" is_bitfield="true"> Default usage (storage, editor and network). </constant> diff --git a/doc/classes/EditorResourcePreviewGenerator.xml b/doc/classes/EditorResourcePreviewGenerator.xml index 1484413536..3dc60b3a98 100644 --- a/doc/classes/EditorResourcePreviewGenerator.xml +++ b/doc/classes/EditorResourcePreviewGenerator.xml @@ -20,20 +20,24 @@ <return type="Texture2D" /> <param index="0" name="resource" type="Resource" /> <param index="1" name="size" type="Vector2i" /> + <param index="2" name="metadata" type="Dictionary" /> <description> Generate a preview from a given resource with the specified size. This must always be implemented. Returning an empty texture is an OK way to fail and let another generator take care. Care must be taken because this function is always called from a thread (not the main thread). + [param metadata] dictionary can modified to store file-specific metadata that can be used by the editor (like image size, sample length etc.). </description> </method> <method name="_generate_from_path" qualifiers="virtual const"> <return type="Texture2D" /> <param index="0" name="path" type="String" /> <param index="1" name="size" type="Vector2i" /> + <param index="2" name="metadata" type="Dictionary" /> <description> Generate a preview directly from a path with the specified size. Implementing this is optional, as default code will load and call [method _generate]. Returning an empty texture is an OK way to fail and let another generator take care. Care must be taken because this function is always called from a thread (not the main thread). + [param metadata] dictionary can modified to store file-specific metadata that can be used by the editor (like image size, sample length etc.). </description> </method> <method name="_generate_small_preview_automatically" qualifiers="virtual const"> diff --git a/doc/classes/EditorSettings.xml b/doc/classes/EditorSettings.xml index 8c27c1bc06..6a27bff763 100644 --- a/doc/classes/EditorSettings.xml +++ b/doc/classes/EditorSettings.xml @@ -416,6 +416,9 @@ <member name="editors/polygon_editor/show_previous_outline" type="bool" setter="" getter=""> If [code]true[/code], displays the polygon's previous shape in the 2D polygon editors with an opaque gray outline. This outline is displayed while dragging a point until the left mouse button is released. </member> + <member name="editors/shader_editor/behavior/files/restore_shaders_on_load" type="bool" setter="" getter=""> + If [code]true[/code], reopens shader files that were open in the shader editor when the project was last closed. + </member> <member name="editors/tiles_editor/display_grid" type="bool" setter="" getter=""> If [code]true[/code], displays a grid while the TileMap editor is active. See also [member editors/tiles_editor/grid_color]. </member> @@ -580,6 +583,17 @@ <member name="interface/inspector/show_low_level_opentype_features" type="bool" setter="" getter=""> If [code]true[/code], display OpenType features marked as [code]hidden[/code] by the font file in the [Font] editor. </member> + <member name="interface/multi_window/enable" type="bool" setter="" getter=""> + If [code]true[/code], the multi window support in editor is enabled. The following panels can become dedicated windows (made floating): Docks, Script editor, and Shader editor. + [b]Note:[/b] When [member interface/editor/single_window_mode] is [code]true[/code], the multi window support is always disabled. + </member> + <member name="interface/multi_window/maximize_window" type="bool" setter="" getter=""> + If [code]true[/code], when panels are made floating they will be maximized. + If [code]false[/code], when panels are made floating their position and size will match the ones when they are attached (excluding window border) to the editor window. + </member> + <member name="interface/multi_window/restore_windows_on_load" type="bool" setter="" getter=""> + If [code]true[/code], the floating panel position, size, and screen will be saved on editor exit. On next launch the panels that were floating will be made floating in the saved positions, sizes and screens, if possible. + </member> <member name="interface/scene_tabs/display_close_button" type="int" setter="" getter=""> Controls when the Close (X) button is displayed on scene tabs at the top of the editor. </member> diff --git a/doc/classes/NavigationAgent2D.xml b/doc/classes/NavigationAgent2D.xml index 1c68058d2f..e401450e42 100644 --- a/doc/classes/NavigationAgent2D.xml +++ b/doc/classes/NavigationAgent2D.xml @@ -5,7 +5,7 @@ </brief_description> <description> 2D Agent that is used in navigation to reach a position while avoiding static and dynamic obstacles. The dynamic obstacles are avoided using RVO collision avoidance. The agent needs navigation data to work correctly. [NavigationAgent2D] is physics safe. - [b]Note:[/b] After setting [member target_position] it is required to use the [method get_next_path_position] function once every physics frame to update the internal path logic of the NavigationAgent. The returned vector position from this function should be used as the next movement position for the agent's parent Node. + [b]Note:[/b] After [member target_position] is set, the [method get_next_path_position] function must be used once every physics frame to update the internal path logic of the NavigationAgent. The returned position from this function should be used as the next movement position for the agent's parent node. </description> <tutorials> <link title="Using NavigationAgents">$DOCS_URL/tutorials/navigation/navigation_using_navigationagents.html</link> @@ -17,6 +17,20 @@ Returns the distance to the target position, using the agent's global position. The user must set [member target_position] in order for this to be accurate. </description> </method> + <method name="get_avoidance_layer_value" qualifiers="const"> + <return type="bool" /> + <param index="0" name="layer_number" type="int" /> + <description> + Returns whether or not the specified layer of the [member avoidance_layers] bitmask is enabled, given a [param layer_number] between 1 and 32. + </description> + </method> + <method name="get_avoidance_mask_value" qualifiers="const"> + <return type="bool" /> + <param index="0" name="mask_number" type="int" /> + <description> + Returns whether or not the specified mask of the [member avoidance_mask] bitmask is enabled, given a [param mask_number] between 1 and 32. + </description> + </method> <method name="get_current_navigation_path" qualifiers="const"> <return type="PackedVector2Array" /> <description> @@ -38,7 +52,7 @@ <method name="get_final_position"> <return type="Vector2" /> <description> - Returns the reachable final position in global coordinates. This can change if the navigation path is altered in any way. Because of this, it would be best to check this each frame. + Returns the reachable final position of the current navigation path in global coordinates. This can change if the navigation path is altered in any way. Because of this, it would be best to check this each frame. </description> </method> <method name="get_navigation_layer_value" qualifiers="const"> @@ -75,7 +89,7 @@ <method name="is_target_reachable"> <return type="bool" /> <description> - Returns true if [member target_position] is reachable. + Returns true if [member target_position] is reachable. The target position is set using [member target_position]. </description> </method> <method name="is_target_reached" qualifiers="const"> @@ -84,6 +98,22 @@ Returns true if [member target_position] is reached. It may not always be possible to reach the target position. It should always be possible to reach the final position though. See [method get_final_position]. </description> </method> + <method name="set_avoidance_layer_value"> + <return type="void" /> + <param index="0" name="layer_number" type="int" /> + <param index="1" name="value" type="bool" /> + <description> + Based on [param value], enables or disables the specified layer in the [member avoidance_layers] bitmask, given a [param layer_number] between 1 and 32. + </description> + </method> + <method name="set_avoidance_mask_value"> + <return type="void" /> + <param index="0" name="mask_number" type="int" /> + <param index="1" name="value" type="bool" /> + <description> + Based on [param value], enables or disables the specified mask in the [member avoidance_mask] bitmask, given a [param mask_number] between 1 and 32. + </description> + </method> <method name="set_navigation_layer_value"> <return type="void" /> <param index="0" name="layer_number" type="int" /> @@ -99,17 +129,26 @@ Sets the [RID] of the navigation map this NavigationAgent node should use and also updates the [code]agent[/code] on the NavigationServer. </description> </method> - <method name="set_velocity"> + <method name="set_velocity_forced"> <return type="void" /> <param index="0" name="velocity" type="Vector2" /> <description> - Sends the passed in velocity to the collision avoidance algorithm. It will adjust the velocity to avoid collisions. Once the adjustment to the velocity is complete, it will emit the [signal velocity_computed] signal. + Replaces the internal velocity in the collision avoidance simulation with [param velocity]. When an agent is teleported to a new position this function should be used in the same frame. If called frequently this function can get agents stuck. </description> </method> </methods> <members> <member name="avoidance_enabled" type="bool" setter="set_avoidance_enabled" getter="get_avoidance_enabled" default="false"> - If [code]true[/code] the agent is registered for an RVO avoidance callback on the [NavigationServer2D]. When [method set_velocity] is used and the processing is completed a [code]safe_velocity[/code] Vector2 is received with a signal connection to [signal velocity_computed]. Avoidance processing with many registered agents has a significant performance cost and should only be enabled on agents that currently require it. + If [code]true[/code] the agent is registered for an RVO avoidance callback on the [NavigationServer2D]. When [member velocity] is used and the processing is completed a [code]safe_velocity[/code] Vector2 is received with a signal connection to [signal velocity_computed]. Avoidance processing with many registered agents has a significant performance cost and should only be enabled on agents that currently require it. + </member> + <member name="avoidance_layers" type="int" setter="set_avoidance_layers" getter="get_avoidance_layers" default="1"> + A bitfield determining the avoidance layers for this NavigationAgent. Other agent's with a matching bit on the [member avoidance_mask] will avoid this agent. + </member> + <member name="avoidance_mask" type="int" setter="set_avoidance_mask" getter="get_avoidance_mask" default="1"> + A bitfield determining what other avoidance agent's and obstacles this NavigationAgent will avoid when a bit matches at least one of their [member avoidance_layers]. + </member> + <member name="avoidance_priority" type="float" setter="set_avoidance_priority" getter="get_avoidance_priority" default="1.0"> + The agent does not adjust the velocity for other agents that would match the [member avoidance_mask] but have a lower [member avoidance_priority]. This in turn makes the other agents with lower priority adjust their velocities even more to avoid collision with this agent. </member> <member name="debug_enabled" type="bool" setter="set_debug_enabled" getter="get_debug_enabled" default="false"> If [code]true[/code] shows debug visuals for this agent. @@ -161,10 +200,16 @@ The distance threshold before the final target point is considered to be reached. This will allow an agent to not have to hit the point of the final target exactly, but only the area. If this value is set to low the NavigationAgent will be stuck in a repath loop cause it will constantly overshoot or undershoot the distance to the final target point on each physics frame update. </member> <member name="target_position" type="Vector2" setter="set_target_position" getter="get_target_position" default="Vector2(0, 0)"> - The user-defined target position. Setting this property will clear the current navigation path. + If set a new navigation path from the current agent position to the [member target_position] is requested from the NavigationServer. + </member> + <member name="time_horizon_agents" type="float" setter="set_time_horizon_agents" getter="get_time_horizon_agents" default="1.0"> + The minimal amount of time for which this agent's velocities, that are computed with the collision avoidance algorithm, are safe with respect to other agents. The larger the number, the sooner the agent will respond to other agents, but less freedom in choosing its velocities. A too high value will slow down agents movement considerably. Must be positive. + </member> + <member name="time_horizon_obstacles" type="float" setter="set_time_horizon_obstacles" getter="get_time_horizon_obstacles" default="0.0"> + The minimal amount of time for which this agent's velocities, that are computed with the collision avoidance algorithm, are safe with respect to static avoidance obstacles. The larger the number, the sooner the agent will respond to static avoidance obstacles, but less freedom in choosing its velocities. A too high value will slow down agents movement considerably. Must be positive. </member> - <member name="time_horizon" type="float" setter="set_time_horizon" getter="get_time_horizon" default="1.0"> - The minimal amount of time for which this agent's velocities, that are computed with the collision avoidance algorithm, are safe with respect to other agents. The larger the number, the sooner the agent will respond to other agents, but less freedom in choosing its velocities. Must be positive. + <member name="velocity" type="Vector2" setter="set_velocity" getter="get_velocity" default="Vector2(0, 0)"> + Sets the new wanted velocity for the agent. The avoidance simulation will try to fulfil this velocity if possible but will modify it to avoid collision with other agent's and obstacles. When an agent is teleported to a new position use [method set_velocity_forced] as well to reset the internal simulation velocity. </member> </members> <signals> @@ -199,7 +244,7 @@ <signal name="velocity_computed"> <param index="0" name="safe_velocity" type="Vector2" /> <description> - Notifies when the collision avoidance velocity is calculated. Emitted at the end of the physics frame in which [method set_velocity] is called. Only emitted when [member avoidance_enabled] is true. + Notifies when the collision avoidance velocity is calculated. Emitted when [member velocity] is set. Only emitted when [member avoidance_enabled] is true. </description> </signal> <signal name="waypoint_reached"> diff --git a/doc/classes/NavigationAgent3D.xml b/doc/classes/NavigationAgent3D.xml index 6fdea5431d..00ef894378 100644 --- a/doc/classes/NavigationAgent3D.xml +++ b/doc/classes/NavigationAgent3D.xml @@ -5,7 +5,7 @@ </brief_description> <description> 3D Agent that is used in navigation to reach a position while avoiding static and dynamic obstacles. The dynamic obstacles are avoided using RVO collision avoidance. The agent needs navigation data to work correctly. [NavigationAgent3D] is physics safe. - [b]Note:[/b] After setting [member target_position] it is required to use the [method get_next_path_position] function once every physics frame to update the internal path logic of the NavigationAgent. The returned vector position from this function should be used as the next movement position for the agent's parent Node. + [b]Note:[/b] After [member target_position] is set, the [method get_next_path_position] function must be used once every physics frame to update the internal path logic of the NavigationAgent. The returned position from this function should be used as the next movement position for the agent's parent node. </description> <tutorials> <link title="Using NavigationAgents">$DOCS_URL/tutorials/navigation/navigation_using_navigationagents.html</link> @@ -17,6 +17,20 @@ Returns the distance to the target position, using the agent's global position. The user must set [member target_position] in order for this to be accurate. </description> </method> + <method name="get_avoidance_layer_value" qualifiers="const"> + <return type="bool" /> + <param index="0" name="layer_number" type="int" /> + <description> + Returns whether or not the specified layer of the [member avoidance_layers] bitmask is enabled, given a [param layer_number] between 1 and 32. + </description> + </method> + <method name="get_avoidance_mask_value" qualifiers="const"> + <return type="bool" /> + <param index="0" name="mask_number" type="int" /> + <description> + Returns whether or not the specified mask of the [member avoidance_mask] bitmask is enabled, given a [param mask_number] between 1 and 32. + </description> + </method> <method name="get_current_navigation_path" qualifiers="const"> <return type="PackedVector3Array" /> <description> @@ -38,7 +52,7 @@ <method name="get_final_position"> <return type="Vector3" /> <description> - Returns the reachable final position in global coordinates. This can change if the navigation path is altered in any way. Because of this, it would be best to check this each frame. + Returns the reachable final position of the current navigation path in global coordinates. This position can change if the navigation path is altered in any way. Because of this, it would be best to check this each frame. </description> </method> <method name="get_navigation_layer_value" qualifiers="const"> @@ -75,7 +89,7 @@ <method name="is_target_reachable"> <return type="bool" /> <description> - Returns true if [member target_position] is reachable. + Returns true if [member target_position] is reachable. The target position is set using [member target_position]. </description> </method> <method name="is_target_reached" qualifiers="const"> @@ -84,6 +98,22 @@ Returns true if [member target_position] is reached. It may not always be possible to reach the target position. It should always be possible to reach the final position though. See [method get_final_position]. </description> </method> + <method name="set_avoidance_layer_value"> + <return type="void" /> + <param index="0" name="layer_number" type="int" /> + <param index="1" name="value" type="bool" /> + <description> + Based on [param value], enables or disables the specified layer in the [member avoidance_layers] bitmask, given a [param layer_number] between 1 and 32. + </description> + </method> + <method name="set_avoidance_mask_value"> + <return type="void" /> + <param index="0" name="mask_number" type="int" /> + <param index="1" name="value" type="bool" /> + <description> + Based on [param value], enables or disables the specified mask in the [member avoidance_mask] bitmask, given a [param mask_number] between 1 and 32. + </description> + </method> <method name="set_navigation_layer_value"> <return type="void" /> <param index="0" name="layer_number" type="int" /> @@ -99,20 +129,26 @@ Sets the [RID] of the navigation map this NavigationAgent node should use and also updates the [code]agent[/code] on the NavigationServer. </description> </method> - <method name="set_velocity"> + <method name="set_velocity_forced"> <return type="void" /> <param index="0" name="velocity" type="Vector3" /> <description> - Sends the passed in velocity to the collision avoidance algorithm. It will adjust the velocity to avoid collisions. Once the adjustment to the velocity is complete, it will emit the [signal velocity_computed] signal. + Replaces the internal velocity in the collision avoidance simulation with [param velocity]. When an agent is teleported to a new position this function should be used in the same frame. If called frequently this function can get agents stuck. </description> </method> </methods> <members> - <member name="agent_height_offset" type="float" setter="set_agent_height_offset" getter="get_agent_height_offset" default="0.0"> - The NavigationAgent height offset is subtracted from the y-axis value of any vector path position for this NavigationAgent. The NavigationAgent height offset does not change or influence the navigation mesh or pathfinding query result. Additional navigation maps that use regions with navigation meshes that the developer baked with appropriate agent radius or height values are required to support different-sized agents. - </member> <member name="avoidance_enabled" type="bool" setter="set_avoidance_enabled" getter="get_avoidance_enabled" default="false"> - If [code]true[/code] the agent is registered for an RVO avoidance callback on the [NavigationServer3D]. When [method set_velocity] is used and the processing is completed a [code]safe_velocity[/code] Vector3 is received with a signal connection to [signal velocity_computed]. Avoidance processing with many registered agents has a significant performance cost and should only be enabled on agents that currently require it. + If [code]true[/code] the agent is registered for an RVO avoidance callback on the [NavigationServer3D]. When [member velocity] is set and the processing is completed a [code]safe_velocity[/code] Vector3 is received with a signal connection to [signal velocity_computed]. Avoidance processing with many registered agents has a significant performance cost and should only be enabled on agents that currently require it. + </member> + <member name="avoidance_layers" type="int" setter="set_avoidance_layers" getter="get_avoidance_layers" default="1"> + A bitfield determining the avoidance layers for this NavigationAgent. Other agent's with a matching bit on the [member avoidance_mask] will avoid this agent. + </member> + <member name="avoidance_mask" type="int" setter="set_avoidance_mask" getter="get_avoidance_mask" default="1"> + A bitfield determining what other avoidance agent's and obstacles this NavigationAgent will avoid when a bit matches at least one of their [member avoidance_layers]. + </member> + <member name="avoidance_priority" type="float" setter="set_avoidance_priority" getter="get_avoidance_priority" default="1.0"> + The agent does not adjust the velocity for other agents that would match the [member avoidance_mask] but have a lower [member avoidance_priority]. This in turn makes the other agents with lower priority adjust their velocities even more to avoid collision with this agent. </member> <member name="debug_enabled" type="bool" setter="set_debug_enabled" getter="get_debug_enabled" default="false"> If [code]true[/code] shows debug visuals for this agent. @@ -126,8 +162,8 @@ <member name="debug_use_custom" type="bool" setter="set_debug_use_custom" getter="get_debug_use_custom" default="false"> If [code]true[/code] uses the defined [member debug_path_custom_color] for this agent instead of global color. </member> - <member name="ignore_y" type="bool" setter="set_ignore_y" getter="get_ignore_y" default="true"> - Ignores collisions on the Y axis. Must be true to move on a horizontal plane. + <member name="height" type="float" setter="set_height" getter="get_height" default="1.0"> + The height of the avoidance agent. Agent's will ignore other agents or obstacles that are above or below their current position + height in 2D avoidance. Does nothing in 3D avoidance which uses radius spheres alone. </member> <member name="max_neighbors" type="int" setter="set_max_neighbors" getter="get_max_neighbors" default="10"> The maximum number of neighbors for the agent to consider. @@ -144,6 +180,9 @@ <member name="path_desired_distance" type="float" setter="set_path_desired_distance" getter="get_path_desired_distance" default="1.0"> The distance threshold before a path point is considered to be reached. This will allow an agent to not have to hit a path point on the path exactly, but in the area. If this value is set to high the NavigationAgent will skip points on the path which can lead to leaving the navigation mesh. If this value is set to low the NavigationAgent will be stuck in a repath loop cause it will constantly overshoot or undershoot the distance to the next point on each physics frame update. </member> + <member name="path_height_offset" type="float" setter="set_path_height_offset" getter="get_path_height_offset" default="0.0"> + The height offset is subtracted from the y-axis value of any vector path position for this NavigationAgent. The NavigationAgent height offset does not change or influence the navigation mesh or pathfinding query result. Additional navigation maps that use regions with navigation meshes that the developer baked with appropriate agent radius or height values are required to support different-sized agents. + </member> <member name="path_max_distance" type="float" setter="set_path_max_distance" getter="get_path_max_distance" default="5.0"> The maximum distance the agent is allowed away from the ideal path to the final position. This can happen due to trying to avoid collisions. When the maximum distance is exceeded, it recalculates the ideal path. </member> @@ -164,10 +203,20 @@ The distance threshold before the final target point is considered to be reached. This will allow an agent to not have to hit the point of the final target exactly, but only the area. If this value is set to low the NavigationAgent will be stuck in a repath loop cause it will constantly overshoot or undershoot the distance to the final target point on each physics frame update. </member> <member name="target_position" type="Vector3" setter="set_target_position" getter="get_target_position" default="Vector3(0, 0, 0)"> - The user-defined target position. Setting this property will clear the current navigation path. + If set a new navigation path from the current agent position to the [member target_position] is requested from the NavigationServer. + </member> + <member name="time_horizon_agents" type="float" setter="set_time_horizon_agents" getter="get_time_horizon_agents" default="1.0"> + The minimal amount of time for which this agent's velocities, that are computed with the collision avoidance algorithm, are safe with respect to other agents. The larger the number, the sooner the agent will respond to other agents, but less freedom in choosing its velocities. A too high value will slow down agents movement considerably. Must be positive. + </member> + <member name="time_horizon_obstacles" type="float" setter="set_time_horizon_obstacles" getter="get_time_horizon_obstacles" default="0.0"> + The minimal amount of time for which this agent's velocities, that are computed with the collision avoidance algorithm, are safe with respect to static avoidance obstacles. The larger the number, the sooner the agent will respond to static avoidance obstacles, but less freedom in choosing its velocities. A too high value will slow down agents movement considerably. Must be positive. + </member> + <member name="use_3d_avoidance" type="bool" setter="set_use_3d_avoidance" getter="get_use_3d_avoidance" default="false"> + If [code]true[/code] the agent calculates avoidance velocities in 3D for the xyz-axis, e.g. for games that take place in air, unterwater or space. The 3D using agent only avoids other 3D avoidance using agent's. The 3D using agent only reacts to radius based avoidance obstacles. The 3D using agent ignores any vertices based obstacles. The 3D using agent only avoids other 3D using agent's. + If [code]false[/code] the agent calculates avoidance velocities in 2D along the xz-axis ignoring the y-axis. The 2D using agent only avoids other 2D avoidance using agent's. The 2D using agent reacts to radius avoidance obstacles. The 2D using agent reacts to vertices based avoidance obstacles. The 2D using agent only avoids other 2D using agent's. 2D using agents will ignore other 2D using agents or obstacles that are below their current position or above their current position including [member height] in 2D avoidance. </member> - <member name="time_horizon" type="float" setter="set_time_horizon" getter="get_time_horizon" default="1.0"> - The minimal amount of time for which this agent's velocities, that are computed with the collision avoidance algorithm, are safe with respect to other agents. The larger the number, the sooner the agent will respond to other agents, but less freedom in choosing its velocities. Must be positive. + <member name="velocity" type="Vector3" setter="set_velocity" getter="get_velocity" default="Vector3(0, 0, 0)"> + Sets the new wanted velocity for the agent. The avoidance simulation will try to fulfil this velocity if possible but will modify it to avoid collision with other agent's and obstacles. When an agent is teleported to a new position use [method set_velocity_forced] as well to reset the internal simulation velocity. </member> </members> <signals> @@ -202,7 +251,7 @@ <signal name="velocity_computed"> <param index="0" name="safe_velocity" type="Vector3" /> <description> - Notifies when the collision avoidance velocity is calculated. Emitted at the end of the physics frame in which [method set_velocity] is called. Only emitted when [member avoidance_enabled] is true. + Notifies when the collision avoidance velocity is calculated. Emitted when [member velocity] is set. Only emitted when [member avoidance_enabled] is true. </description> </signal> <signal name="waypoint_reached"> diff --git a/doc/classes/NavigationObstacle2D.xml b/doc/classes/NavigationObstacle2D.xml index 06334e3dc7..e365d1aec5 100644 --- a/doc/classes/NavigationObstacle2D.xml +++ b/doc/classes/NavigationObstacle2D.xml @@ -1,42 +1,71 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="NavigationObstacle2D" inherits="Node" is_experimental="true" version="4.1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> +<class name="NavigationObstacle2D" inherits="Node2D" is_experimental="true" version="4.1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> <brief_description> - 2D Obstacle used in navigation for collision avoidance. + 2D Obstacle used in navigation to constrain avoidance controlled agents outside or inside an area. </brief_description> <description> - 2D Obstacle used in navigation for collision avoidance. The obstacle needs navigation data to work correctly. [NavigationObstacle2D] is physics safe. - Obstacles [b]don't[/b] change the resulting path from the pathfinding, they only affect the navigation agent movement in a radius. Therefore, using obstacles for the static walls in your level won't work because those walls don't exist in the pathfinding. The navigation agent will be pushed in a semi-random direction away while moving inside that radius. Obstacles are intended as a last resort option for constantly moving objects that cannot be (re)baked to a navigation mesh efficiently. + 2D Obstacle used in navigation to constrain avoidance controlled agents outside or inside an area. The obstacle needs a navigation map and outline vertices defined to work correctly. + If the obstacle's vertices are winded in clockwise order, avoidance agents will be pushed in by the obstacle, otherwise, avoidance agents will be pushed out. Outlines must not cross or overlap. + Obstacles are [b]not[/b] a replacement for a (re)baked navigation mesh. Obstacles [b]don't[/b] change the resulting path from the pathfinding, obstacles only affect the navigation avoidance agent movement by altering the suggested velocity of the avoidance agent. + Obstacles using vertices can warp to a new position but should not moved every frame as each move requires a rebuild of the avoidance map. </description> <tutorials> <link title="Using NavigationObstacles">$DOCS_URL/tutorials/navigation/navigation_using_navigationobstacles.html</link> </tutorials> <methods> + <method name="get_agent_rid" qualifiers="const"> + <return type="RID" /> + <description> + Returns the [RID] of this agent on the [NavigationServer2D]. This [RID] is used for the moving avoidance "obstacle" component (using a fake avoidance agent) which size is defined by [member radius] and velocity set by using [member velocity]. + </description> + </method> + <method name="get_avoidance_layer_value" qualifiers="const"> + <return type="bool" /> + <param index="0" name="layer_number" type="int" /> + <description> + Returns whether or not the specified layer of the [member avoidance_layers] bitmask is enabled, given a [param layer_number] between 1 and 32. + </description> + </method> <method name="get_navigation_map" qualifiers="const"> <return type="RID" /> <description> - Returns the [RID] of the navigation map for this NavigationObstacle node. This function returns always the map set on the NavigationObstacle node and not the map of the abstract agent on the NavigationServer. If the agent map is changed directly with the NavigationServer API the NavigationObstacle node will not be aware of the map change. Use [method set_navigation_map] to change the navigation map for the NavigationObstacle and also update the agent on the NavigationServer. + Returns the [RID] of the navigation map for this NavigationObstacle node. This function returns always the map set on the NavigationObstacle node and not the map of the abstract obstacle on the NavigationServer. If the obstacle map is changed directly with the NavigationServer API the NavigationObstacle node will not be aware of the map change. Use [method set_navigation_map] to change the navigation map for the NavigationObstacle and also update the obstacle on the NavigationServer. </description> </method> - <method name="get_rid" qualifiers="const"> + <method name="get_obstacle_rid" qualifiers="const"> <return type="RID" /> <description> - Returns the [RID] of this obstacle on the [NavigationServer2D]. + Returns the [RID] of this obstacle on the [NavigationServer2D]. This [RID] is used for the static avoidance obstacle component which shape is defined by [member vertices]. + </description> + </method> + <method name="set_avoidance_layer_value"> + <return type="void" /> + <param index="0" name="layer_number" type="int" /> + <param index="1" name="value" type="bool" /> + <description> + Based on [param value], enables or disables the specified layer in the [member avoidance_layers] bitmask, given a [param layer_number] between 1 and 32. </description> </method> <method name="set_navigation_map"> <return type="void" /> <param index="0" name="navigation_map" type="RID" /> <description> - Sets the [RID] of the navigation map this NavigationObstacle node should use and also updates the [code]agent[/code] on the NavigationServer. + Sets the [RID] of the navigation map this NavigationObstacle node should use and also updates the [code]obstacle[/code] on the NavigationServer. </description> </method> </methods> <members> - <member name="estimate_radius" type="bool" setter="set_estimate_radius" getter="is_radius_estimated" default="true"> - Enables radius estimation algorithm which uses parent's collision shapes to determine the obstacle radius. + <member name="avoidance_layers" type="int" setter="set_avoidance_layers" getter="get_avoidance_layers" default="1"> + A bitfield determining the avoidance layers for this obstacle. Agent's with a matching bit on the their avoidance mask will avoid this obstacle. + </member> + <member name="radius" type="float" setter="set_radius" getter="get_radius" default="0.0"> + Sets the avoidance radius for the obstacle. + </member> + <member name="velocity" type="Vector2" setter="set_velocity" getter="get_velocity" default="Vector2(0, 0)"> + Sets the wanted velocity for the obstacle so other agent's can better predict the obstacle if it is moved with a velocity regularly (every frame) instead of warped to a new position. Does only affect avoidance for the obstacles [member radius]. Does nothing for the obstacles static vertices. </member> - <member name="radius" type="float" setter="set_radius" getter="get_radius" default="1.0"> - The radius of the agent. Used only if [member estimate_radius] is set to false. + <member name="vertices" type="PackedVector2Array" setter="set_vertices" getter="get_vertices" default="PackedVector2Array()"> + The outline vertices of the obstacle. If the vertices are winded in clockwise order agents will be pushed in by the obstacle, else they will be pushed out. Outlines can not be crossed or overlap. Should the vertices using obstacle be warped to a new position agent's can not predict this movement and may get trapped inside the obstacle. </member> </members> </class> diff --git a/doc/classes/NavigationObstacle3D.xml b/doc/classes/NavigationObstacle3D.xml index 2a2642fe8c..eb76d86fc0 100644 --- a/doc/classes/NavigationObstacle3D.xml +++ b/doc/classes/NavigationObstacle3D.xml @@ -1,42 +1,78 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="NavigationObstacle3D" inherits="Node" is_experimental="true" version="4.1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> +<class name="NavigationObstacle3D" inherits="Node3D" is_experimental="true" version="4.1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> <brief_description> - 3D Obstacle used in navigation for collision avoidance. + 3D Obstacle used in navigation to constrain avoidance controlled agents outside or inside an area. </brief_description> <description> - 3D Obstacle used in navigation for collision avoidance. The obstacle needs navigation data to work correctly. [NavigationObstacle3D] is physics safe. - Obstacles [b]don't[/b] change the resulting path from the pathfinding, they only affect the navigation agent movement in a radius. Therefore, using obstacles for the static walls in your level won't work because those walls don't exist in the pathfinding. The navigation agent will be pushed in a semi-random direction away while moving inside that radius. Obstacles are intended as a last resort option for constantly moving objects that cannot be (re)baked to a navigation mesh efficiently. + 3D Obstacle used in navigation to constrain avoidance controlled agents outside or inside an area. The obstacle needs a navigation map and outline vertices defined to work correctly. + If the obstacle's vertices are winded in clockwise order, avoidance agents will be pushed in by the obstacle, otherwise, avoidance agents will be pushed out. Outlines must not cross or overlap. + Obstacles are [b]not[/b] a replacement for a (re)baked navigation mesh. Obstacles [b]don't[/b] change the resulting path from the pathfinding, obstacles only affect the navigation avoidance agent movement by altering the suggested velocity of the avoidance agent. + Obstacles using vertices can warp to a new position but should not moved every frame as each move requires a rebuild of the avoidance map. </description> <tutorials> <link title="Using NavigationObstacles">$DOCS_URL/tutorials/navigation/navigation_using_navigationobstacles.html</link> </tutorials> <methods> + <method name="get_agent_rid" qualifiers="const"> + <return type="RID" /> + <description> + Returns the [RID] of this agent on the [NavigationServer3D]. This [RID] is used for the moving avoidance "obstacle" component (using a fake avoidance agent) which size is defined by [member radius] and velocity set by using [member velocity]. + </description> + </method> + <method name="get_avoidance_layer_value" qualifiers="const"> + <return type="bool" /> + <param index="0" name="layer_number" type="int" /> + <description> + Returns whether or not the specified layer of the [member avoidance_layers] bitmask is enabled, given a [param layer_number] between 1 and 32. + </description> + </method> <method name="get_navigation_map" qualifiers="const"> <return type="RID" /> <description> - Returns the [RID] of the navigation map for this NavigationObstacle node. This function returns always the map set on the NavigationObstacle node and not the map of the abstract agent on the NavigationServer. If the agent map is changed directly with the NavigationServer API the NavigationObstacle node will not be aware of the map change. Use [method set_navigation_map] to change the navigation map for the NavigationObstacle and also update the agent on the NavigationServer. + Returns the [RID] of the navigation map for this NavigationObstacle node. This function returns always the map set on the NavigationObstacle node and not the map of the abstract obstacle on the NavigationServer. If the obstacle map is changed directly with the NavigationServer API the NavigationObstacle node will not be aware of the map change. Use [method set_navigation_map] to change the navigation map for the NavigationObstacle and also update the obstacle on the NavigationServer. </description> </method> - <method name="get_rid" qualifiers="const"> + <method name="get_obstacle_rid" qualifiers="const"> <return type="RID" /> <description> - Returns the [RID] of this obstacle on the [NavigationServer3D]. + Returns the [RID] of this obstacle on the [NavigationServer3D]. This [RID] is used for the static avoidance obstacle component which shape is defined by [member vertices]. + </description> + </method> + <method name="set_avoidance_layer_value"> + <return type="void" /> + <param index="0" name="layer_number" type="int" /> + <param index="1" name="value" type="bool" /> + <description> + Based on [param value], enables or disables the specified layer in the [member avoidance_layers] bitmask, given a [param layer_number] between 1 and 32. </description> </method> <method name="set_navigation_map"> <return type="void" /> <param index="0" name="navigation_map" type="RID" /> <description> - Sets the [RID] of the navigation map this NavigationObstacle node should use and also updates the [code]agent[/code] on the NavigationServer. + Sets the [RID] of the navigation map this NavigationObstacle node should use and also updates the [code]obstacle[/code] on the NavigationServer. </description> </method> </methods> <members> - <member name="estimate_radius" type="bool" setter="set_estimate_radius" getter="is_radius_estimated" default="true"> - Enables radius estimation algorithm which uses parent's collision shapes to determine the obstacle radius. + <member name="avoidance_layers" type="int" setter="set_avoidance_layers" getter="get_avoidance_layers" default="1"> + A bitfield determining the avoidance layers for this obstacle. Agent's with a matching bit on the their avoidance mask will avoid this obstacle. + </member> + <member name="height" type="float" setter="set_height" getter="get_height" default="1.0"> + Sets the obstacle height used in 2D avoidance. 2D avoidance using agent's ignore obstacles that are below or above them. + </member> + <member name="radius" type="float" setter="set_radius" getter="get_radius" default="0.0"> + Sets the avoidance radius for the obstacle. + </member> + <member name="use_3d_avoidance" type="bool" setter="set_use_3d_avoidance" getter="get_use_3d_avoidance" default="false"> + If [code]true[/code] the obstacle affects 3D avoidance using agent's with obstacle [member radius]. + If [code]false[/code] the obstacle affects 2D avoidance using agent's with both obstacle [member vertices] as well as obstacle [member radius]. + </member> + <member name="velocity" type="Vector3" setter="set_velocity" getter="get_velocity" default="Vector3(0, 0, 0)"> + Sets the wanted velocity for the obstacle so other agent's can better predict the obstacle if it is moved with a velocity regularly (every frame) instead of warped to a new position. Does only affect avoidance for the obstacles [member radius]. Does nothing for the obstacles static vertices. </member> - <member name="radius" type="float" setter="set_radius" getter="get_radius" default="1.0"> - The radius of the agent. Used only if [member estimate_radius] is set to false. + <member name="vertices" type="PackedVector3Array" setter="set_vertices" getter="get_vertices" default="PackedVector3Array()"> + The outline vertices of the obstacle. If the vertices are winded in clockwise order agents will be pushed in by the obstacle, else they will be pushed out. Outlines can not be crossed or overlap. Should the vertices using obstacle be warped to a new position agent's can not predict this movement and may get trapped inside the obstacle. </member> </members> </class> diff --git a/doc/classes/NavigationRegion2D.xml b/doc/classes/NavigationRegion2D.xml index f05fd74685..bb5992f112 100644 --- a/doc/classes/NavigationRegion2D.xml +++ b/doc/classes/NavigationRegion2D.xml @@ -16,6 +16,13 @@ <link title="Using NavigationRegions">$DOCS_URL/tutorials/navigation/navigation_using_navigationregions.html</link> </tutorials> <methods> + <method name="get_avoidance_layer_value" qualifiers="const"> + <return type="bool" /> + <param index="0" name="layer_number" type="int" /> + <description> + Returns whether or not the specified layer of the [member avoidance_layers] bitmask is enabled, given a [param layer_number] between 1 and 32. + </description> + </method> <method name="get_navigation_layer_value" qualifiers="const"> <return type="bool" /> <param index="0" name="layer_number" type="int" /> @@ -29,6 +36,14 @@ Returns the [RID] of this region on the [NavigationServer2D]. Combined with [method NavigationServer2D.map_get_closest_point_owner] can be used to identify the [NavigationRegion2D] closest to a point on the merged navigation map. </description> </method> + <method name="set_avoidance_layer_value"> + <return type="void" /> + <param index="0" name="layer_number" type="int" /> + <param index="1" name="value" type="bool" /> + <description> + Based on [param value], enables or disables the specified layer in the [member avoidance_layers] bitmask, given a [param layer_number] between 1 and 32. + </description> + </method> <method name="set_navigation_layer_value"> <return type="void" /> <param index="0" name="layer_number" type="int" /> @@ -39,6 +54,13 @@ </method> </methods> <members> + <member name="avoidance_layers" type="int" setter="set_avoidance_layers" getter="get_avoidance_layers" default="1"> + A bitfield determining all avoidance layers for the avoidance constrain. + </member> + <member name="constrain_avoidance" type="bool" setter="set_constrain_avoidance" getter="get_constrain_avoidance" default="false"> + If [code]true[/code] constraints avoidance agent's with an avoidance mask bit that matches with a bit of the [member avoidance_layers] to the navigation polygon. Due to each navigation polygon outline creating an obstacle and each polygon edge creating an avoidance line constrain keep the navigation polygon shape as simple as possible for performance. + [b]Experimental:[/b] This is an experimental feature and should not be used in production as agent's can get stuck on the navigation polygon corners and edges especially at high frame rate. + </member> <member name="enabled" type="bool" setter="set_enabled" getter="is_enabled" default="true"> Determines if the [NavigationRegion2D] is enabled or disabled. </member> diff --git a/doc/classes/NavigationServer2D.xml b/doc/classes/NavigationServer2D.xml index e96f3dc7e4..f9e6a69452 100644 --- a/doc/classes/NavigationServer2D.xml +++ b/doc/classes/NavigationServer2D.xml @@ -24,6 +24,13 @@ Creates the agent. </description> </method> + <method name="agent_get_avoidance_enabled" qualifiers="const"> + <return type="bool" /> + <param index="0" name="agent" type="RID" /> + <description> + Return [code]true[/code] if the specified [param agent] uses avoidance. + </description> + </method> <method name="agent_get_map" qualifiers="const"> <return type="RID" /> <param index="0" name="agent" type="RID" /> @@ -38,13 +45,46 @@ Returns true if the map got changed the previous frame. </description> </method> - <method name="agent_set_callback"> + <method name="agent_set_avoidance_callback"> <return type="void" /> <param index="0" name="agent" type="RID" /> <param index="1" name="callback" type="Callable" /> <description> - Sets the callback that gets called after each avoidance processing step for the [param agent]. The calculated [code]safe_velocity[/code] will be passed as the first parameter just before the physics calculations. - [b]Note:[/b] Created callbacks are always processed independently of the SceneTree state as long as the agent is on a navigation map and not freed. To disable the dispatch of a callback from an agent use [method agent_set_callback] again with an empty [Callable]. + Sets the callback [Callable] that gets called after each avoidance processing step for the [param agent]. The calculated [code]safe_velocity[/code] will be dispatched with a signal to the object just before the physics calculations. + [b]Note:[/b] Created callbacks are always processed independently of the SceneTree state as long as the agent is on a navigation map and not freed. To disable the dispatch of a callback from an agent use [method agent_set_avoidance_callback] again with an empty [Callable]. + </description> + </method> + <method name="agent_set_avoidance_enabled"> + <return type="void" /> + <param index="0" name="agent" type="RID" /> + <param index="1" name="enabled" type="bool" /> + <description> + If [param enabled] is [code]true[/code] the specified [param agent] uses avoidance. + </description> + </method> + <method name="agent_set_avoidance_layers"> + <return type="void" /> + <param index="0" name="agent" type="RID" /> + <param index="1" name="layers" type="int" /> + <description> + Set the agent's [code]avoidance_layers[/code] bitmask. + </description> + </method> + <method name="agent_set_avoidance_mask"> + <return type="void" /> + <param index="0" name="agent" type="RID" /> + <param index="1" name="mask" type="int" /> + <description> + Set the agent's [code]avoidance_mask[/code] bitmask. + </description> + </method> + <method name="agent_set_avoidance_priority"> + <return type="void" /> + <param index="0" name="agent" type="RID" /> + <param index="1" name="priority" type="float" /> + <description> + Set the agent's [code]avoidance_priority[/code] with a [param priority] between 0.0 (lowest priority) to 1.0 (highest priority). + The specified [param agent] does not adjust the velocity for other agents that would match the [code]avoidance_mask[/code] but have a lower [code] avoidance_priority[/code]. This in turn makes the other agents with lower priority adjust their velocities even more to avoid collision with this agent. </description> </method> <method name="agent_set_map"> @@ -95,20 +135,20 @@ Sets the radius of the agent. </description> </method> - <method name="agent_set_target_velocity"> + <method name="agent_set_time_horizon_agents"> <return type="void" /> <param index="0" name="agent" type="RID" /> - <param index="1" name="target_velocity" type="Vector2" /> + <param index="1" name="time_horizon" type="float" /> <description> - Sets the new target velocity. + The minimal amount of time for which the agent's velocities that are computed by the simulation are safe with respect to other agents. The larger this number, the sooner this agent will respond to the presence of other agents, but the less freedom this agent has in choosing its velocities. A too high value will slow down agents movement considerably. Must be positive. </description> </method> - <method name="agent_set_time_horizon"> + <method name="agent_set_time_horizon_obstacles"> <return type="void" /> <param index="0" name="agent" type="RID" /> - <param index="1" name="time" type="float" /> + <param index="1" name="time_horizon" type="float" /> <description> - The minimal amount of time for which the agent's velocities that are computed by the simulation are safe with respect to other agents. The larger this number, the sooner this agent will respond to the presence of other agents, but the less freedom this agent has in choosing its velocities. Must be positive. + The minimal amount of time for which the agent's velocities that are computed by the simulation are safe with respect to static avoidance obstacles. The larger this number, the sooner this agent will respond to the presence of static avoidance obstacles, but the less freedom this agent has in choosing its velocities. A too high value will slow down agents movement considerably. Must be positive. </description> </method> <method name="agent_set_velocity"> @@ -116,7 +156,15 @@ <param index="0" name="agent" type="RID" /> <param index="1" name="velocity" type="Vector2" /> <description> - Sets the current velocity of the agent. + Sets [param velocity] as the new wanted velocity for the specified [param agent]. The avoidance simulation will try to fulfil this velocity if possible but will modify it to avoid collision with other agent's and obstacles. When an agent is teleported to a new position far away use [method agent_set_velocity_forced] instead to reset the internal velocity state. + </description> + </method> + <method name="agent_set_velocity_forced"> + <return type="void" /> + <param index="0" name="agent" type="RID" /> + <param index="1" name="velocity" type="Vector2" /> + <description> + Replaces the internal velocity in the collision avoidance simulation with [param velocity] for the specified [param agent]. When an agent is teleported to a new position far away this function should be used in the same frame. If called frequently this function can get agents stuck. </description> </method> <method name="free_rid"> @@ -331,6 +379,13 @@ Returns all navigation link [RID]s that are currently assigned to the requested navigation [param map]. </description> </method> + <method name="map_get_obstacles" qualifiers="const"> + <return type="RID[]" /> + <param index="0" name="map" type="RID" /> + <description> + Returns all navigation obstacle [RID]s that are currently assigned to the requested navigation [code]map[/code]. + </description> + </method> <method name="map_get_path" qualifiers="const"> <return type="PackedVector2Array" /> <param index="0" name="map" type="RID" /> @@ -388,6 +443,50 @@ Set the map's link connection radius used to connect links to navigation polygons. </description> </method> + <method name="obstacle_create"> + <return type="RID" /> + <description> + Creates a new navigation obstacle. + </description> + </method> + <method name="obstacle_get_map" qualifiers="const"> + <return type="RID" /> + <param index="0" name="obstacle" type="RID" /> + <description> + Returns the navigation map [RID] the requested [param obstacle] is currently assigned to. + </description> + </method> + <method name="obstacle_set_avoidance_layers"> + <return type="void" /> + <param index="0" name="obstacle" type="RID" /> + <param index="1" name="layers" type="int" /> + <description> + </description> + </method> + <method name="obstacle_set_map"> + <return type="void" /> + <param index="0" name="obstacle" type="RID" /> + <param index="1" name="map" type="RID" /> + <description> + Sets the navigation map [RID] for the obstacle. + </description> + </method> + <method name="obstacle_set_position"> + <return type="void" /> + <param index="0" name="obstacle" type="RID" /> + <param index="1" name="position" type="Vector2" /> + <description> + Sets the position of the obstacle in world space. + </description> + </method> + <method name="obstacle_set_vertices"> + <return type="void" /> + <param index="0" name="obstacle" type="RID" /> + <param index="1" name="vertices" type="PackedVector2Array" /> + <description> + Sets the outline vertices for the obstacle. If the vertices are winded in clockwise order agents will be pushed in by the obstacle, else they will be pushed out. + </description> + </method> <method name="query_path" qualifiers="const"> <return type="void" /> <param index="0" name="parameters" type="NavigationPathQueryParameters2D" /> diff --git a/doc/classes/NavigationServer3D.xml b/doc/classes/NavigationServer3D.xml index 5b79355b61..3fe0793715 100644 --- a/doc/classes/NavigationServer3D.xml +++ b/doc/classes/NavigationServer3D.xml @@ -24,6 +24,13 @@ Creates the agent. </description> </method> + <method name="agent_get_avoidance_enabled" qualifiers="const"> + <return type="bool" /> + <param index="0" name="agent" type="RID" /> + <description> + Returns [code]true[/code] if the provided [param agent] has avoidance enabled. + </description> + </method> <method name="agent_get_map" qualifiers="const"> <return type="RID" /> <param index="0" name="agent" type="RID" /> @@ -31,6 +38,13 @@ Returns the navigation map [RID] the requested [param agent] is currently assigned to. </description> </method> + <method name="agent_get_use_3d_avoidance" qualifiers="const"> + <return type="bool" /> + <param index="0" name="agent" type="RID" /> + <description> + Returns [code]true[/code] if the provided [param agent] uses avoidance in 3D space Vector3(x,y,z) instead of horizontal 2D Vector2(x,y) / Vector3(x,0.0,z). + </description> + </method> <method name="agent_is_map_changed" qualifiers="const"> <return type="bool" /> <param index="0" name="agent" type="RID" /> @@ -38,13 +52,54 @@ Returns true if the map got changed the previous frame. </description> </method> - <method name="agent_set_callback"> + <method name="agent_set_avoidance_callback"> <return type="void" /> <param index="0" name="agent" type="RID" /> <param index="1" name="callback" type="Callable" /> <description> - Sets the callback that gets called after each avoidance processing step for the [param agent]. The calculated [code]safe_velocity[/code] will be passed as the first parameter just before the physics calculations. - [b]Note:[/b] Created callbacks are always processed independently of the SceneTree state as long as the agent is on a navigation map and not freed. To disable the dispatch of a callback from an agent use [method agent_set_callback] again with an empty [Callable]. + Sets the callback [Callable] that gets called after each avoidance processing step for the [param agent]. The calculated [code]safe_velocity[/code] will be dispatched with a signal to the object just before the physics calculations. + [b]Note:[/b] Created callbacks are always processed independently of the SceneTree state as long as the agent is on a navigation map and not freed. To disable the dispatch of a callback from an agent use [method agent_set_avoidance_callback] again with an empty [Callable]. + </description> + </method> + <method name="agent_set_avoidance_enabled"> + <return type="void" /> + <param index="0" name="agent" type="RID" /> + <param index="1" name="enabled" type="bool" /> + <description> + If [param enabled] the provided [param agent] calculates avoidance. + </description> + </method> + <method name="agent_set_avoidance_layers"> + <return type="void" /> + <param index="0" name="agent" type="RID" /> + <param index="1" name="layers" type="int" /> + <description> + Set the agent's [code]avoidance_layers[/code] bitmask. + </description> + </method> + <method name="agent_set_avoidance_mask"> + <return type="void" /> + <param index="0" name="agent" type="RID" /> + <param index="1" name="mask" type="int" /> + <description> + Set the agent's [code]avoidance_mask[/code] bitmask. + </description> + </method> + <method name="agent_set_avoidance_priority"> + <return type="void" /> + <param index="0" name="agent" type="RID" /> + <param index="1" name="priority" type="float" /> + <description> + Set the agent's [code]avoidance_priority[/code] with a [param priority] between 0.0 (lowest priority) to 1.0 (highest priority). + The specified [param agent] does not adjust the velocity for other agents that would match the [code]avoidance_mask[/code] but have a lower [code] avoidance_priority[/code]. This in turn makes the other agents with lower priority adjust their velocities even more to avoid collision with this agent. + </description> + </method> + <method name="agent_set_height"> + <return type="void" /> + <param index="0" name="agent" type="RID" /> + <param index="1" name="height" type="float" /> + <description> + Updates the provided [param agent] [param height]. </description> </method> <method name="agent_set_map"> @@ -95,20 +150,30 @@ Sets the radius of the agent. </description> </method> - <method name="agent_set_target_velocity"> + <method name="agent_set_time_horizon_agents"> + <return type="void" /> + <param index="0" name="agent" type="RID" /> + <param index="1" name="time_horizon" type="float" /> + <description> + The minimal amount of time for which the agent's velocities that are computed by the simulation are safe with respect to other agents. The larger this number, the sooner this agent will respond to the presence of other agents, but the less freedom this agent has in choosing its velocities. A too high value will slow down agents movement considerably. Must be positive. + </description> + </method> + <method name="agent_set_time_horizon_obstacles"> <return type="void" /> <param index="0" name="agent" type="RID" /> - <param index="1" name="target_velocity" type="Vector3" /> + <param index="1" name="time_horizon" type="float" /> <description> - Sets the new target velocity. + The minimal amount of time for which the agent's velocities that are computed by the simulation are safe with respect to static avoidance obstacles. The larger this number, the sooner this agent will respond to the presence of static avoidance obstacles, but the less freedom this agent has in choosing its velocities. A too high value will slow down agents movement considerably. Must be positive. </description> </method> - <method name="agent_set_time_horizon"> + <method name="agent_set_use_3d_avoidance"> <return type="void" /> <param index="0" name="agent" type="RID" /> - <param index="1" name="time" type="float" /> + <param index="1" name="enabled" type="bool" /> <description> - The minimal amount of time for which the agent's velocities that are computed by the simulation are safe with respect to other agents. The larger this number, the sooner this agent will respond to the presence of other agents, but the less freedom this agent has in choosing its velocities. Must be positive. + Sets if the agent uses the 2D avoidance or the 3D avoidance while avoidance is enabled. + If [code]true[/code] the agent calculates avoidance velocities in 3D for the xyz-axis, e.g. for games that take place in air, unterwater or space. The 3D using agent only avoids other 3D avoidance using agent's. The 3D using agent only reacts to radius based avoidance obstacles. The 3D using agent ignores any vertices based obstacles. The 3D using agent only avoids other 3D using agent's. + If [code]false[/code] the agent calculates avoidance velocities in 2D along the xz-axis ignoring the y-axis. The 2D using agent only avoids other 2D avoidance using agent's. The 2D using agent reacts to radius avoidance obstacles. The 2D using agent reacts to vertices based avoidance obstacles. The 2D using agent only avoids other 2D using agent's. 2D using agents will ignore other 2D using agents or obstacles that are below their current position or above their current position including the agents height in 2D avoidance. </description> </method> <method name="agent_set_velocity"> @@ -116,7 +181,15 @@ <param index="0" name="agent" type="RID" /> <param index="1" name="velocity" type="Vector3" /> <description> - Sets the current velocity of the agent. + Sets [param velocity] as the new wanted velocity for the specified [param agent]. The avoidance simulation will try to fulfil this velocity if possible but will modify it to avoid collision with other agent's and obstacles. When an agent is teleported to a new position use [method agent_set_velocity_forced] as well to reset the internal simulation velocity. + </description> + </method> + <method name="agent_set_velocity_forced"> + <return type="void" /> + <param index="0" name="agent" type="RID" /> + <param index="1" name="velocity" type="Vector3" /> + <description> + Replaces the internal velocity in the collision avoidance simulation with [param velocity] for the specified [param agent]. When an agent is teleported to a new position this function should be used in the same frame. If called frequently this function can get agents stuck. </description> </method> <method name="free_rid"> @@ -356,6 +429,13 @@ Returns all navigation link [RID]s that are currently assigned to the requested navigation [param map]. </description> </method> + <method name="map_get_obstacles" qualifiers="const"> + <return type="RID[]" /> + <param index="0" name="map" type="RID" /> + <description> + Returns all navigation obstacle [RID]s that are currently assigned to the requested navigation [code]map[/code]. + </description> + </method> <method name="map_get_path" qualifiers="const"> <return type="PackedVector3Array" /> <param index="0" name="map" type="RID" /> @@ -428,6 +508,59 @@ Sets the map up direction. </description> </method> + <method name="obstacle_create"> + <return type="RID" /> + <description> + Creates a new obstacle. + </description> + </method> + <method name="obstacle_get_map" qualifiers="const"> + <return type="RID" /> + <param index="0" name="obstacle" type="RID" /> + <description> + Returns the navigation map [RID] the requested [param obstacle] is currently assigned to. + </description> + </method> + <method name="obstacle_set_avoidance_layers"> + <return type="void" /> + <param index="0" name="obstacle" type="RID" /> + <param index="1" name="layers" type="int" /> + <description> + Set the obstacles's [code]avoidance_layers[/code] bitmask. + </description> + </method> + <method name="obstacle_set_height"> + <return type="void" /> + <param index="0" name="obstacle" type="RID" /> + <param index="1" name="height" type="float" /> + <description> + Sets the [param height] for the [param obstacle]. In 3D agents will ignore obstacles that are above or below them while using 2D avoidance. + </description> + </method> + <method name="obstacle_set_map"> + <return type="void" /> + <param index="0" name="obstacle" type="RID" /> + <param index="1" name="map" type="RID" /> + <description> + Assigns the [param obstacle] to a navigation map. + </description> + </method> + <method name="obstacle_set_position"> + <return type="void" /> + <param index="0" name="obstacle" type="RID" /> + <param index="1" name="position" type="Vector3" /> + <description> + Updates the [param position] in world space for the [param obstacle]. + </description> + </method> + <method name="obstacle_set_vertices"> + <return type="void" /> + <param index="0" name="obstacle" type="RID" /> + <param index="1" name="vertices" type="PackedVector3Array" /> + <description> + Sets the outline vertices for the obstacle. If the vertices are winded in clockwise order agents will be pushed in by the obstacle, else they will be pushed out. + </description> + </method> <method name="query_path" qualifiers="const"> <return type="void" /> <param index="0" name="parameters" type="NavigationPathQueryParameters3D" /> @@ -590,6 +723,11 @@ </method> </methods> <signals> + <signal name="avoidance_debug_changed"> + <description> + Emitted when avoidance debug settings are changed. Only available in debug builds. + </description> + </signal> <signal name="map_changed"> <param index="0" name="map" type="RID" /> <description> diff --git a/doc/classes/Node.xml b/doc/classes/Node.xml index 1615c928b5..3f79822ca9 100644 --- a/doc/classes/Node.xml +++ b/doc/classes/Node.xml @@ -182,6 +182,20 @@ [b]Note:[/b] For performance reasons, the order of node groups is [i]not[/i] guaranteed. The order of node groups should not be relied upon as it can vary across project runs. </description> </method> + <method name="call_deferred_thread_group" qualifiers="vararg"> + <return type="Variant" /> + <param index="0" name="method" type="StringName" /> + <description> + This function is similar to [method Object.call_deferred] except that the call will take place when the node thread group is processed. If the node thread group processes in sub-threads, then the call will be done on that thread, right before [constant NOTIFICATION_PROCESS] or [constant NOTIFICATION_PHYSICS_PROCESS], the [method _process] or [method _physics_process] or their internal versions are called. + </description> + </method> + <method name="call_thread_safe" qualifiers="vararg"> + <return type="Variant" /> + <param index="0" name="method" type="StringName" /> + <description> + This function ensures that the calling of this function will succeed, no matter whether it's being done from a thread or not. If called from a thread that is not allowed to call the function, the call will become deferred. Otherwise, the call will go through directly. + </description> + </method> <method name="can_process" qualifiers="const"> <return type="bool" /> <description> @@ -564,6 +578,20 @@ [b]Note:[/b] Internal children can only be moved within their expected "internal range" (see [code]internal[/code] parameter in [method add_child]). </description> </method> + <method name="notify_deferred_thread_group"> + <return type="void" /> + <param index="0" name="what" type="int" /> + <description> + Similar to [method call_deferred_thread_group], but for notifications. + </description> + </method> + <method name="notify_thread_safe"> + <return type="void" /> + <param index="0" name="what" type="int" /> + <description> + Similar to [method call_thread_safe], but for notifications. + </description> + </method> <method name="print_orphan_nodes" qualifiers="static"> <return type="void" /> <description> @@ -697,6 +725,14 @@ Sends a [method rpc] to a specific peer identified by [param peer_id] (see [method MultiplayerPeer.set_target_peer]). Returns [code]null[/code]. </description> </method> + <method name="set_deferred_thread_group"> + <return type="void" /> + <param index="0" name="property" type="StringName" /> + <param index="1" name="value" type="Variant" /> + <description> + Similar to [method call_deferred_thread_group], but for setting properties. + </description> + </method> <method name="set_display_folded"> <return type="void" /> <param index="0" name="fold" type="bool" /> @@ -785,6 +821,14 @@ Sets whether this is an instance load placeholder. See [InstancePlaceholder]. </description> </method> + <method name="set_thread_safe"> + <return type="void" /> + <param index="0" name="property" type="StringName" /> + <param index="1" name="value" type="Variant" /> + <description> + Similar to [method call_thread_safe], but for setting properties. + </description> + </method> <method name="update_configuration_warnings"> <return type="void" /> <description> @@ -811,9 +855,24 @@ <member name="process_mode" type="int" setter="set_process_mode" getter="get_process_mode" enum="Node.ProcessMode" default="0"> Can be used to pause or unpause the node, or make the node paused based on the [SceneTree], or make it inherit the process mode from its parent (default). </member> + <member name="process_physics_priority" type="int" setter="set_physics_process_priority" getter="get_physics_process_priority" default="0"> + Similar to [member process_priority] but for [constant NOTIFICATION_PHYSICS_PROCESS], [method _physics_process] or the internal version. + </member> <member name="process_priority" type="int" setter="set_process_priority" getter="get_process_priority" default="0"> The node's priority in the execution order of the enabled processing callbacks (i.e. [constant NOTIFICATION_PROCESS], [constant NOTIFICATION_PHYSICS_PROCESS] and their internal counterparts). Nodes whose process priority value is [i]lower[/i] will have their processing callbacks executed first. </member> + <member name="process_thread_group" type="int" setter="set_process_thread_group" getter="get_process_thread_group" enum="Node.ProcessThreadGroup" default="0"> + Set the process thread group for this node (basically, whether it receives [constant NOTIFICATION_PROCESS], [constant NOTIFICATION_PHYSICS_PROCESS], [method _process] or [method _physics_process] (and the internal versions) on the main thread or in a sub-thread. + By default, the thread group is [constant PROCESS_THREAD_GROUP_INHERIT], which means that this node belongs to the same thread group as the parent node. The thread groups means that nodes in a specific thread group will process together, separate to other thread groups (depending on [member process_thread_group_order]). If the value is set is [constant PROCESS_THREAD_GROUP_SUB_THREAD], this thread group will occur on a sub thread (not the main thread), otherwise if set to [constant PROCESS_THREAD_GROUP_MAIN_THREAD] it will process on the main thread. If there is not a parent or grandparent node set to something other than inherit, the node will belong to the [i]default thread group[/i]. This default group will process on the main thread and its group order is 0. + During processing in a sub-thread, accessing most functions in nodes outside the thread group is forbidden (and it will result in an error in debug mode). Use [method Object.call_deferred], [method call_thread_safe], [method call_deferred_thread_group] and the likes in order to communicate from the thread groups to the main thread (or to other thread groups). + To better understand process thread groups, the idea is that any node set to any other value than [constant PROCESS_THREAD_GROUP_INHERIT] will include any children (and grandchildren) nodes set to inherit into its process thread group. this means that the processing of all the nodes in the group will happen together, at the same time as the node including them. + </member> + <member name="process_thread_group_order" type="int" setter="set_process_thread_group_order" getter="get_process_thread_group_order"> + Change the process thread group order. Groups with a lesser order will process before groups with a greater order. This is useful when a large amount of nodes process in sub thread and, afterwards, another group wants to collect their result in the main thread, as an example. + </member> + <member name="process_thread_messages" type="int" setter="set_process_thread_messages" getter="get_process_thread_messages" enum="Node.ProcessThreadMessages"> + Set whether the current thread group will process messages (calls to [method call_deferred_thread_group] on threads, and whether it wants to receive them during regular process or physics process callbacks. + </member> <member name="scene_file_path" type="String" setter="set_scene_file_path" getter="get_scene_file_path"> If a scene is instantiated from a file, its topmost node contains the absolute file path from which it was loaded in [member scene_file_path] (e.g. [code]res://levels/1.tscn[/code]). Otherwise, [member scene_file_path] is set to an empty string. </member> @@ -1033,6 +1092,21 @@ <constant name="PROCESS_MODE_DISABLED" value="4" enum="ProcessMode"> Never process. Completely disables processing, ignoring the [SceneTree]'s paused property. This is the inverse of [constant PROCESS_MODE_ALWAYS]. </constant> + <constant name="PROCESS_THREAD_GROUP_INHERIT" value="0" enum="ProcessThreadGroup"> + If the [member process_thread_group] property is sent to this, the node will belong to any parent (or grandparent) node that has a thread group mode that is not inherit. See [member process_thread_group] for more information. + </constant> + <constant name="PROCESS_THREAD_GROUP_MAIN_THREAD" value="1" enum="ProcessThreadGroup"> + Process this node (and children nodes set to inherit) on the main thread. See [member process_thread_group] for more information. + </constant> + <constant name="PROCESS_THREAD_GROUP_SUB_THREAD" value="2" enum="ProcessThreadGroup"> + Process this node (and children nodes set to inherit) on a sub-thread. See [member process_thread_group] for more information. + </constant> + <constant name="FLAG_PROCESS_THREAD_MESSAGES" value="1" enum="ProcessThreadMessages"> + </constant> + <constant name="FLAG_PROCESS_THREAD_MESSAGES_PHYSICS" value="2" enum="ProcessThreadMessages"> + </constant> + <constant name="FLAG_PROCESS_THREAD_MESSAGES_ALL" value="3" enum="ProcessThreadMessages"> + </constant> <constant name="DUPLICATE_SIGNALS" value="1" enum="DuplicateFlags"> Duplicate the node's signals. </constant> diff --git a/doc/classes/Object.xml b/doc/classes/Object.xml index 619276b154..0a52df1e49 100644 --- a/doc/classes/Object.xml +++ b/doc/classes/Object.xml @@ -354,6 +354,12 @@ Returns [code]true[/code] if the object is allowed to translate messages with [method tr] and [method tr_n]. See also [method set_message_translation]. </description> </method> + <method name="cancel_free"> + <return type="void" /> + <description> + If this method is called during [constant NOTIFICATION_PREDELETE], this object will reject being freed and will remain allocated. This is mostly an internal function used for error handling to avoid the user from freeing objects when they are not intended to. + </description> + </method> <method name="connect"> <return type="int" enum="Error" /> <param index="0" name="signal" type="StringName" /> diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index ef335bd526..2c54e364c7 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -579,6 +579,33 @@ <member name="debug/shader_language/warnings/unused_varying" type="bool" setter="" getter="" default="true"> When set to [code]true[/code], produces a warning when a varying is never used. </member> + <member name="debug/shapes/avoidance/agents_radius_color" type="Color" setter="" getter="" default="Color(1, 1, 0, 0.25)"> + Color of the avoidance agents radius, visible when "Visible Avoidance" is enabled in the Debug menu. + </member> + <member name="debug/shapes/avoidance/enable_agents_radius" type="bool" setter="" getter="" default="true"> + If enabled, displays avoidance agents radius when "Visible Avoidance" is enabled in the Debug menu. + </member> + <member name="debug/shapes/avoidance/enable_obstacles_radius" type="bool" setter="" getter="" default="true"> + If enabled, displays avoidance obstacles radius when "Visible Avoidance" is enabled in the Debug menu. + </member> + <member name="debug/shapes/avoidance/enable_obstacles_static" type="bool" setter="" getter="" default="true"> + If enabled, displays static avoidance obstacles when "Visible Avoidance" is enabled in the Debug menu. + </member> + <member name="debug/shapes/avoidance/obstacles_radius_color" type="Color" setter="" getter="" default="Color(1, 0.5, 0, 0.25)"> + Color of the avoidance obstacles radius, visible when "Visible Avoidance" is enabled in the Debug menu. + </member> + <member name="debug/shapes/avoidance/obstacles_static_edge_pushin_color" type="Color" setter="" getter="" default="Color(1, 0, 0, 1)"> + Color of the static avoidance obstacles edges when their vertices are winded in order to push agents in, visible when "Visible Avoidance" is enabled in the Debug menu. + </member> + <member name="debug/shapes/avoidance/obstacles_static_edge_pushout_color" type="Color" setter="" getter="" default="Color(1, 1, 0, 1)"> + Color of the static avoidance obstacles edges when their vertices are winded in order to push agents out, visible when "Visible Avoidance" is enabled in the Debug menu. + </member> + <member name="debug/shapes/avoidance/obstacles_static_face_pushin_color" type="Color" setter="" getter="" default="Color(1, 0, 0, 0)"> + Color of the static avoidance obstacles faces when their vertices are winded in order to push agents in, visible when "Visible Avoidance" is enabled in the Debug menu. + </member> + <member name="debug/shapes/avoidance/obstacles_static_face_pushout_color" type="Color" setter="" getter="" default="Color(1, 1, 0, 0.5)"> + Color of the static avoidance obstacles faces when their vertices are winded in order to push agents out, visible when "Visible Avoidance" is enabled in the Debug menu. + </member> <member name="debug/shapes/collision/contact_color" type="Color" setter="" getter="" default="Color(1, 0.2, 0.1, 0.8)"> Color of the contact points between collision shapes, visible when "Visible Collision Shapes" is enabled in the Debug menu. </member> @@ -1777,6 +1804,102 @@ <member name="layer_names/3d_render/layer_20" type="String" setter="" getter="" default=""""> Optional name for the 3D render layer 20. If left empty, the layer will display as "Layer 20". </member> + <member name="layer_names/avoidance/layer_1" type="String" setter="" getter="" default=""""> + Optional name for the navigation avoidance layer 1. If left empty, the layer will display as "Layer 1". + </member> + <member name="layer_names/avoidance/layer_2" type="String" setter="" getter="" default=""""> + Optional name for the navigation avoidance layer 2. If left empty, the layer will display as "Layer 2". + </member> + <member name="layer_names/avoidance/layer_3" type="String" setter="" getter="" default=""""> + Optional name for the navigation avoidance layer 3. If left empty, the layer will display as "Layer 3". + </member> + <member name="layer_names/avoidance/layer_4" type="String" setter="" getter="" default=""""> + Optional name for the navigation avoidance layer 4. If left empty, the layer will display as "Layer 4". + </member> + <member name="layer_names/avoidance/layer_5" type="String" setter="" getter="" default=""""> + Optional name for the navigation avoidance layer 5. If left empty, the layer will display as "Layer 5". + </member> + <member name="layer_names/avoidance/layer_6" type="String" setter="" getter="" default=""""> + Optional name for the navigation avoidance layer 6. If left empty, the layer will display as "Layer 6". + </member> + <member name="layer_names/avoidance/layer_7" type="String" setter="" getter="" default=""""> + Optional name for the navigation avoidance layer 7. If left empty, the layer will display as "Layer 7". + </member> + <member name="layer_names/avoidance/layer_8" type="String" setter="" getter="" default=""""> + Optional name for the navigation avoidance layer 8. If left empty, the layer will display as "Layer 8". + </member> + <member name="layer_names/avoidance/layer_9" type="String" setter="" getter="" default=""""> + Optional name for the navigation avoidance layer 9. If left empty, the layer will display as "Layer 9". + </member> + <member name="layer_names/avoidance/layer_10" type="String" setter="" getter="" default=""""> + Optional name for the navigation avoidance layer 10. If left empty, the layer will display as "Layer 10". + </member> + <member name="layer_names/avoidance/layer_11" type="String" setter="" getter="" default=""""> + Optional name for the navigation avoidance layer 11. If left empty, the layer will display as "Layer 11". + </member> + <member name="layer_names/avoidance/layer_12" type="String" setter="" getter="" default=""""> + Optional name for the navigation avoidance layer 12. If left empty, the layer will display as "Layer 12". + </member> + <member name="layer_names/avoidance/layer_13" type="String" setter="" getter="" default=""""> + Optional name for the navigation avoidance layer 13. If left empty, the layer will display as "Layer 13". + </member> + <member name="layer_names/avoidance/layer_14" type="String" setter="" getter="" default=""""> + Optional name for the navigation avoidance layer 14. If left empty, the layer will display as "Layer 14". + </member> + <member name="layer_names/avoidance/layer_15" type="String" setter="" getter="" default=""""> + Optional name for the navigation avoidance layer 15. If left empty, the layer will display as "Layer 15". + </member> + <member name="layer_names/avoidance/layer_16" type="String" setter="" getter="" default=""""> + Optional name for the navigation avoidance layer 16. If left empty, the layer will display as "Layer 16". + </member> + <member name="layer_names/avoidance/layer_17" type="String" setter="" getter="" default=""""> + Optional name for the navigation avoidance layer 17. If left empty, the layer will display as "Layer 17". + </member> + <member name="layer_names/avoidance/layer_18" type="String" setter="" getter="" default=""""> + Optional name for the navigation avoidance layer 18. If left empty, the layer will display as "Layer 18". + </member> + <member name="layer_names/avoidance/layer_19" type="String" setter="" getter="" default=""""> + Optional name for the navigation avoidance layer 19. If left empty, the layer will display as "Layer 19". + </member> + <member name="layer_names/avoidance/layer_20" type="String" setter="" getter="" default=""""> + Optional name for the navigation avoidance layer 20. If left empty, the layer will display as "Layer 20". + </member> + <member name="layer_names/avoidance/layer_21" type="String" setter="" getter="" default=""""> + Optional name for the navigation avoidance layer 21. If left empty, the layer will display as "Layer 21". + </member> + <member name="layer_names/avoidance/layer_22" type="String" setter="" getter="" default=""""> + Optional name for the navigation avoidance layer 22. If left empty, the layer will display as "Layer 22". + </member> + <member name="layer_names/avoidance/layer_23" type="String" setter="" getter="" default=""""> + Optional name for the navigation avoidance layer 23. If left empty, the layer will display as "Layer 23". + </member> + <member name="layer_names/avoidance/layer_24" type="String" setter="" getter="" default=""""> + Optional name for the navigation avoidance layer 24. If left empty, the layer will display as "Layer 24". + </member> + <member name="layer_names/avoidance/layer_25" type="String" setter="" getter="" default=""""> + Optional name for the navigation avoidance layer 25. If left empty, the layer will display as "Layer 25". + </member> + <member name="layer_names/avoidance/layer_26" type="String" setter="" getter="" default=""""> + Optional name for the navigation avoidance layer 26. If left empty, the layer will display as "Layer 26". + </member> + <member name="layer_names/avoidance/layer_27" type="String" setter="" getter="" default=""""> + Optional name for the navigation avoidance layer 27. If left empty, the layer will display as "Layer 27". + </member> + <member name="layer_names/avoidance/layer_28" type="String" setter="" getter="" default=""""> + Optional name for the navigation avoidance layer 28. If left empty, the layer will display as "Layer 28". + </member> + <member name="layer_names/avoidance/layer_29" type="String" setter="" getter="" default=""""> + Optional name for the navigation avoidance layer 29. If left empty, the layer will display as "Layer 29". + </member> + <member name="layer_names/avoidance/layer_30" type="String" setter="" getter="" default=""""> + Optional name for the navigation avoidance layer 30. If left empty, the layer will display as "Layer 30". + </member> + <member name="layer_names/avoidance/layer_31" type="String" setter="" getter="" default=""""> + Optional name for the navigation avoidance layer 31. If left empty, the layer will display as "Layer 31". + </member> + <member name="layer_names/avoidance/layer_32" type="String" setter="" getter="" default=""""> + Optional name for the navigation avoidance layer 32. If left empty, the layer will display as "Layer 32". + </member> <member name="memory/limits/message_queue/max_size_mb" type="int" setter="" getter="" default="32"> Godot uses a message queue to defer some function calls. If you run out of space on it (you will see an error), you can increase the size here. </member> @@ -1801,6 +1924,12 @@ <member name="navigation/3d/default_link_connection_radius" type="float" setter="" getter="" default="1.0"> Default link connection radius for 3D navigation maps. See [method NavigationServer3D.map_set_link_connection_radius]. </member> + <member name="navigation/avoidance/thread_model/avoidance_use_high_priority_threads" type="bool" setter="" getter="" default="true"> + If enabled and avoidance calculations use multiple threads the threads run with high priority. + </member> + <member name="navigation/avoidance/thread_model/avoidance_use_multiple_threads" type="bool" setter="" getter="" default="true"> + If enabled the avoidance calculations use multiple threads. + </member> <member name="network/limits/debugger/max_chars_per_second" type="int" setter="" getter="" default="32768"> Maximum number of characters allowed to send as output from the debugger. Over this value, content is dropped. This helps not to stall the debugger connection. </member> diff --git a/doc/classes/Tree.xml b/doc/classes/Tree.xml index 2a3cba3a44..afe0088a26 100644 --- a/doc/classes/Tree.xml +++ b/doc/classes/Tree.xml @@ -60,8 +60,11 @@ </method> <method name="edit_selected"> <return type="bool" /> + <param index="0" name="force_edit" type="bool" default="false" /> <description> - Edits the selected tree item as if it was clicked. The item must be set editable with [method TreeItem.set_editable]. Returns [code]true[/code] if the item could be edited. Fails if no item is selected. + Edits the selected tree item as if it was clicked. + Either the item must be set editable with [method TreeItem.set_editable] or [param force_edit] must be [code]true[/code]. + Returns [code]true[/code] if the item could be edited. Fails if no item is selected. </description> </method> <method name="ensure_cursor_is_visible"> diff --git a/doc/classes/TreeItem.xml b/doc/classes/TreeItem.xml index b97b4bf17d..6b6851daf1 100644 --- a/doc/classes/TreeItem.xml +++ b/doc/classes/TreeItem.xml @@ -226,11 +226,19 @@ Returns the next sibling TreeItem in the tree or a null object if there is none. </description> </method> + <method name="get_next_in_tree"> + <return type="TreeItem" /> + <param index="0" name="wrap" type="bool" default="false" /> + <description> + Returns the next TreeItem in the tree (in the context of a depth-first search) or a [code]null[/code] object if there is none. + If [param wrap] is enabled, the method will wrap around to the first element in the tree when called on the last element, otherwise it returns [code]null[/code]. + </description> + </method> <method name="get_next_visible"> <return type="TreeItem" /> <param index="0" name="wrap" type="bool" default="false" /> <description> - Returns the next visible sibling TreeItem in the tree or a null object if there is none. + Returns the next visible TreeItem in the tree (in the context of a depth-first search) or a [code]null[/code] object if there is none. If [param wrap] is enabled, the method will wrap around to the first visible element in the tree when called on the last visible element, otherwise it returns [code]null[/code]. </description> </method> @@ -246,11 +254,19 @@ Returns the previous sibling TreeItem in the tree or a null object if there is none. </description> </method> + <method name="get_prev_in_tree"> + <return type="TreeItem" /> + <param index="0" name="wrap" type="bool" default="false" /> + <description> + Returns the previous TreeItem in the tree (in the context of a depth-first search) or a [code]null[/code] object if there is none. + If [param wrap] is enabled, the method will wrap around to the last element in the tree when called on the first visible element, otherwise it returns [code]null[/code]. + </description> + </method> <method name="get_prev_visible"> <return type="TreeItem" /> <param index="0" name="wrap" type="bool" default="false" /> <description> - Returns the previous visible sibling TreeItem in the tree or a null object if there is none. + Returns the previous visible sibling TreeItem in the tree (in the context of a depth-first search) or a [code]null[/code] object if there is none. If [param wrap] is enabled, the method will wrap around to the last visible element in the tree when called on the first visible element, otherwise it returns [code]null[/code]. </description> </method> diff --git a/doc/classes/ViewportTexture.xml b/doc/classes/ViewportTexture.xml index bf66e466d7..2f62199813 100644 --- a/doc/classes/ViewportTexture.xml +++ b/doc/classes/ViewportTexture.xml @@ -17,6 +17,7 @@ <members> <member name="viewport_path" type="NodePath" setter="set_viewport_path_in_scene" getter="get_viewport_path_in_scene" default="NodePath("")"> The path to the [Viewport] node to display. This is relative to the scene root, not to the node which uses the texture. + [b]Note:[/b] In the editor, it is automatically updated when the target viewport's node path changes due to renaming or moving the viewport or its ancestors. At runtime, it may not be able to automatically update due to the inability to determine the scene root. </member> </members> </class> diff --git a/editor/debugger/script_editor_debugger.cpp b/editor/debugger/script_editor_debugger.cpp index a696e1ff1f..b4ca499bcf 100644 --- a/editor/debugger/script_editor_debugger.cpp +++ b/editor/debugger/script_editor_debugger.cpp @@ -1704,6 +1704,14 @@ void ScriptEditorDebugger::remove_debugger_tab(Control *p_control) { p_control->queue_free(); } +int ScriptEditorDebugger::get_current_debugger_tab() const { + return tabs->get_current_tab(); +} + +void ScriptEditorDebugger::switch_to_debugger(int p_debugger_tab_idx) { + tabs->set_current_tab(p_debugger_tab_idx); +} + void ScriptEditorDebugger::send_message(const String &p_message, const Array &p_args) { _put_msg(p_message, p_args); } diff --git a/editor/debugger/script_editor_debugger.h b/editor/debugger/script_editor_debugger.h index 1659bbee8d..336a113163 100644 --- a/editor/debugger/script_editor_debugger.h +++ b/editor/debugger/script_editor_debugger.h @@ -284,6 +284,8 @@ public: void add_debugger_tab(Control *p_control); void remove_debugger_tab(Control *p_control); + int get_current_debugger_tab() const; + void switch_to_debugger(int p_debugger_tab_idx); void send_message(const String &p_message, const Array &p_args); void toggle_profiler(const String &p_profiler, bool p_enable, const Array &p_data); diff --git a/editor/editor_data.cpp b/editor/editor_data.cpp index 753f54b807..5d3037b4ec 100644 --- a/editor/editor_data.cpp +++ b/editor/editor_data.cpp @@ -300,7 +300,7 @@ void EditorData::get_editor_breakpoints(List<String> *p_breakpoints) { } } -Dictionary EditorData::get_editor_states() const { +Dictionary EditorData::get_editor_plugin_states() const { Dictionary metadata; for (int i = 0; i < editor_plugins.size(); i++) { Dictionary state = editor_plugins[i]->get_state(); @@ -319,7 +319,7 @@ Dictionary EditorData::get_scene_editor_states(int p_idx) const { return es.editor_states; } -void EditorData::set_editor_states(const Dictionary &p_states) { +void EditorData::set_editor_plugin_states(const Dictionary &p_states) { if (p_states.is_empty()) { for (EditorPlugin *ep : editor_plugins) { ep->clear(); @@ -891,7 +891,7 @@ void EditorData::save_edited_scene_state(EditorSelection *p_selection, EditorSel es.selection = p_selection->get_full_selected_node_list(); es.history_current = p_history->current_elem_idx; es.history_stored = p_history->history; - es.editor_states = get_editor_states(); + es.editor_states = get_editor_plugin_states(); es.custom_state = p_custom; } @@ -907,7 +907,7 @@ Dictionary EditorData::restore_edited_scene_state(EditorSelection *p_selection, for (Node *E : es.selection) { p_selection->add_node(E); } - set_editor_states(es.editor_states); + set_editor_plugin_states(es.editor_states); return es.custom_state; } diff --git a/editor/editor_data.h b/editor/editor_data.h index d4a2f534cd..7ca04b5680 100644 --- a/editor/editor_data.h +++ b/editor/editor_data.h @@ -156,9 +156,9 @@ public: void copy_object_params(Object *p_object); void paste_object_params(Object *p_object); - Dictionary get_editor_states() const; + Dictionary get_editor_plugin_states() const; Dictionary get_scene_editor_states(int p_idx) const; - void set_editor_states(const Dictionary &p_states); + void set_editor_plugin_states(const Dictionary &p_states); void get_editor_breakpoints(List<String> *p_breakpoints); void clear_editor_states(); void save_editor_external_data(); diff --git a/editor/editor_help_search.cpp b/editor/editor_help_search.cpp index 204d918989..6aa508f40e 100644 --- a/editor/editor_help_search.cpp +++ b/editor/editor_help_search.cpp @@ -117,8 +117,11 @@ void EditorHelpSearch::_notification(int p_what) { _update_icons(); } break; - case NOTIFICATION_ENTER_TREE: { + case NOTIFICATION_READY: { connect("confirmed", callable_mp(this, &EditorHelpSearch::_confirmed)); + } break; + + case NOTIFICATION_THEME_CHANGED: { _update_icons(); } break; diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 864d45230a..6f763d59f4 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -67,6 +67,7 @@ #include "editor/audio_stream_preview.h" #include "editor/debugger/editor_debugger_node.h" +#include "editor/debugger/script_editor_debugger.h" #include "editor/dependency_editor.h" #include "editor/editor_about.h" #include "editor/editor_audio_buses.h" @@ -147,6 +148,7 @@ #include "editor/project_settings_editor.h" #include "editor/register_exporters.h" #include "editor/scene_tree_dock.h" +#include "editor/window_wrapper.h" #include <stdio.h> #include <stdlib.h> @@ -156,6 +158,8 @@ EditorNode *EditorNode::singleton = nullptr; // The metadata key used to store and retrieve the version text to copy to the clipboard. static const String META_TEXT_TO_COPY = "text_to_copy"; +static const String EDITOR_NODE_CONFIG_SECTION = "EditorNode"; + class AcceptDialogAutoReparent : public AcceptDialog { GDCLASS(AcceptDialogAutoReparent, AcceptDialog); @@ -636,6 +640,8 @@ void EditorNode::_notification(int p_what) { } break; case NOTIFICATION_ENTER_TREE: { + get_tree()->set_disable_node_threading(true); // No node threading while running editor. + Engine::get_singleton()->set_editor_hint(true); Window *window = get_window(); @@ -1091,7 +1097,7 @@ void EditorNode::_sources_changed(bool p_exist) { EditorResourcePreview::get_singleton()->start(); } - _load_docks(); + _load_editor_layout(); if (!defer_load_scene.is_empty()) { Engine::get_singleton()->startup_benchmark_begin_measure("editor_load_scene"); @@ -1460,39 +1466,28 @@ void EditorNode::_dialog_display_load_error(String p_file, Error p_error) { } } -void EditorNode::_get_scene_metadata(const String &p_file) { +void EditorNode::_load_editor_plugin_states_from_config(const Ref<ConfigFile> &p_config_file) { Node *scene = editor_data.get_edited_scene_root(); if (!scene) { return; } - String path = EditorPaths::get_singleton()->get_project_settings_dir().path_join(p_file.get_file() + "-editstate-" + p_file.md5_text() + ".cfg"); - - Ref<ConfigFile> cf; - cf.instantiate(); - - Error err = cf->load(path); - if (err != OK || !cf->has_section("editor_states")) { - // Must not exist. - return; - } - List<String> esl; - cf->get_section_keys("editor_states", &esl); + p_config_file->get_section_keys("editor_states", &esl); Dictionary md; for (const String &E : esl) { - Variant st = cf->get_value("editor_states", E); + Variant st = p_config_file->get_value("editor_states", E); if (st.get_type() != Variant::NIL) { md[E] = st; } } - editor_data.set_editor_states(md); + editor_data.set_editor_plugin_states(md); } -void EditorNode::_set_scene_metadata(const String &p_file, int p_idx) { +void EditorNode::_save_editor_states(const String &p_file, int p_idx) { Node *scene = editor_data.get_edited_scene_root(p_idx); if (!scene) { @@ -1505,20 +1500,27 @@ void EditorNode::_set_scene_metadata(const String &p_file, int p_idx) { cf.instantiate(); Dictionary md; - if (p_idx < 0 || editor_data.get_edited_scene() == p_idx) { - md = editor_data.get_editor_states(); + md = editor_data.get_editor_plugin_states(); } else { md = editor_data.get_scene_editor_states(p_idx); } List<Variant> keys; md.get_key_list(&keys); - for (const Variant &E : keys) { cf->set_value("editor_states", E, md[E]); } + // Save the currently selected nodes. + + List<Node *> selection = editor_selection->get_full_selected_node_list(); + TypedArray<NodePath> selection_paths; + for (Node *selected_node : selection) { + selection_paths.push_back(selected_node->get_path()); + } + cf->set_value("editor_states", "selected_nodes", selection_paths); + Error err = cf->save(path); ERR_FAIL_COND_MSG(err != OK, "Cannot save config file to '" + path + "'."); } @@ -1810,7 +1812,7 @@ void EditorNode::_save_scene(String p_file, int idx) { _reset_animation_players(scene, &anim_backups); save_default_environment(); - _set_scene_metadata(p_file, idx); + _save_editor_states(p_file, idx); Ref<PackedScene> sdata; @@ -2022,7 +2024,7 @@ void EditorNode::_dialog_action(String p_file) { save_default_environment(); _save_scene_with_preview(p_file, scene_idx); _add_to_recent_scenes(p_file); - save_layout(); + save_editor_layout_delayed(); if (scene_idx != -1) { _discard_changes(); @@ -2602,7 +2604,7 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) { if (scene_idx != -1) { _discard_changes(); } - save_layout(); + save_editor_layout_delayed(); } else { show_save_accept(vformat(TTR("%s no longer exists! Please specify a new save location."), scene->get_scene_file_path().get_base_dir()), TTR("OK")); } @@ -3081,7 +3083,7 @@ int EditorNode::_next_unsaved_scene(bool p_valid_filename, int p_start) { void EditorNode::_exit_editor(int p_exit_code) { exiting = true; resource_preview->stop(); // Stop early to avoid crashes. - _save_docks(); + _save_editor_layout(); // Dim the editor window while it's quitting to make it clearer that it's busy. dim_editor(true); @@ -3750,7 +3752,14 @@ Error EditorNode::load_scene(const String &p_scene, bool p_ignore_broken_deps, b new_scene->set_scene_instance_state(Ref<SceneState>()); set_edited_scene(new_scene); - _get_scene_metadata(p_scene); + + String config_file_path = EditorPaths::get_singleton()->get_project_settings_dir().path_join(p_scene.get_file() + "-editstate-" + p_scene.md5_text() + ".cfg"); + Ref<ConfigFile> editor_state_cf; + editor_state_cf.instantiate(); + Error editor_state_cf_err = editor_state_cf->load(config_file_path); + if (editor_state_cf_err == OK || editor_state_cf->has_section("editor_states")) { + _load_editor_plugin_states_from_config(editor_state_cf); + } _update_title(); _update_scene_tabs(); @@ -3771,8 +3780,20 @@ Error EditorNode::load_scene(const String &p_scene, bool p_ignore_broken_deps, b push_item(new_scene); + // Load the selected nodes. + if (editor_state_cf->has_section_key("editor_states", "selected_nodes")) { + TypedArray<NodePath> selected_node_list = editor_state_cf->get_value("editor_states", "selected_nodes", TypedArray<String>()); + + for (int i = 0; i < selected_node_list.size(); i++) { + Node *selected_node = new_scene->get_node_or_null(selected_node_list[i]); + if (selected_node) { + editor_selection->add_node(selected_node); + } + } + } + if (!restoring_scenes) { - save_layout(); + save_editor_layout_delayed(); } return OK; @@ -4470,67 +4491,66 @@ void EditorNode::_copy_warning(const String &p_str) { DisplayServer::get_singleton()->clipboard_set(warning->get_text()); } -void EditorNode::_dock_floating_close_request(Control *p_control) { - // Through the MarginContainer to the Window. - Window *window = static_cast<Window *>(p_control->get_parent()->get_parent()); - int window_slot = window->get_meta("dock_slot"); +void EditorNode::_dock_floating_close_request(WindowWrapper *p_wrapper) { + int dock_slot_num = p_wrapper->get_meta("dock_slot"); + int dock_slot_index = p_wrapper->get_meta("dock_index"); - p_control->get_parent()->remove_child(p_control); - dock_slot[window_slot]->add_child(p_control); - dock_slot[window_slot]->move_child(p_control, MIN((int)window->get_meta("dock_index"), dock_slot[window_slot]->get_tab_count() - 1)); - dock_slot[window_slot]->set_current_tab(dock_slot[window_slot]->get_tab_idx_from_control(p_control)); - dock_slot[window_slot]->set_tab_title(dock_slot[window_slot]->get_tab_idx_from_control(p_control), TTRGET(p_control->get_name())); + // Give back the dock to the original owner. + Control *dock = p_wrapper->release_wrapped_control(); - window->queue_free(); + dock_slot[dock_slot_num]->add_child(dock); + dock_slot[dock_slot_num]->move_child(dock, MIN(dock_slot_index, dock_slot[dock_slot_num]->get_tab_count())); + dock_slot[dock_slot_num]->set_current_tab(dock_slot_index); - _update_dock_containers(); + floating_docks.erase(p_wrapper); + p_wrapper->queue_free(); - floating_docks.erase(p_control); + _update_dock_containers(); _edit_current(); } -void EditorNode::_dock_make_float() { +void EditorNode::_dock_make_selected_float() { Control *dock = dock_slot[dock_popup_selected_idx]->get_current_tab_control(); - ERR_FAIL_COND(!dock); + _dock_make_float(dock, dock_popup_selected_idx); + + dock_select_popup->hide(); + _edit_current(); +} + +void EditorNode::_dock_make_float(Control *p_dock, int p_slot_index, bool p_show_window) { + ERR_FAIL_COND(!p_dock); Size2 borders = Size2(4, 4) * EDSCALE; // Remember size and position before removing it from the main window. - Size2 dock_size = dock->get_size() + borders * 2; - Point2 dock_screen_pos = dock->get_global_position() + get_tree()->get_root()->get_position() - borders; - - int dock_index = dock->get_index(false); - dock_slot[dock_popup_selected_idx]->remove_child(dock); - - Window *window = memnew(Window); - window->set_title(TTRGET(dock->get_name())); - Panel *p = memnew(Panel); - p->add_theme_style_override("panel", gui_base->get_theme_stylebox(SNAME("PanelForeground"), SNAME("EditorStyles"))); - p->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT); - window->add_child(p); - MarginContainer *margin = memnew(MarginContainer); - margin->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT); - margin->add_theme_constant_override("margin_right", borders.width); - margin->add_theme_constant_override("margin_top", borders.height); - margin->add_theme_constant_override("margin_left", borders.width); - margin->add_theme_constant_override("margin_bottom", borders.height); - window->add_child(margin); - dock->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT); - margin->add_child(dock); - window->set_wrap_controls(true); - window->set_size(dock_size); - window->set_position(dock_screen_pos); - window->set_transient(true); - window->connect("close_requested", callable_mp(this, &EditorNode::_dock_floating_close_request).bind(dock)); - window->set_meta("dock_slot", dock_popup_selected_idx); - window->set_meta("dock_index", dock_index); - gui_base->add_child(window); + Size2 dock_size = p_dock->get_size() + borders * 2; + Point2 dock_screen_pos = p_dock->get_screen_position(); + + int dock_index = p_dock->get_index() - 1; + dock_slot[p_slot_index]->remove_child(p_dock); + + WindowWrapper *wrapper = memnew(WindowWrapper); + wrapper->set_window_title(vformat(TTR("%s - Godot Engine"), p_dock->get_name())); + wrapper->set_margins_enabled(true); + + gui_base->add_child(wrapper); + + wrapper->set_wrapped_control(p_dock); + wrapper->set_meta("dock_slot", p_slot_index); + wrapper->set_meta("dock_index", dock_index); + wrapper->set_meta("dock_name", p_dock->get_name().operator String()); + + wrapper->connect("window_close_requested", callable_mp(this, &EditorNode::_dock_floating_close_request).bind(wrapper)); dock_select_popup->hide(); + if (p_show_window) { + wrapper->restore_window(Rect2i(dock_screen_pos, dock_size), get_window()->get_current_screen()); + } + _update_dock_containers(); - floating_docks.push_back(dock); + floating_docks.push_back(wrapper); _edit_current(); } @@ -4607,7 +4627,7 @@ void EditorNode::_dock_select_input(const Ref<InputEvent> &p_input) { _update_dock_containers(); _edit_current(); - _save_docks(); + _save_editor_layout(); } } } @@ -4633,7 +4653,7 @@ void EditorNode::_dock_move_left() { dock_slot[dock_popup_selected_idx]->move_child(current_ctl, prev_ctl->get_index(false)); dock_select->queue_redraw(); _edit_current(); - _save_docks(); + _save_editor_layout(); } void EditorNode::_dock_move_right() { @@ -4645,7 +4665,7 @@ void EditorNode::_dock_move_right() { dock_slot[dock_popup_selected_idx]->move_child(next_ctl, current_ctl->get_index(false)); dock_select->queue_redraw(); _edit_current(); - _save_docks(); + _save_editor_layout(); } void EditorNode::_dock_select_draw() { @@ -4734,7 +4754,7 @@ void EditorNode::_dock_select_draw() { } } -void EditorNode::_save_docks() { +void EditorNode::_save_editor_layout() { if (waiting_for_first_scan) { return; // Scanning, do not touch docks. } @@ -4744,7 +4764,8 @@ void EditorNode::_save_docks() { config->load(EditorPaths::get_singleton()->get_project_settings_dir().path_join("editor_layout.cfg")); _save_docks_to_config(config, "docks"); - _save_open_scenes_to_config(config, "EditorNode"); + _save_open_scenes_to_config(config); + _save_central_editor_layout_to_config(config); editor_data.get_plugin_window_layout(config); config->save(EditorPaths::get_singleton()->get_project_settings_dir().path_join("editor_layout.cfg")); @@ -4770,12 +4791,41 @@ void EditorNode::_save_docks_to_config(Ref<ConfigFile> p_layout, const String &p if (!names.is_empty()) { p_layout->set_value(p_section, config_key, names); } + + int selected_tab_idx = dock_slot[i]->get_current_tab(); + if (selected_tab_idx >= 0) { + p_layout->set_value(p_section, "dock_" + itos(i + 1) + "_selected_tab_idx", selected_tab_idx); + } } - p_layout->set_value(p_section, "dock_filesystem_split", FileSystemDock::get_singleton()->get_split_offset()); - p_layout->set_value(p_section, "dock_filesystem_display_mode", FileSystemDock::get_singleton()->get_display_mode()); - p_layout->set_value(p_section, "dock_filesystem_file_sort", FileSystemDock::get_singleton()->get_file_sort()); - p_layout->set_value(p_section, "dock_filesystem_file_list_display_mode", FileSystemDock::get_singleton()->get_file_list_display_mode()); + Dictionary floating_docks_dump; + + for (WindowWrapper *wrapper : floating_docks) { + Control *dock = wrapper->get_wrapped_control(); + + Dictionary dock_dump; + dock_dump["window_rect"] = wrapper->get_window_rect(); + + int screen = wrapper->get_window_screen(); + dock_dump["window_screen"] = wrapper->get_window_screen(); + dock_dump["window_screen_rect"] = DisplayServer::get_singleton()->screen_get_usable_rect(screen); + + String name = dock->get_name(); + floating_docks_dump[name] = dock_dump; + + int dock_slot_id = wrapper->get_meta("dock_slot"); + String config_key = "dock_" + itos(dock_slot_id + 1); + + String names = p_layout->get_value(p_section, config_key, ""); + if (names.is_empty()) { + names = name; + } else { + names += "," + name; + } + p_layout->set_value(p_section, config_key, names); + } + + p_layout->set_value(p_section, "dock_floating", floating_docks_dump); for (int i = 0; i < vsplits.size(); i++) { if (vsplits[i]->is_visible_in_tree()) { @@ -4786,10 +4836,21 @@ void EditorNode::_save_docks_to_config(Ref<ConfigFile> p_layout, const String &p for (int i = 0; i < hsplits.size(); i++) { p_layout->set_value(p_section, "dock_hsplit_" + itos(i + 1), hsplits[i]->get_split_offset()); } + + // Save FileSystemDock state. + + p_layout->set_value(p_section, "dock_filesystem_split", FileSystemDock::get_singleton()->get_split_offset()); + p_layout->set_value(p_section, "dock_filesystem_display_mode", FileSystemDock::get_singleton()->get_display_mode()); + p_layout->set_value(p_section, "dock_filesystem_file_sort", FileSystemDock::get_singleton()->get_file_sort()); + p_layout->set_value(p_section, "dock_filesystem_file_list_display_mode", FileSystemDock::get_singleton()->get_file_list_display_mode()); + PackedStringArray selected_files = FileSystemDock::get_singleton()->get_selected_paths(); + p_layout->set_value(p_section, "dock_filesystem_selected_paths", selected_files); + Vector<String> uncollapsed_paths = FileSystemDock::get_singleton()->get_uncollapsed_paths(); + p_layout->set_value(p_section, "dock_filesystem_uncollapsed_paths", uncollapsed_paths); } -void EditorNode::_save_open_scenes_to_config(Ref<ConfigFile> p_layout, const String &p_section) { - Array scenes; +void EditorNode::_save_open_scenes_to_config(Ref<ConfigFile> p_layout) { + PackedStringArray scenes; for (int i = 0; i < editor_data.get_edited_scene_count(); i++) { String path = editor_data.get_scene_path(i); if (path.is_empty()) { @@ -4797,18 +4858,21 @@ void EditorNode::_save_open_scenes_to_config(Ref<ConfigFile> p_layout, const Str } scenes.push_back(path); } - p_layout->set_value(p_section, "open_scenes", scenes); + p_layout->set_value(EDITOR_NODE_CONFIG_SECTION, "open_scenes", scenes); + + String currently_edited_scene_path = editor_data.get_scene_path(editor_data.get_edited_scene()); + p_layout->set_value(EDITOR_NODE_CONFIG_SECTION, "current_scene", currently_edited_scene_path); } -void EditorNode::save_layout() { - dock_drag_timer->start(); +void EditorNode::save_editor_layout_delayed() { + editor_layout_save_delay_timer->start(); } void EditorNode::_dock_split_dragged(int ofs) { - dock_drag_timer->start(); + editor_layout_save_delay_timer->start(); } -void EditorNode::_load_docks() { +void EditorNode::_load_editor_layout() { Ref<ConfigFile> config; config.instantiate(); Error err = config->load(EditorPaths::get_singleton()->get_project_settings_dir().path_join("editor_layout.cfg")); @@ -4821,7 +4885,8 @@ void EditorNode::_load_docks() { } _load_docks_from_config(config, "docks"); - _load_open_scenes_from_config(config, "EditorNode"); + _load_open_scenes_from_config(config); + _load_central_editor_layout_from_config(config); editor_data.set_plugin_window_layout(config); } @@ -4918,7 +4983,24 @@ void EditorNode::_dock_tab_changed(int p_tab) { } } +void EditorNode::_restore_floating_dock(const Dictionary &p_dock_dump, Control *p_dock, int p_slot_index) { + WindowWrapper *wrapper = Object::cast_to<WindowWrapper>(p_dock); + if (!wrapper) { + _dock_make_float(p_dock, p_slot_index, false); + wrapper = floating_docks[floating_docks.size() - 1]; + } + + wrapper->restore_window_from_saved_position( + p_dock_dump.get("window_rect", Rect2i()), + p_dock_dump.get("window_screen", -1), + p_dock_dump.get("window_screen_rect", Rect2i())); +} + void EditorNode::_load_docks_from_config(Ref<ConfigFile> p_layout, const String &p_section) { + Dictionary floating_docks_dump = p_layout->get_value(p_section, "dock_floating", Dictionary()); + + bool restore_window_on_load = EDITOR_GET("interface/multi_window/restore_windows_on_load"); + for (int i = 0; i < DOCK_SLOT_MAX; i++) { if (!p_layout->has_section_key(p_section, "dock_" + itos(i + 1))) { continue; @@ -4928,6 +5010,7 @@ void EditorNode::_load_docks_from_config(Ref<ConfigFile> p_layout, const String for (int j = names.size() - 1; j >= 0; j--) { String name = names[j]; + // FIXME: Find it, in a horribly inefficient way. int atidx = -1; Control *node = nullptr; @@ -4942,45 +5025,55 @@ void EditorNode::_load_docks_from_config(Ref<ConfigFile> p_layout, const String atidx = k; break; } - if (atidx == -1) { // Well, it's not anywhere. + + if (atidx == -1) { + // Try floating docks. + for (WindowWrapper *wrapper : floating_docks) { + if (wrapper->get_meta("dock_name") == name) { + if (restore_window_on_load && floating_docks_dump.has(name)) { + _restore_floating_dock(floating_docks_dump[name], wrapper, i); + return; + } else { + _dock_floating_close_request(wrapper); + atidx = wrapper->get_meta("dock_index"); + } + } + } + + // Well, it's not anywhere. continue; } if (atidx == i) { dock_slot[i]->move_child(node, 0); - continue; - } + } else if (atidx != -1) { + dock_slot[atidx]->remove_child(node); - dock_slot[atidx]->remove_child(node); + if (dock_slot[atidx]->get_tab_count() == 0) { + dock_slot[atidx]->hide(); + } + dock_slot[i]->add_child(node); + dock_slot[i]->move_child(node, 0); + dock_slot[i]->set_tab_title(0, TTRGET(node->get_name())); + dock_slot[i]->show(); + } - if (dock_slot[atidx]->get_tab_count() == 0) { - dock_slot[atidx]->hide(); + WindowWrapper *wrapper = Object::cast_to<WindowWrapper>(node); + if (restore_window_on_load && floating_docks_dump.has(name)) { + _restore_floating_dock(floating_docks_dump[name], node, i); + } else if (wrapper) { + _dock_floating_close_request(wrapper); } - dock_slot[i]->add_child(node); - dock_slot[i]->move_child(node, 0); - dock_slot[i]->set_tab_title(0, TTRGET(node->get_name())); - dock_slot[i]->show(); } - } - - if (p_layout->has_section_key(p_section, "dock_filesystem_split")) { - int fs_split_ofs = p_layout->get_value(p_section, "dock_filesystem_split"); - FileSystemDock::get_singleton()->set_split_offset(fs_split_ofs); - } - if (p_layout->has_section_key(p_section, "dock_filesystem_display_mode")) { - FileSystemDock::DisplayMode dock_filesystem_display_mode = FileSystemDock::DisplayMode(int(p_layout->get_value(p_section, "dock_filesystem_display_mode"))); - FileSystemDock::get_singleton()->set_display_mode(dock_filesystem_display_mode); - } - - if (p_layout->has_section_key(p_section, "dock_filesystem_file_sort")) { - FileSystemDock::FileSortOption dock_filesystem_file_sort = FileSystemDock::FileSortOption(int(p_layout->get_value(p_section, "dock_filesystem_file_sort"))); - FileSystemDock::get_singleton()->set_file_sort(dock_filesystem_file_sort); - } + if (!p_layout->has_section_key(p_section, "dock_" + itos(i + 1) + "_selected_tab_idx")) { + continue; + } - if (p_layout->has_section_key(p_section, "dock_filesystem_file_list_display_mode")) { - FileSystemDock::FileListDisplayMode dock_filesystem_file_list_display_mode = FileSystemDock::FileListDisplayMode(int(p_layout->get_value(p_section, "dock_filesystem_file_list_display_mode"))); - FileSystemDock::get_singleton()->set_file_list_display_mode(dock_filesystem_file_list_display_mode); + int selected_tab_idx = p_layout->get_value(p_section, "dock_" + itos(i + 1) + "_selected_tab_idx"); + if (selected_tab_idx >= 0 && selected_tab_idx < dock_slot[i]->get_tab_count()) { + dock_slot[i]->call_deferred("set_current_tab", selected_tab_idx); + } } for (int i = 0; i < vsplits.size(); i++) { @@ -5020,24 +5113,141 @@ void EditorNode::_load_docks_from_config(Ref<ConfigFile> p_layout, const String dock_slot[i]->set_current_tab(0); } } + + // FileSystemDock. + + if (p_layout->has_section_key(p_section, "dock_filesystem_split")) { + int fs_split_ofs = p_layout->get_value(p_section, "dock_filesystem_split"); + FileSystemDock::get_singleton()->set_split_offset(fs_split_ofs); + } + + if (p_layout->has_section_key(p_section, "dock_filesystem_display_mode")) { + FileSystemDock::DisplayMode dock_filesystem_display_mode = FileSystemDock::DisplayMode(int(p_layout->get_value(p_section, "dock_filesystem_display_mode"))); + FileSystemDock::get_singleton()->set_display_mode(dock_filesystem_display_mode); + } + + if (p_layout->has_section_key(p_section, "dock_filesystem_file_sort")) { + FileSystemDock::FileSortOption dock_filesystem_file_sort = FileSystemDock::FileSortOption(int(p_layout->get_value(p_section, "dock_filesystem_file_sort"))); + FileSystemDock::get_singleton()->set_file_sort(dock_filesystem_file_sort); + } + + if (p_layout->has_section_key(p_section, "dock_filesystem_file_list_display_mode")) { + FileSystemDock::FileListDisplayMode dock_filesystem_file_list_display_mode = FileSystemDock::FileListDisplayMode(int(p_layout->get_value(p_section, "dock_filesystem_file_list_display_mode"))); + FileSystemDock::get_singleton()->set_file_list_display_mode(dock_filesystem_file_list_display_mode); + } + + if (p_layout->has_section_key(p_section, "dock_filesystem_selected_paths")) { + PackedStringArray dock_filesystem_selected_paths = p_layout->get_value(p_section, "dock_filesystem_selected_paths"); + for (int i = 0; i < dock_filesystem_selected_paths.size(); i++) { + FileSystemDock::get_singleton()->select_file(dock_filesystem_selected_paths[i]); + } + } + + // Restore collapsed state of FileSystemDock. + if (p_layout->has_section_key(p_section, "dock_filesystem_uncollapsed_paths")) { + PackedStringArray uncollapsed_tis = p_layout->get_value(p_section, "dock_filesystem_uncollapsed_paths"); + for (int i = 0; i < uncollapsed_tis.size(); i++) { + TreeItem *uncollapsed_ti = FileSystemDock::get_singleton()->get_tree_control()->get_item_with_metadata(uncollapsed_tis[i], 0); + if (uncollapsed_ti) { + uncollapsed_ti->set_collapsed(false); + } + } + FileSystemDock::get_singleton()->get_tree_control()->queue_redraw(); + } } -void EditorNode::_load_open_scenes_from_config(Ref<ConfigFile> p_layout, const String &p_section) { +void EditorNode::_save_central_editor_layout_to_config(Ref<ConfigFile> p_config_file) { + // Bottom panel. + + int center_split_offset = center_split->get_split_offset(); + p_config_file->set_value(EDITOR_NODE_CONFIG_SECTION, "center_split_offset", center_split_offset); + + int selected_bottom_panel_item_idx = -1; + for (int i = 0; i < bottom_panel_items.size(); i++) { + if (bottom_panel_items[i].button->is_pressed()) { + selected_bottom_panel_item_idx = i; + break; + } + } + if (selected_bottom_panel_item_idx != -1) { + p_config_file->set_value(EDITOR_NODE_CONFIG_SECTION, "selected_bottom_panel_item", selected_bottom_panel_item_idx); + } + + // Debugger tab. + + int selected_default_debugger_tab_idx = EditorDebuggerNode::get_singleton()->get_default_debugger()->get_current_debugger_tab(); + p_config_file->set_value(EDITOR_NODE_CONFIG_SECTION, "selected_default_debugger_tab_idx", selected_default_debugger_tab_idx); + + // Main editor (plugin). + + int selected_main_editor_idx = -1; + for (int i = 0; i < main_editor_buttons.size(); i++) { + if (main_editor_buttons[i]->is_pressed()) { + selected_main_editor_idx = i; + break; + } + } + if (selected_main_editor_idx != -1) { + p_config_file->set_value(EDITOR_NODE_CONFIG_SECTION, "selected_main_editor_idx", selected_main_editor_idx); + } +} + +void EditorNode::_load_central_editor_layout_from_config(Ref<ConfigFile> p_config_file) { + // Bottom panel. + + if (p_config_file->has_section_key(EDITOR_NODE_CONFIG_SECTION, "center_split_offset")) { + int center_split_offset = p_config_file->get_value(EDITOR_NODE_CONFIG_SECTION, "center_split_offset"); + center_split->set_split_offset(center_split_offset); + } + + if (p_config_file->has_section_key(EDITOR_NODE_CONFIG_SECTION, "selected_bottom_panel_item")) { + int selected_bottom_panel_item_idx = p_config_file->get_value(EDITOR_NODE_CONFIG_SECTION, "selected_bottom_panel_item"); + if (selected_bottom_panel_item_idx >= 0 && selected_bottom_panel_item_idx < bottom_panel_items.size()) { + _bottom_panel_switch(true, selected_bottom_panel_item_idx); + } + } + + // Debugger tab. + + if (p_config_file->has_section_key(EDITOR_NODE_CONFIG_SECTION, "selected_default_debugger_tab_idx")) { + int selected_default_debugger_tab_idx = p_config_file->get_value(EDITOR_NODE_CONFIG_SECTION, "selected_default_debugger_tab_idx"); + EditorDebuggerNode::get_singleton()->get_default_debugger()->switch_to_debugger(selected_default_debugger_tab_idx); + } + + // Main editor (plugin). + + if (p_config_file->has_section_key(EDITOR_NODE_CONFIG_SECTION, "selected_main_editor_idx")) { + int selected_main_editor_idx = p_config_file->get_value(EDITOR_NODE_CONFIG_SECTION, "selected_main_editor_idx"); + if (selected_main_editor_idx >= 0 && selected_main_editor_idx < main_editor_buttons.size()) { + callable_mp(this, &EditorNode::editor_select).call_deferred(selected_main_editor_idx); + } + } +} + +void EditorNode::_load_open_scenes_from_config(Ref<ConfigFile> p_layout) { if (!bool(EDITOR_GET("interface/scene_tabs/restore_scenes_on_load"))) { return; } - if (!p_layout->has_section(p_section) || !p_layout->has_section_key(p_section, "open_scenes")) { + if (!p_layout->has_section(EDITOR_NODE_CONFIG_SECTION) || + !p_layout->has_section_key(EDITOR_NODE_CONFIG_SECTION, "open_scenes")) { return; } restoring_scenes = true; - Array scenes = p_layout->get_value(p_section, "open_scenes"); + PackedStringArray scenes = p_layout->get_value(EDITOR_NODE_CONFIG_SECTION, "open_scenes"); for (int i = 0; i < scenes.size(); i++) { load_scene(scenes[i]); } - save_layout(); + + if (p_layout->has_section_key(EDITOR_NODE_CONFIG_SECTION, "current_scene")) { + String current_scene = p_layout->get_value(EDITOR_NODE_CONFIG_SECTION, "current_scene"); + int current_scene_idx = scenes.find(current_scene); + set_current_scene(current_scene_idx); + } + + save_editor_layout_delayed(); restoring_scenes = false; } @@ -5052,10 +5262,10 @@ bool EditorNode::has_scenes_in_session() { if (err != OK) { return false; } - if (!config->has_section("EditorNode") || !config->has_section_key("EditorNode", "open_scenes")) { + if (!config->has_section(EDITOR_NODE_CONFIG_SECTION) || !config->has_section_key(EDITOR_NODE_CONFIG_SECTION, "open_scenes")) { return false; } - Array scenes = config->get_value("EditorNode", "open_scenes"); + Array scenes = config->get_value(EDITOR_NODE_CONFIG_SECTION, "open_scenes"); return !scenes.is_empty(); } @@ -5181,7 +5391,7 @@ void EditorNode::_layout_menu_option(int p_id) { } break; case SETTINGS_LAYOUT_DEFAULT: { _load_docks_from_config(default_layout, "docks"); - _save_docks(); + _save_editor_layout(); } break; default: { Ref<ConfigFile> config; @@ -5192,7 +5402,7 @@ void EditorNode::_layout_menu_option(int p_id) { } _load_docks_from_config(config, editor_layouts->get_item_text(p_id)); - _save_docks(); + _save_editor_layout(); } } } @@ -5264,7 +5474,7 @@ void EditorNode::_scene_tab_closed(int p_tab, int p_option) { _discard_changes(); } - save_layout(); + save_editor_layout_delayed(); _update_scene_tabs(); } @@ -5765,7 +5975,7 @@ void EditorNode::reload_scene(const String &p_path) { if (current_tab == scene_idx) { editor_data.apply_changes_in_editors(); - _set_scene_metadata(p_path); + _save_editor_states(p_path); } // Reload scene. @@ -6824,13 +7034,16 @@ EditorNode::EditorNode() { dock_select->set_v_size_flags(Control::SIZE_EXPAND_FILL); dock_vb->add_child(dock_select); - dock_float = memnew(Button); - dock_float->set_text(TTR("Make Floating")); - dock_float->set_focus_mode(Control::FOCUS_NONE); - dock_float->set_h_size_flags(Control::SIZE_SHRINK_CENTER); - dock_float->connect("pressed", callable_mp(this, &EditorNode::_dock_make_float)); + if (!SceneTree::get_singleton()->get_root()->is_embedding_subwindows() && EDITOR_GET("interface/multi_window/enable")) { + dock_float = memnew(Button); + dock_float->set_icon(theme->get_icon("MakeFloating", "EditorIcons")); + dock_float->set_text(TTR("Make Floating")); + dock_float->set_focus_mode(Control::FOCUS_NONE); + dock_float->set_h_size_flags(Control::SIZE_SHRINK_CENTER); + dock_float->connect("pressed", callable_mp(this, &EditorNode::_dock_make_selected_float)); - dock_vb->add_child(dock_float); + dock_vb->add_child(dock_float); + } dock_select_popup->reset_size(); @@ -6845,11 +7058,11 @@ EditorNode::EditorNode() { dock_slot[i]->set_use_hidden_tabs_for_min_size(true); } - dock_drag_timer = memnew(Timer); - add_child(dock_drag_timer); - dock_drag_timer->set_wait_time(0.5); - dock_drag_timer->set_one_shot(true); - dock_drag_timer->connect("timeout", callable_mp(this, &EditorNode::_save_docks)); + editor_layout_save_delay_timer = memnew(Timer); + add_child(editor_layout_save_delay_timer); + editor_layout_save_delay_timer->set_wait_time(0.5); + editor_layout_save_delay_timer->set_one_shot(true); + editor_layout_save_delay_timer->connect("timeout", callable_mp(this, &EditorNode::_save_editor_layout)); top_split = memnew(VSplitContainer); center_split->add_child(top_split); @@ -7330,7 +7543,7 @@ EditorNode::EditorNode() { FileSystemDock *filesystem_dock = memnew(FileSystemDock); filesystem_dock->connect("inherit", callable_mp(this, &EditorNode::_inherit_request)); filesystem_dock->connect("instantiate", callable_mp(this, &EditorNode::_instantiate_request)); - filesystem_dock->connect("display_mode_changed", callable_mp(this, &EditorNode::_save_docks)); + filesystem_dock->connect("display_mode_changed", callable_mp(this, &EditorNode::_save_editor_layout)); get_project_settings()->connect_filesystem_dock_signals(filesystem_dock); history_dock = memnew(HistoryDock); diff --git a/editor/editor_node.h b/editor/editor_node.h index 0003fee301..9917fa16bc 100644 --- a/editor/editor_node.h +++ b/editor/editor_node.h @@ -113,6 +113,7 @@ class ProjectSettingsEditor; class RunSettingsDialog; class SceneImportSettings; class ScriptCreateDialog; +class WindowWrapper; class EditorNode : public Node { GDCLASS(EditorNode, Node); @@ -420,7 +421,7 @@ private: Button *new_inherited_button = nullptr; String open_import_request; - Vector<Control *> floating_docks; + Vector<WindowWrapper *> floating_docks; Button *dock_float = nullptr; Button *dock_tab_move_left = nullptr; @@ -429,7 +430,7 @@ private: PopupPanel *dock_select_popup = nullptr; Rect2 dock_select_rect[DOCK_SLOT_MAX]; TabContainer *dock_slot[DOCK_SLOT_MAX]; - Timer *dock_drag_timer = nullptr; + Timer *editor_layout_save_delay_timer = nullptr; bool docks_visible = true; int dock_popup_selected_idx = -1; int dock_select_rect_over_idx = -1; @@ -558,8 +559,8 @@ private: void _node_renamed(); void _editor_select_next(); void _editor_select_prev(); - void _set_scene_metadata(const String &p_file, int p_idx = -1); - void _get_scene_metadata(const String &p_file); + void _save_editor_states(const String &p_file, int p_idx = -1); + void _load_editor_plugin_states_from_config(const Ref<ConfigFile> &p_config_file); void _update_title(); void _update_scene_tabs(); void _version_control_menu_option(int p_idx); @@ -628,8 +629,9 @@ private: void _dock_pre_popup(int p_which); void _dock_split_dragged(int ofs); void _dock_popup_exit(); - void _dock_floating_close_request(Control *p_control); - void _dock_make_float(); + void _dock_floating_close_request(WindowWrapper *p_wrapper); + void _dock_make_selected_float(); + void _dock_make_float(Control *p_control, int p_slot_index, bool p_show_window = true); void _scene_tab_changed(int p_tab); void _proceed_closing_scene_tabs(); bool _is_closing_editor() const; @@ -646,15 +648,19 @@ private: int _get_current_main_editor(); - void _save_docks(); - void _load_docks(); + void _save_editor_layout(); + void _load_editor_layout(); void _save_docks_to_config(Ref<ConfigFile> p_layout, const String &p_section); + void _restore_floating_dock(const Dictionary &p_dock_dump, Control *p_wrapper, int p_slot_index); void _load_docks_from_config(Ref<ConfigFile> p_layout, const String &p_section); void _update_dock_slots_visibility(bool p_keep_selected_tabs = false); void _dock_tab_changed(int p_tab); - void _save_open_scenes_to_config(Ref<ConfigFile> p_layout, const String &p_section); - void _load_open_scenes_from_config(Ref<ConfigFile> p_layout, const String &p_section); + void _save_central_editor_layout_to_config(Ref<ConfigFile> p_config_file); + void _load_central_editor_layout_from_config(Ref<ConfigFile> p_config_file); + + void _save_open_scenes_to_config(Ref<ConfigFile> p_layout); + void _load_open_scenes_from_config(Ref<ConfigFile> p_layout); void _update_layouts_menu(); void _layout_menu_option(int p_id); @@ -881,7 +887,7 @@ public: bool is_scene_in_use(const String &p_path); - void save_layout(); + void save_editor_layout_delayed(); void save_default_environment(); void open_export_template_manager(); diff --git a/editor/editor_plugin.cpp b/editor/editor_plugin.cpp index 442524d579..9e22a0ead6 100644 --- a/editor/editor_plugin.cpp +++ b/editor/editor_plugin.cpp @@ -471,7 +471,7 @@ bool EditorPlugin::build() { } void EditorPlugin::queue_save_layout() { - EditorNode::get_singleton()->save_layout(); + EditorNode::get_singleton()->save_editor_layout_delayed(); } void EditorPlugin::make_bottom_panel_item_visible(Control *p_item) { diff --git a/editor/editor_properties.cpp b/editor/editor_properties.cpp index 18c5d4ba51..04f10bceac 100644 --- a/editor/editor_properties.cpp +++ b/editor/editor_properties.cpp @@ -1155,6 +1155,12 @@ void EditorPropertyLayers::setup(LayerType p_layer_type) { layer_group_size = 4; layer_count = 32; } break; + + case LAYER_AVOIDANCE: { + basename = "layer_names/avoidance"; + layer_group_size = 4; + layer_count = 32; + } break; } Vector<String> names; @@ -4001,7 +4007,6 @@ void EditorPropertyResource::_viewport_selected(const NodePath &p_path) { Ref<ViewportTexture> vt; vt.instantiate(); vt->set_viewport_path_in_scene(get_tree()->get_edited_scene_root()->get_path_to(to_node)); - vt->setup_local_to_scene(); emit_changed(get_edited_property(), vt); update_property(); @@ -4284,7 +4289,8 @@ EditorProperty *EditorInspectorDefaultPlugin::get_editor_for_property(Object *p_ p_hint == PROPERTY_HINT_LAYERS_2D_NAVIGATION || p_hint == PROPERTY_HINT_LAYERS_3D_PHYSICS || p_hint == PROPERTY_HINT_LAYERS_3D_RENDER || - p_hint == PROPERTY_HINT_LAYERS_3D_NAVIGATION) { + p_hint == PROPERTY_HINT_LAYERS_3D_NAVIGATION || + p_hint == PROPERTY_HINT_LAYERS_AVOIDANCE) { EditorPropertyLayers::LayerType lt = EditorPropertyLayers::LAYER_RENDER_2D; switch (p_hint) { case PROPERTY_HINT_LAYERS_2D_RENDER: @@ -4305,6 +4311,9 @@ EditorProperty *EditorInspectorDefaultPlugin::get_editor_for_property(Object *p_ case PROPERTY_HINT_LAYERS_3D_NAVIGATION: lt = EditorPropertyLayers::LAYER_NAVIGATION_3D; break; + case PROPERTY_HINT_LAYERS_AVOIDANCE: + lt = EditorPropertyLayers::LAYER_AVOIDANCE; + break; default: { } //compiler could be smarter here and realize this can't happen } diff --git a/editor/editor_properties.h b/editor/editor_properties.h index 0d54025c7b..015c65b4c6 100644 --- a/editor/editor_properties.h +++ b/editor/editor_properties.h @@ -308,6 +308,7 @@ public: LAYER_PHYSICS_3D, LAYER_RENDER_3D, LAYER_NAVIGATION_3D, + LAYER_AVOIDANCE, }; private: diff --git a/editor/editor_property_name_processor.cpp b/editor/editor_property_name_processor.cpp index 4ae8b262c1..83d9a18c02 100644 --- a/editor/editor_property_name_processor.cpp +++ b/editor/editor_property_name_processor.cpp @@ -36,6 +36,9 @@ EditorPropertyNameProcessor *EditorPropertyNameProcessor::singleton = nullptr; EditorPropertyNameProcessor::Style EditorPropertyNameProcessor::get_default_inspector_style() { + if (!EditorSettings::get_singleton()) { + return STYLE_CAPITALIZED; + } const Style style = (Style)EDITOR_GET("interface/inspector/default_property_name_style").operator int(); if (style == STYLE_LOCALIZED && !is_localization_available()) { return STYLE_CAPITALIZED; @@ -44,6 +47,9 @@ EditorPropertyNameProcessor::Style EditorPropertyNameProcessor::get_default_insp } EditorPropertyNameProcessor::Style EditorPropertyNameProcessor::get_settings_style() { + if (!EditorSettings::get_singleton()) { + return STYLE_LOCALIZED; + } const bool translate = EDITOR_GET("interface/editor/localize_settings"); return translate ? STYLE_LOCALIZED : STYLE_CAPITALIZED; } @@ -53,6 +59,9 @@ EditorPropertyNameProcessor::Style EditorPropertyNameProcessor::get_tooltip_styl } bool EditorPropertyNameProcessor::is_localization_available() { + if (!EditorSettings::get_singleton()) { + return false; + } const Vector<String> forbidden = String("en").split(","); return forbidden.find(EDITOR_GET("interface/editor/editor_language")) == -1; } diff --git a/editor/editor_resource_preview.cpp b/editor/editor_resource_preview.cpp index 6eef4e5a5a..7dea1a7e01 100644 --- a/editor/editor_resource_preview.cpp +++ b/editor/editor_resource_preview.cpp @@ -35,6 +35,7 @@ #include "core/io/resource_loader.h" #include "core/io/resource_saver.h" #include "core/object/message_queue.h" +#include "core/variant/variant_utility.cpp" #include "editor/editor_node.h" #include "editor/editor_paths.h" #include "editor/editor_scale.h" @@ -48,17 +49,17 @@ bool EditorResourcePreviewGenerator::handles(const String &p_type) const { ERR_FAIL_V_MSG(false, "EditorResourcePreviewGenerator::_handles needs to be overridden."); } -Ref<Texture2D> EditorResourcePreviewGenerator::generate(const Ref<Resource> &p_from, const Size2 &p_size) const { +Ref<Texture2D> EditorResourcePreviewGenerator::generate(const Ref<Resource> &p_from, const Size2 &p_size, Dictionary &p_metadata) const { Ref<Texture2D> preview; - if (GDVIRTUAL_CALL(_generate, p_from, p_size, preview)) { + if (GDVIRTUAL_CALL(_generate, p_from, p_size, p_metadata, preview)) { return preview; } ERR_FAIL_V_MSG(Ref<Texture2D>(), "EditorResourcePreviewGenerator::_generate needs to be overridden."); } -Ref<Texture2D> EditorResourcePreviewGenerator::generate_from_path(const String &p_path, const Size2 &p_size) const { +Ref<Texture2D> EditorResourcePreviewGenerator::generate_from_path(const String &p_path, const Size2 &p_size, Dictionary &p_metadata) const { Ref<Texture2D> preview; - if (GDVIRTUAL_CALL(_generate_from_path, p_path, p_size, preview)) { + if (GDVIRTUAL_CALL(_generate_from_path, p_path, p_size, p_metadata, preview)) { return preview; } @@ -66,7 +67,7 @@ Ref<Texture2D> EditorResourcePreviewGenerator::generate_from_path(const String & if (!res.is_valid()) { return res; } - return generate(res, p_size); + return generate(res, p_size, p_metadata); } bool EditorResourcePreviewGenerator::generate_small_preview_automatically() const { @@ -83,8 +84,8 @@ bool EditorResourcePreviewGenerator::can_generate_small_preview() const { void EditorResourcePreviewGenerator::_bind_methods() { GDVIRTUAL_BIND(_handles, "type"); - GDVIRTUAL_BIND(_generate, "resource", "size"); - GDVIRTUAL_BIND(_generate_from_path, "path", "size"); + GDVIRTUAL_BIND(_generate, "resource", "size", "metadata"); + GDVIRTUAL_BIND(_generate_from_path, "path", "size", "metadata"); GDVIRTUAL_BIND(_generate_small_preview_automatically); GDVIRTUAL_BIND(_can_generate_small_preview); } @@ -99,35 +100,31 @@ void EditorResourcePreview::_thread_func(void *ud) { erp->_thread(); } -void EditorResourcePreview::_preview_ready(const String &p_str, const Ref<Texture2D> &p_texture, const Ref<Texture2D> &p_small_texture, ObjectID id, const StringName &p_func, const Variant &p_ud) { - String path = p_str; +void EditorResourcePreview::_preview_ready(const String &p_path, int p_hash, const Ref<Texture2D> &p_texture, const Ref<Texture2D> &p_small_texture, ObjectID id, const StringName &p_func, const Variant &p_ud, const Dictionary &p_metadata) { { MutexLock lock(preview_mutex); - uint32_t hash = 0; uint64_t modified_time = 0; - if (p_str.begins_with("ID:")) { - hash = uint32_t(p_str.get_slicec(':', 2).to_int()); - path = "ID:" + p_str.get_slicec(':', 1); - } else { - modified_time = FileAccess::get_modified_time(path); + if (!p_path.begins_with("ID:")) { + modified_time = FileAccess::get_modified_time(p_path); } Item item; item.order = order++; item.preview = p_texture; item.small_preview = p_small_texture; - item.last_hash = hash; + item.last_hash = p_hash; item.modified_time = modified_time; + item.preview_metadata = p_metadata; - cache[path] = item; + cache[p_path] = item; } - MessageQueue::get_singleton()->push_call(id, p_func, path, p_texture, p_small_texture, p_ud); + MessageQueue::get_singleton()->push_call(id, p_func, p_path, p_texture, p_small_texture, p_ud); } -void EditorResourcePreview::_generate_preview(Ref<ImageTexture> &r_texture, Ref<ImageTexture> &r_small_texture, const QueueItem &p_item, const String &cache_base) { +void EditorResourcePreview::_generate_preview(Ref<ImageTexture> &r_texture, Ref<ImageTexture> &r_small_texture, const QueueItem &p_item, const String &cache_base, Dictionary &p_metadata) { String type; if (p_item.resource.is_valid()) { @@ -155,9 +152,9 @@ void EditorResourcePreview::_generate_preview(Ref<ImageTexture> &r_texture, Ref< Ref<Texture2D> generated; if (p_item.resource.is_valid()) { - generated = preview_generators[i]->generate(p_item.resource, Vector2(thumbnail_size, thumbnail_size)); + generated = preview_generators.write[i]->generate(p_item.resource, Vector2(thumbnail_size, thumbnail_size), p_metadata); } else { - generated = preview_generators[i]->generate_from_path(p_item.path, Vector2(thumbnail_size, thumbnail_size)); + generated = preview_generators.write[i]->generate_from_path(p_item.path, Vector2(thumbnail_size, thumbnail_size), p_metadata); } r_texture = generated; @@ -165,10 +162,11 @@ void EditorResourcePreview::_generate_preview(Ref<ImageTexture> &r_texture, Ref< if (preview_generators[i]->can_generate_small_preview()) { Ref<Texture2D> generated_small; + Dictionary d; if (p_item.resource.is_valid()) { - generated_small = preview_generators[i]->generate(p_item.resource, Vector2(small_thumbnail_size, small_thumbnail_size)); + generated_small = preview_generators.write[i]->generate(p_item.resource, Vector2(small_thumbnail_size, small_thumbnail_size), d); } else { - generated_small = preview_generators[i]->generate_from_path(p_item.path, Vector2(small_thumbnail_size, small_thumbnail_size)); + generated_small = preview_generators.write[i]->generate_from_path(p_item.path, Vector2(small_thumbnail_size, small_thumbnail_size), d); } r_small_texture = generated_small; } @@ -185,9 +183,9 @@ void EditorResourcePreview::_generate_preview(Ref<ImageTexture> &r_texture, Ref< } if (!p_item.resource.is_valid()) { - // cache the preview in case it's a resource on disk + // Cache the preview in case it's a resource on disk. if (r_texture.is_valid()) { - //wow it generated a preview... save cache + // Wow it generated a preview... save cache. bool has_small_texture = r_small_texture.is_valid(); ResourceSaver::save(r_texture, cache_base + ".png"); if (has_small_texture) { @@ -195,14 +193,16 @@ void EditorResourcePreview::_generate_preview(Ref<ImageTexture> &r_texture, Ref< } Ref<FileAccess> f = FileAccess::open(cache_base + ".txt", FileAccess::WRITE); ERR_FAIL_COND_MSG(f.is_null(), "Cannot create file '" + cache_base + ".txt'. Check user write permissions."); - f->store_line(itos(thumbnail_size)); - f->store_line(itos(has_small_texture)); - f->store_line(itos(FileAccess::get_modified_time(p_item.path))); - f->store_line(FileAccess::get_md5(p_item.path)); + _write_preview_cache(f, thumbnail_size, has_small_texture, FileAccess::get_modified_time(p_item.path), FileAccess::get_md5(p_item.path), p_metadata); } } } +Variant EditorResourcePreview::get_preview_metadata(const String &p_path, const String &p_meta) const { + ERR_FAIL_COND_V(!cache.has(p_path), Variant()); + return cache[p_path].preview_metadata.get(p_meta, Variant()); +} + void EditorResourcePreview::_iterate() { preview_mutex.lock(); @@ -211,13 +211,8 @@ void EditorResourcePreview::_iterate() { queue.pop_front(); if (cache.has(item.path)) { - //already has it because someone loaded it, just let it know it's ready - String path = item.path; - if (item.resource.is_valid()) { - path += ":" + itos(cache[item.path].last_hash); //keep last hash (see description of what this is in condition below) - } - - _preview_ready(path, cache[item.path].preview, cache[item.path].small_preview, item.id, item.function, item.userdata); + // Already has it because someone loaded it, just let it know it's ready. + _preview_ready(item.path, cache[item.path].last_hash, cache[item.path].preview, cache[item.path].small_preview, item.id, item.function, item.userdata, cache[item.path].preview_metadata); preview_mutex.unlock(); } else { @@ -230,28 +225,31 @@ void EditorResourcePreview::_iterate() { thumbnail_size *= EDSCALE; if (item.resource.is_valid()) { - _generate_preview(texture, small_texture, item, String()); + Dictionary preview_metadata; + _generate_preview(texture, small_texture, item, String(), preview_metadata); - //adding hash to the end of path (should be ID:<objid>:<hash>) because of 5 argument limit to call_deferred - _preview_ready(item.path + ":" + itos(item.resource->hash_edited_version()), texture, small_texture, item.id, item.function, item.userdata); + _preview_ready(item.path, item.resource->hash_edited_version(), texture, small_texture, item.id, item.function, item.userdata, preview_metadata); } else { + Dictionary preview_metadata; String temp_path = EditorPaths::get_singleton()->get_cache_dir(); String cache_base = ProjectSettings::get_singleton()->globalize_path(item.path).md5_text(); cache_base = temp_path.path_join("resthumb-" + cache_base); - //does not have it, try to load a cached thumbnail + // Does not have it, try to load a cached thumbnail. String file = cache_base + ".txt"; Ref<FileAccess> f = FileAccess::open(file, FileAccess::READ); if (f.is_null()) { - // No cache found, generate - _generate_preview(texture, small_texture, item, cache_base); + // No cache found, generate. + _generate_preview(texture, small_texture, item, cache_base, preview_metadata); } else { uint64_t modtime = FileAccess::get_modified_time(item.path); - int tsize = f->get_line().to_int(); - bool has_small_texture = f->get_line().to_int(); - uint64_t last_modtime = f->get_line().to_int(); + int tsize; + bool has_small_texture; + uint64_t last_modtime; + String hash; + _read_preview_cache(f, &tsize, &has_small_texture, &last_modtime, &hash, &preview_metadata); bool cache_valid = true; @@ -266,7 +264,7 @@ void EditorResourcePreview::_iterate() { if (last_md5 != md5) { cache_valid = false; } else { - //update modified time + // Update modified time. Ref<FileAccess> f2 = FileAccess::open(file, FileAccess::WRITE); if (f2.is_null()) { @@ -274,10 +272,7 @@ void EditorResourcePreview::_iterate() { // some proper cleanup/disabling of resource preview generation. ERR_PRINT("Cannot create file '" + file + "'. Check user write permissions."); } else { - f2->store_line(itos(thumbnail_size)); - f2->store_line(itos(has_small_texture)); - f2->store_line(itos(modtime)); - f2->store_line(md5); + _write_preview_cache(f2, thumbnail_size, has_small_texture, modtime, md5, preview_metadata); } } } else { @@ -308,10 +303,10 @@ void EditorResourcePreview::_iterate() { } if (!cache_valid) { - _generate_preview(texture, small_texture, item, cache_base); + _generate_preview(texture, small_texture, item, cache_base, preview_metadata); } } - _preview_ready(item.path, texture, small_texture, item.id, item.function, item.userdata); + _preview_ready(item.path, 0, texture, small_texture, item.id, item.function, item.userdata, preview_metadata); } } @@ -320,6 +315,22 @@ void EditorResourcePreview::_iterate() { } } +void EditorResourcePreview::_write_preview_cache(Ref<FileAccess> p_file, int p_thumbnail_size, bool p_has_small_texture, uint64_t p_modified_time, String p_hash, const Dictionary &p_metadata) { + p_file->store_line(itos(p_thumbnail_size)); + p_file->store_line(itos(p_has_small_texture)); + p_file->store_line(itos(p_modified_time)); + p_file->store_line(p_hash); + p_file->store_line(VariantUtilityFunctions::var_to_str(p_metadata).replace("\n", " ")); +} + +void EditorResourcePreview::_read_preview_cache(Ref<FileAccess> p_file, int *r_thumbnail_size, bool *r_has_small_texture, uint64_t *r_modified_time, String *r_hash, Dictionary *r_metadata) { + *r_thumbnail_size = p_file->get_line().to_int(); + *r_has_small_texture = p_file->get_line().to_int(); + *r_modified_time = p_file->get_line().to_int(); + *r_hash = p_file->get_line(); + *r_metadata = VariantUtilityFunctions::str_to_var(p_file->get_line()); +} + void EditorResourcePreview::_thread() { exited.clear(); while (!exit.is_set()) { diff --git a/editor/editor_resource_preview.h b/editor/editor_resource_preview.h index aae7c5b164..c97e153c0a 100644 --- a/editor/editor_resource_preview.h +++ b/editor/editor_resource_preview.h @@ -44,15 +44,15 @@ protected: static void _bind_methods(); GDVIRTUAL1RC(bool, _handles, String) - GDVIRTUAL2RC(Ref<Texture2D>, _generate, Ref<Resource>, Vector2i) - GDVIRTUAL2RC(Ref<Texture2D>, _generate_from_path, String, Vector2i) + GDVIRTUAL3RC(Ref<Texture2D>, _generate, Ref<Resource>, Vector2i, Dictionary) + GDVIRTUAL3RC(Ref<Texture2D>, _generate_from_path, String, Vector2i, Dictionary) GDVIRTUAL0RC(bool, _generate_small_preview_automatically) GDVIRTUAL0RC(bool, _can_generate_small_preview) public: virtual bool handles(const String &p_type) const; - virtual Ref<Texture2D> generate(const Ref<Resource> &p_from, const Size2 &p_size) const; - virtual Ref<Texture2D> generate_from_path(const String &p_path, const Size2 &p_size) const; + virtual Ref<Texture2D> generate(const Ref<Resource> &p_from, const Size2 &p_size, Dictionary &p_metadata) const; + virtual Ref<Texture2D> generate_from_path(const String &p_path, const Size2 &p_size, Dictionary &p_metadata) const; virtual bool generate_small_preview_automatically() const; virtual bool can_generate_small_preview() const; @@ -84,6 +84,7 @@ class EditorResourcePreview : public Node { struct Item { Ref<Texture2D> preview; Ref<Texture2D> small_preview; + Dictionary preview_metadata; int order = 0; uint32_t last_hash = 0; uint64_t modified_time = 0; @@ -93,13 +94,16 @@ class EditorResourcePreview : public Node { HashMap<String, Item> cache; - void _preview_ready(const String &p_str, const Ref<Texture2D> &p_texture, const Ref<Texture2D> &p_small_texture, ObjectID id, const StringName &p_func, const Variant &p_ud); - void _generate_preview(Ref<ImageTexture> &r_texture, Ref<ImageTexture> &r_small_texture, const QueueItem &p_item, const String &cache_base); + void _preview_ready(const String &p_path, int p_hash, const Ref<Texture2D> &p_texture, const Ref<Texture2D> &p_small_texture, ObjectID id, const StringName &p_func, const Variant &p_ud, const Dictionary &p_metadata); + void _generate_preview(Ref<ImageTexture> &r_texture, Ref<ImageTexture> &r_small_texture, const QueueItem &p_item, const String &cache_base, Dictionary &p_metadata); static void _thread_func(void *ud); void _thread(); void _iterate(); + void _write_preview_cache(Ref<FileAccess> p_file, int p_thumbnail_size, bool p_has_small_texture, uint64_t p_modified_time, String p_hash, const Dictionary &p_metadata); + void _read_preview_cache(Ref<FileAccess> p_file, int *r_thumbnail_size, bool *r_has_small_texture, uint64_t *r_modified_time, String *r_hash, Dictionary *r_metadata); + Vector<Ref<EditorResourcePreviewGenerator>> preview_generators; protected: @@ -112,6 +116,7 @@ public: // p_preview will be null if there was an error void queue_resource_preview(const String &p_path, Object *p_receiver, const StringName &p_receiver_func, const Variant &p_userdata); void queue_edited_resource_preview(const Ref<Resource> &p_res, Object *p_receiver, const StringName &p_receiver_func, const Variant &p_userdata); + Variant get_preview_metadata(const String &p_path, const String &p_meta) const; void add_preview_generator(const Ref<EditorResourcePreviewGenerator> &p_generator); void remove_preview_generator(const Ref<EditorResourcePreviewGenerator> &p_generator); diff --git a/editor/editor_run.cpp b/editor/editor_run.cpp index b090638c37..25c25c3f7c 100644 --- a/editor/editor_run.cpp +++ b/editor/editor_run.cpp @@ -70,6 +70,7 @@ Error EditorRun::run(const String &p_scene, const String &p_write_movie) { bool debug_collisions = EditorSettings::get_singleton()->get_project_metadata("debug_options", "run_debug_collisions", false); bool debug_paths = EditorSettings::get_singleton()->get_project_metadata("debug_options", "run_debug_paths", false); bool debug_navigation = EditorSettings::get_singleton()->get_project_metadata("debug_options", "run_debug_navigation", false); + bool debug_avoidance = EditorSettings::get_singleton()->get_project_metadata("debug_options", "run_debug_avoidance", false); if (debug_collisions) { args.push_back("--debug-collisions"); } @@ -82,6 +83,10 @@ Error EditorRun::run(const String &p_scene, const String &p_write_movie) { args.push_back("--debug-navigation"); } + if (debug_avoidance) { + args.push_back("--debug-avoidance"); + } + if (p_write_movie != "") { args.push_back("--write-movie"); args.push_back(p_write_movie); diff --git a/editor/editor_settings.cpp b/editor/editor_settings.cpp index 9577cd0e63..0eb76c6011 100644 --- a/editor/editor_settings.cpp +++ b/editor/editor_settings.cpp @@ -485,6 +485,12 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) { EDITOR_SETTING_USAGE(Variant::INT, PROPERTY_HINT_RANGE, "interface/scene_tabs/maximum_width", 350, "0,9999,1", PROPERTY_USAGE_DEFAULT) _initial_set("interface/scene_tabs/show_script_button", false); + // Multi Window + EDITOR_SETTING(Variant::BOOL, PROPERTY_HINT_NONE, "interface/multi_window/enable", true, ""); + EDITOR_SETTING(Variant::BOOL, PROPERTY_HINT_NONE, "interface/multi_window/restore_windows_on_load", true, ""); + EDITOR_SETTING(Variant::BOOL, PROPERTY_HINT_NONE, "interface/multi_window/maximize_window", false, ""); + set_restart_if_changed("interface/multi_window/enable", true); + /* Filesystem */ // External Programs @@ -708,6 +714,9 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) { _initial_set("editors/animation/onion_layers_past_color", Color(1, 0, 0)); _initial_set("editors/animation/onion_layers_future_color", Color(0, 1, 0)); + // Shader editor + _initial_set("editors/shader_editor/behavior/files/restore_shaders_on_load", true); + // Visual editors EDITOR_SETTING(Variant::FLOAT, PROPERTY_HINT_RANGE, "editors/visual_editors/minimap_opacity", 0.85, "0.0,1.0,0.01") EDITOR_SETTING(Variant::FLOAT, PROPERTY_HINT_RANGE, "editors/visual_editors/lines_curvature", 0.5, "0.0,1.0,0.01") diff --git a/editor/editor_themes.cpp b/editor/editor_themes.cpp index 1b5144af67..3cdd78dc7f 100644 --- a/editor/editor_themes.cpp +++ b/editor/editor_themes.cpp @@ -833,6 +833,8 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { // Script Editor theme->set_stylebox("ScriptEditorPanel", "EditorStyles", make_empty_stylebox(default_margin_size, 0, default_margin_size, default_margin_size)); + theme->set_stylebox("ScriptEditorPanelFloating", "EditorStyles", make_empty_stylebox(0, 0, 0, 0)); + theme->set_stylebox("ScriptEditor", "EditorStyles", make_empty_stylebox(0, 0, 0, 0)); // Launch Pad and Play buttons diff --git a/editor/export/editor_export.cpp b/editor/export/editor_export.cpp index 3f192342a1..c6faefc45f 100644 --- a/editor/export/editor_export.cpp +++ b/editor/export/editor_export.cpp @@ -37,7 +37,9 @@ EditorExport *EditorExport::singleton = nullptr; void EditorExport::_save() { Ref<ConfigFile> config; + Ref<ConfigFile> credentials; config.instantiate(); + credentials.instantiate(); for (int i = 0; i < export_presets.size(); i++) { Ref<EditorExportPreset> preset = export_presets[i]; String section = "preset." + itos(i); @@ -83,16 +85,21 @@ void EditorExport::_save() { config->set_value(section, "encryption_exclude_filters", preset->get_enc_ex_filter()); config->set_value(section, "encrypt_pck", preset->get_enc_pck()); config->set_value(section, "encrypt_directory", preset->get_enc_directory()); - config->set_value(section, "script_encryption_key", preset->get_script_encryption_key()); + credentials->set_value(section, "script_encryption_key", preset->get_script_encryption_key()); String option_section = "preset." + itos(i) + ".options"; for (const PropertyInfo &E : preset->get_properties()) { - config->set_value(option_section, E.name, preset->get(E.name)); + if (E.usage & PROPERTY_USAGE_SECRET) { + credentials->set_value(option_section, E.name, preset->get(E.name)); + } else { + config->set_value(option_section, E.name, preset->get(E.name)); + } } } config->save("res://export_presets.cfg"); + credentials->save("res://.godot/export_credentials.cfg"); } void EditorExport::save_presets() { @@ -202,6 +209,13 @@ void EditorExport::load_config() { return; } + Ref<ConfigFile> credentials; + credentials.instantiate(); + err = credentials->load("res://.godot/export_credentials.cfg"); + if (!(err == OK || err == ERR_FILE_NOT_FOUND)) { + return; + } + block_save = true; int index = 0; @@ -284,22 +298,30 @@ void EditorExport::load_config() { if (config->has_section_key(section, "encryption_exclude_filters")) { preset->set_enc_ex_filter(config->get_value(section, "encryption_exclude_filters")); } - if (config->has_section_key(section, "script_encryption_key")) { - preset->set_script_encryption_key(config->get_value(section, "script_encryption_key")); + if (credentials->has_section_key(section, "script_encryption_key")) { + preset->set_script_encryption_key(credentials->get_value(section, "script_encryption_key")); } String option_section = "preset." + itos(index) + ".options"; List<String> options; - config->get_section_keys(option_section, &options); for (const String &E : options) { Variant value = config->get_value(option_section, E); - preset->set(E, value); } + if (credentials->has_section(option_section)) { + options.clear(); + credentials->get_section_keys(option_section, &options); + + for (const String &E : options) { + Variant value = credentials->get_value(option_section, E); + preset->set(E, value); + } + } + add_export_preset(preset); index++; } diff --git a/editor/export/editor_export_platform.cpp b/editor/export/editor_export_platform.cpp index 670f5af713..65ffa45b38 100644 --- a/editor/export/editor_export_platform.cpp +++ b/editor/export/editor_export_platform.cpp @@ -818,6 +818,14 @@ String EditorExportPlatform::_export_customize(const String &p_path, LocalVector return save_path.is_empty() ? p_path : save_path; } +String EditorExportPlatform::_get_script_encryption_key(const Ref<EditorExportPreset> &p_preset) const { + const String from_env = OS::get_singleton()->get_environment(ENV_SCRIPT_ENCRYPTION_KEY); + if (!from_env.is_empty()) { + return from_env.to_lower(); + } + return p_preset->get_script_encryption_key().to_lower(); +} + Vector<String> EditorExportPlatform::get_forced_export_files() { Vector<String> files; @@ -946,7 +954,7 @@ Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> & } // Get encryption key. - String script_key = p_preset->get_script_encryption_key().to_lower(); + String script_key = _get_script_encryption_key(p_preset); key.resize(32); if (script_key.length() == 64) { for (int i = 0; i < 32; i++) { @@ -1577,7 +1585,7 @@ Error EditorExportPlatform::save_pack(const Ref<EditorExportPreset> &p_preset, b Ref<FileAccess> fhead = f; if (enc_pck && enc_directory) { - String script_key = p_preset->get_script_encryption_key().to_lower(); + String script_key = _get_script_encryption_key(p_preset); Vector<uint8_t> key; key.resize(32); if (script_key.length() == 64) { diff --git a/editor/export/editor_export_platform.h b/editor/export/editor_export_platform.h index df5a66f099..3b9663ebdf 100644 --- a/editor/export/editor_export_platform.h +++ b/editor/export/editor_export_platform.h @@ -43,6 +43,8 @@ struct EditorProgress; class EditorExportPlugin; +const String ENV_SCRIPT_ENCRYPTION_KEY = "GODOT_SCRIPT_ENCRYPTION_KEY"; + class EditorExportPlatform : public RefCounted { GDCLASS(EditorExportPlatform, RefCounted); @@ -116,6 +118,7 @@ private: bool _is_editable_ancestor(Node *p_root, Node *p_node); String _export_customize(const String &p_path, LocalVector<Ref<EditorExportPlugin>> &customize_resources_plugins, LocalVector<Ref<EditorExportPlugin>> &customize_scenes_plugins, HashMap<String, FileExportCache> &export_cache, const String &export_base_path, bool p_force_save); + String _get_script_encryption_key(const Ref<EditorExportPreset> &p_preset) const; protected: struct ExportNotifier { diff --git a/editor/export/editor_export_preset.cpp b/editor/export/editor_export_preset.cpp index ac93479605..2aca19a2ad 100644 --- a/editor/export/editor_export_preset.cpp +++ b/editor/export/editor_export_preset.cpp @@ -302,4 +302,15 @@ String EditorExportPreset::get_script_encryption_key() const { return script_key; } +Variant EditorExportPreset::get_or_env(const StringName &p_name, const String &p_env_var, bool *r_valid) const { + const String from_env = OS::get_singleton()->get_environment(p_env_var); + if (!from_env.is_empty()) { + if (r_valid) { + *r_valid = true; + } + return from_env; + } + return get(p_name, r_valid); +} + EditorExportPreset::EditorExportPreset() {} diff --git a/editor/export/editor_export_preset.h b/editor/export/editor_export_preset.h index 003e3c05a3..194858b4e8 100644 --- a/editor/export/editor_export_preset.h +++ b/editor/export/editor_export_preset.h @@ -152,6 +152,8 @@ public: void set_script_encryption_key(const String &p_key); String get_script_encryption_key() const; + Variant get_or_env(const StringName &p_name, const String &p_env_var, bool *r_valid = nullptr) const; + const List<PropertyInfo> &get_properties() const { return properties; } EditorExportPreset(); diff --git a/editor/filesystem_dock.cpp b/editor/filesystem_dock.cpp index 169e78c0f5..dd80ff49ac 100644 --- a/editor/filesystem_dock.cpp +++ b/editor/filesystem_dock.cpp @@ -87,14 +87,14 @@ bool FileSystemDock::_create_tree(TreeItem *p_parent, EditorFileSystemDirectory subdirectory_item->set_selectable(0, true); String lpath = p_dir->get_path(); subdirectory_item->set_metadata(0, lpath); - if (!p_select_in_favorites && (path == lpath || ((display_mode == DISPLAY_MODE_SPLIT) && path.get_base_dir() == lpath))) { + if (!p_select_in_favorites && (current_path == lpath || ((display_mode == DISPLAY_MODE_SPLIT) && current_path.get_base_dir() == lpath))) { subdirectory_item->select(0); // Keep select an item when re-created a tree // To prevent crashing when nothing is selected. subdirectory_item->set_as_cursor(0); } - if (p_unfold_path && path.begins_with(lpath) && path != lpath) { + if (p_unfold_path && current_path.begins_with(lpath) && current_path != lpath) { subdirectory_item->set_collapsed(false); } else { subdirectory_item->set_collapsed(uncollapsed_paths.find(lpath) < 0); @@ -155,7 +155,7 @@ bool FileSystemDock::_create_tree(TreeItem *p_parent, EditorFileSystemDirectory file_item->set_icon(0, _get_tree_item_icon(!fi.import_broken, fi.type)); String file_metadata = lpath.path_join(fi.name); file_item->set_metadata(0, file_metadata); - if (!p_select_in_favorites && path == file_metadata) { + if (!p_select_in_favorites && current_path == file_metadata) { file_item->select(0); file_item->set_as_cursor(0); } @@ -168,7 +168,7 @@ bool FileSystemDock::_create_tree(TreeItem *p_parent, EditorFileSystemDirectory EditorResourcePreview::get_singleton()->queue_resource_preview(file_metadata, this, "_tree_thumbnail_done", udata); } } else if (display_mode == DISPLAY_MODE_SPLIT) { - if (lpath.get_base_dir() == path.get_base_dir()) { + if (lpath.get_base_dir() == current_path.get_base_dir()) { subdirectory_item->select(0); subdirectory_item->set_as_cursor(0); } @@ -186,8 +186,7 @@ bool FileSystemDock::_create_tree(TreeItem *p_parent, EditorFileSystemDirectory return parent_should_expand; } -Vector<String> FileSystemDock::_compute_uncollapsed_paths() { - // Register currently collapsed paths. +Vector<String> FileSystemDock::get_uncollapsed_paths() const { Vector<String> uncollapsed_paths; TreeItem *root = tree->get_root(); if (root) { @@ -196,21 +195,24 @@ Vector<String> FileSystemDock::_compute_uncollapsed_paths() { uncollapsed_paths.push_back(favorites_item->get_metadata(0)); } - TreeItem *resTree = root->get_first_child()->get_next(); - if (resTree) { - Vector<TreeItem *> needs_check; - needs_check.push_back(resTree); + // BFS to find all uncollapsed paths of the resource directory. + TreeItem *res_subtree = root->get_first_child()->get_next(); + if (res_subtree) { + List<TreeItem *> queue; + queue.push_back(res_subtree); - while (needs_check.size()) { - if (!needs_check[0]->is_collapsed()) { - uncollapsed_paths.push_back(needs_check[0]->get_metadata(0)); - TreeItem *child = needs_check[0]->get_first_child(); - while (child) { - needs_check.push_back(child); - child = child->get_next(); + while (!queue.is_empty()) { + TreeItem *ti = queue.back()->get(); + queue.pop_back(); + if (!ti->is_collapsed() && ti->get_child_count() > 0) { + Variant path = ti->get_metadata(0); + if (path) { + uncollapsed_paths.push_back(path); } } - needs_check.remove_at(0); + for (int i = 0; i < ti->get_child_count(); i++) { + queue.push_back(ti->get_child(i)); + } } } } @@ -286,7 +288,7 @@ void FileSystemDock::_update_tree(const Vector<String> &p_uncollapsed_paths, boo ti->set_tooltip_text(0, favorite); ti->set_selectable(0, true); ti->set_metadata(0, favorite); - if (p_select_in_favorites && favorite == path) { + if (p_select_in_favorites && favorite == current_path) { ti->select(0); ti->set_as_cursor(0); } @@ -329,7 +331,7 @@ void FileSystemDock::_update_display_mode(bool p_force) { toolbar2_hbc->hide(); } - _update_tree(_compute_uncollapsed_paths()); + _update_tree(get_uncollapsed_paths()); file_list_vb->hide(); break; @@ -338,7 +340,7 @@ void FileSystemDock::_update_display_mode(bool p_force) { tree->set_v_size_flags(SIZE_EXPAND_FILL); tree->ensure_cursor_is_visible(); toolbar2_hbc->hide(); - _update_tree(_compute_uncollapsed_paths()); + _update_tree(get_uncollapsed_paths()); file_list_vb->show(); _update_file_list(true); @@ -388,7 +390,7 @@ void FileSystemDock::_notification(int p_what) { file_list_popup->connect("id_pressed", callable_mp(this, &FileSystemDock::_file_list_rmb_option)); tree_popup->connect("id_pressed", callable_mp(this, &FileSystemDock::_tree_rmb_option)); - current_path->connect("text_submitted", callable_mp(this, &FileSystemDock::_navigate_to_path).bind(false)); + current_path_line_edit->connect("text_submitted", callable_mp(this, &FileSystemDock::_navigate_to_path).bind(false)); always_show_folders = bool(EDITOR_GET("docks/filesystem/always_show_folders")); @@ -502,14 +504,14 @@ void FileSystemDock::_tree_multi_selected(Object *p_item, int p_column, bool p_s TreeItem *favorites_item = tree->get_root()->get_first_child(); if (selected->get_parent() == favorites_item && !String(selected->get_metadata(0)).ends_with("/")) { // Go to the favorites if we click in the favorites and the path has changed. - path = "Favorites"; + current_path = "Favorites"; } else { - path = selected->get_metadata(0); + current_path = selected->get_metadata(0); // Note: the "Favorites" item also leads to this path. } - // Set the current path. - _set_current_path_text(path); + // Display the current path. + _set_current_path_line_edit_text(current_path); _push_to_history(); // Update the file list. @@ -523,28 +525,28 @@ Vector<String> FileSystemDock::get_selected_paths() const { } String FileSystemDock::get_current_path() const { - return path; + return current_path; } String FileSystemDock::get_current_directory() const { - if (path.ends_with("/")) { - return path; + if (current_path.ends_with("/")) { + return current_path; } else { - return path.get_base_dir(); + return current_path.get_base_dir(); } } -void FileSystemDock::_set_current_path_text(const String &p_path) { +void FileSystemDock::_set_current_path_line_edit_text(const String &p_path) { if (p_path == "Favorites") { - current_path->set_text(TTR("Favorites")); + current_path_line_edit->set_text(TTR("Favorites")); } else { - current_path->set_text(path); + current_path_line_edit->set_text(current_path); } } void FileSystemDock::_navigate_to_path(const String &p_path, bool p_select_in_favorites) { if (p_path == "Favorites") { - path = p_path; + current_path = p_path; } else { String target_path = p_path; // If the path is a file, do not only go to the directory in the tree, also select the file in the file list. @@ -553,18 +555,18 @@ void FileSystemDock::_navigate_to_path(const String &p_path, bool p_select_in_fa } Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_RESOURCES); if (da->file_exists(p_path)) { - path = target_path; + current_path = target_path; } else if (da->dir_exists(p_path)) { - path = target_path + "/"; + current_path = target_path + "/"; } else { ERR_FAIL_MSG(vformat("Cannot navigate to '%s' as it has not been found in the file system!", p_path)); } } - _set_current_path_text(path); + _set_current_path_line_edit_text(current_path); _push_to_history(); - _update_tree(_compute_uncollapsed_paths(), false, p_select_in_favorites, true); + _update_tree(get_uncollapsed_paths(), false, p_select_in_favorites, true); if (display_mode == DISPLAY_MODE_SPLIT) { _update_file_list(false); files->get_v_scroll_bar()->set_value(0); @@ -588,7 +590,7 @@ void FileSystemDock::navigate_to_path(const String &p_path) { } void FileSystemDock::_file_list_thumbnail_done(const String &p_path, const Ref<Texture2D> &p_preview, const Ref<Texture2D> &p_small_preview, const Variant &p_udata) { - if ((file_list_vb->is_visible_in_tree() || path == p_path.get_base_dir()) && p_preview.is_valid()) { + if ((file_list_vb->is_visible_in_tree() || current_path == p_path.get_base_dir()) && p_preview.is_valid()) { Array uarr = p_udata; int idx = uarr[0]; String file = uarr[1]; @@ -749,9 +751,9 @@ void FileSystemDock::_update_file_list(bool p_keep_selection) { files->clear(); - _set_current_path_text(path); + _set_current_path_line_edit_text(current_path); - String directory = path; + String directory = current_path; String file = ""; int thumbnail_size = EDITOR_GET("docks/filesystem/thumbnail_size"); @@ -793,7 +795,7 @@ void FileSystemDock::_update_file_list(bool p_keep_selection) { // Build the FileInfo list. List<FileInfo> file_list; - if (path == "Favorites") { + if (current_path == "Favorites") { // Display the favorites. Vector<String> favorites_list = EditorSettings::get_singleton()->get_favorites(); for (const String &favorite : favorites_list) { @@ -842,8 +844,8 @@ void FileSystemDock::_update_file_list(bool p_keep_selection) { } EditorFileSystemDirectory *efd = EditorFileSystem::get_singleton()->get_filesystem_path(directory); if (!efd) { - directory = path.get_base_dir(); - file = path.get_file(); + directory = current_path.get_base_dir(); + file = current_path.get_file(); efd = EditorFileSystem::get_singleton()->get_filesystem_path(directory); } if (!efd) { @@ -1084,7 +1086,7 @@ void FileSystemDock::_file_list_activate_file(int p_idx) { } void FileSystemDock::_preview_invalidated(const String &p_path) { - if (file_list_display_mode == FILE_LIST_DISPLAY_THUMBNAILS && p_path.get_base_dir() == path && searched_string.length() == 0 && file_list_vb->is_visible_in_tree()) { + if (file_list_display_mode == FILE_LIST_DISPLAY_THUMBNAILS && p_path.get_base_dir() == current_path && searched_string.length() == 0 && file_list_vb->is_visible_in_tree()) { for (int i = 0; i < files->get_item_count(); i++) { if (files->get_item_metadata(i) == p_path) { // Re-request preview. @@ -1106,7 +1108,7 @@ void FileSystemDock::_fs_changed() { split_box->show(); if (tree->is_visible()) { - _update_tree(_compute_uncollapsed_paths()); + _update_tree(get_uncollapsed_paths()); } if (file_list_vb->is_visible()) { @@ -1146,11 +1148,11 @@ void FileSystemDock::_bw_history() { } void FileSystemDock::_update_history() { - path = history[history_pos]; - _set_current_path_text(path); + current_path = history[history_pos]; + _set_current_path_line_edit_text(current_path); if (tree->is_visible()) { - _update_tree(_compute_uncollapsed_paths()); + _update_tree(get_uncollapsed_paths()); tree->grab_focus(); tree->ensure_cursor_is_visible(); } @@ -1164,9 +1166,9 @@ void FileSystemDock::_update_history() { } void FileSystemDock::_push_to_history() { - if (history[history_pos] != path) { + if (history[history_pos] != current_path) { history.resize(history_pos + 1); - history.push_back(path); + history.push_back(current_path); history_pos++; if (history.size() > history_max_size) { @@ -1255,7 +1257,7 @@ void FileSystemDock::_try_move_item(const FileOrFolder &p_item, const String &p_ for (int j = 0; j < ed->get_edited_scene_count(); j++) { if (ed->get_scene_path(j) == file_changed_paths[i]) { ed->get_edited_scene_root(j)->set_scene_file_path(new_item_path); - EditorNode::get_singleton()->save_layout(); + EditorNode::get_singleton()->save_editor_layout_delayed(); break; } } @@ -1292,7 +1294,7 @@ void FileSystemDock::_try_duplicate_item(const FileOrFolder &p_item, const Strin EditorNode::get_singleton()->add_io_error(TTR("Cannot move a folder into itself.") + "\n" + old_path + "\n"); return; } - const_cast<FileSystemDock *>(this)->path = new_path; + const_cast<FileSystemDock *>(this)->current_path = new_path; Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_RESOURCES); @@ -1532,48 +1534,66 @@ void FileSystemDock::_file_removed(String p_file) { emit_signal(SNAME("file_removed"), p_file); // Find the closest parent directory available, in case multiple items were deleted along the same path. - path = p_file.get_base_dir(); + current_path = p_file.get_base_dir(); Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_RESOURCES); - while (!da->dir_exists(path)) { - path = path.get_base_dir(); + while (!da->dir_exists(current_path)) { + current_path = current_path.get_base_dir(); } - current_path->set_text(path); + current_path_line_edit->set_text(current_path); } void FileSystemDock::_folder_removed(String p_folder) { emit_signal(SNAME("folder_removed"), p_folder); // Find the closest parent directory available, in case multiple items were deleted along the same path. - path = p_folder.get_base_dir(); + current_path = p_folder.get_base_dir(); Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_RESOURCES); - while (!da->dir_exists(path)) { - path = path.get_base_dir(); + while (!da->dir_exists(current_path)) { + current_path = current_path.get_base_dir(); } - current_path->set_text(path); - EditorFileSystemDirectory *efd = EditorFileSystem::get_singleton()->get_filesystem_path(path); + current_path_line_edit->set_text(current_path); + EditorFileSystemDirectory *efd = EditorFileSystem::get_singleton()->get_filesystem_path(current_path); if (efd) { efd->force_update(); } } void FileSystemDock::_rename_operation_confirm() { - String new_name = rename_dialog_text->get_text().strip_edges(); + if (!tree->is_anything_selected()) { + return; + } + TreeItem *s = tree->get_selected(); + int col_index = tree->get_selected_column(); + String new_name = s->get_text(col_index); + new_name = new_name.strip_edges(); + String old_name = to_rename.is_file ? to_rename.path.get_file() : to_rename.path.left(-1).get_file(); + + bool rename_error = false; if (new_name.length() == 0) { EditorNode::get_singleton()->show_warning(TTR("No name provided.")); - return; + rename_error = true; } else if (new_name.contains("/") || new_name.contains("\\") || new_name.contains(":")) { EditorNode::get_singleton()->show_warning(TTR("Name contains invalid characters.")); - return; + rename_error = true; + } else if (new_name.begins_with(".")) { + EditorNode::get_singleton()->show_warning(TTR("This filename begins with a dot rendering the file invisible to the editor.\nIf you want to rename it anyway, use your operating system's file manager.")); + rename_error = true; } else if (to_rename.is_file && to_rename.path.get_extension() != new_name.get_extension()) { if (!EditorFileSystem::get_singleton()->get_valid_extensions().find(new_name.get_extension())) { EditorNode::get_singleton()->show_warning(TTR("This file extension is not recognized by the editor.\nIf you want to rename it anyway, use your operating system's file manager.\nAfter renaming to an unknown extension, the file won't be shown in the editor anymore.")); - return; + rename_error = true; } } - String old_path = to_rename.path.ends_with("/") ? to_rename.path.substr(0, to_rename.path.length() - 1) : to_rename.path; + // Restores Tree to restore original names. + if (rename_error) { + s->set_text(col_index, old_name); + return; + } + + String old_path = to_rename.path.ends_with("/") ? to_rename.path.left(-1) : to_rename.path; String new_path = old_path.get_base_dir().path_join(new_name); if (old_path == new_path) { return; @@ -1592,6 +1612,7 @@ void FileSystemDock::_rename_operation_confirm() { if (da->file_exists(new_path) || da->dir_exists(new_path)) { #endif EditorNode::get_singleton()->show_warning(TTR("A file or folder with this name already exists.")); + s->set_text(col_index, old_name); return; } @@ -1614,8 +1635,8 @@ void FileSystemDock::_rename_operation_confirm() { print_verbose("FileSystem: saving moved scenes."); _save_scenes_after_move(file_renames); - path = new_path; - current_path->set_text(path); + current_path = new_path; + current_path_line_edit->set_text(current_path); } void FileSystemDock::_duplicate_operation_confirm() { @@ -1758,8 +1779,8 @@ void FileSystemDock::_move_operation_confirm(const String &p_to_path, bool p_cop print_verbose("FileSystem: saving moved scenes."); _save_scenes_after_move(file_renames); - path = p_to_path; - current_path->set_text(path); + current_path = p_to_path; + current_path_line_edit->set_text(current_path); } } } @@ -1840,8 +1861,8 @@ void FileSystemDock::_file_option(int p_option, const Vector<String> &p_selected switch (p_option) { case FILE_SHOW_IN_EXPLORER: { // Show the file/folder in the OS explorer. - String fpath = path; - if (path == "Favorites") { + String fpath = current_path; + if (current_path == "Favorites") { fpath = p_selected[0]; } @@ -1850,8 +1871,8 @@ void FileSystemDock::_file_option(int p_option, const Vector<String> &p_selected } break; case FILE_OPEN_EXTERNAL: { - String fpath = path; - if (path == "Favorites") { + String fpath = current_path; + if (current_path == "Favorites") { fpath = p_selected[0]; } @@ -1914,7 +1935,7 @@ void FileSystemDock::_file_option(int p_option, const Vector<String> &p_selected if (p_selected.size() == 1) { ProjectSettings::get_singleton()->set("application/run/main_scene", p_selected[0]); ProjectSettings::get_singleton()->save(); - _update_tree(_compute_uncollapsed_paths()); + _update_tree(get_uncollapsed_paths()); _update_file_list(true); } } break; @@ -1942,7 +1963,7 @@ void FileSystemDock::_file_option(int p_option, const Vector<String> &p_selected } } EditorSettings::get_singleton()->set_favorites(favorites_list); - _update_tree(_compute_uncollapsed_paths()); + _update_tree(get_uncollapsed_paths()); } break; case FILE_REMOVE_FAVORITE: { @@ -1952,8 +1973,8 @@ void FileSystemDock::_file_option(int p_option, const Vector<String> &p_selected favorites_list.erase(p_selected[i]); } EditorSettings::get_singleton()->set_favorites(favorites_list); - _update_tree(_compute_uncollapsed_paths()); - if (path == "Favorites") { + _update_tree(get_uncollapsed_paths()); + if (current_path == "Favorites") { _update_file_list(true); } } break; @@ -1990,24 +2011,21 @@ void FileSystemDock::_file_option(int p_option, const Vector<String> &p_selected } break; case FILE_RENAME: { - // Rename the active file. - if (!p_selected.is_empty()) { + if (tree->is_anything_selected() && !p_selected.is_empty()) { + // Set to_rename variable for callback execution. to_rename.path = p_selected[0]; - if (to_rename.path != "res://") { - to_rename.is_file = !to_rename.path.ends_with("/"); - if (to_rename.is_file) { - String name = to_rename.path.get_file(); - rename_dialog->set_title(TTR("Renaming file:") + " " + name); - rename_dialog_text->set_text(name); - rename_dialog_text->select(0, name.rfind(".")); - } else { - String name = to_rename.path.substr(0, to_rename.path.length() - 1).get_file(); - rename_dialog->set_title(TTR("Renaming folder:") + " " + name); - rename_dialog_text->set_text(name); - rename_dialog_text->select(0, name.length()); - } - rename_dialog->popup_centered(Size2(250, 80) * EDSCALE); - rename_dialog_text->grab_focus(); + to_rename.is_file = !to_rename.path.ends_with("/"); + + // Edit node in Tree. + tree->grab_focus(); + tree->edit_selected(true); + + if (to_rename.is_file) { + String name = to_rename.path.get_file(); + tree->set_editor_selection(0, name.rfind(".")); + } else { + String name = to_rename.path.left(-1).get_file(); // Removes the "/" suffix for folders. + tree->set_editor_selection(0, name.length()); } } } break; @@ -2069,7 +2087,7 @@ void FileSystemDock::_file_option(int p_option, const Vector<String> &p_selected } break; case FILE_NEW_FOLDER: { - String directory = path; + String directory = current_path; if (!directory.ends_with("/")) { directory = directory.get_base_dir(); } @@ -2078,7 +2096,7 @@ void FileSystemDock::_file_option(int p_option, const Vector<String> &p_selected } break; case FILE_NEW_SCENE: { - String directory = path; + String directory = current_path; if (!directory.ends_with("/")) { directory = directory.get_base_dir(); } @@ -2087,7 +2105,7 @@ void FileSystemDock::_file_option(int p_option, const Vector<String> &p_selected } break; case FILE_NEW_SCRIPT: { - String fpath = path; + String fpath = current_path; if (!fpath.ends_with("/")) { fpath = fpath.get_base_dir(); } @@ -2116,7 +2134,7 @@ void FileSystemDock::_file_option(int p_option, const Vector<String> &p_selected new_resource_dialog->popup_create(true); } break; case FILE_NEW_TEXTFILE: { - String fpath = path; + String fpath = current_path; if (!fpath.ends_with("/")) { fpath = fpath.get_base_dir(); } @@ -2127,7 +2145,7 @@ void FileSystemDock::_file_option(int p_option, const Vector<String> &p_selected } void FileSystemDock::_resource_created() { - String fpath = path; + String fpath = current_path; if (!fpath.ends_with("/")) { fpath = fpath.get_base_dir(); } @@ -2168,7 +2186,7 @@ void FileSystemDock::_resource_created() { void FileSystemDock::_search_changed(const String &p_text, const Control *p_from) { if (searched_string.length() == 0) { // Register the uncollapsed paths before they change. - uncollapsed_paths_before_search = _compute_uncollapsed_paths(); + uncollapsed_paths_before_search = get_uncollapsed_paths(); } searched_string = p_text.to_lower(); @@ -2179,7 +2197,7 @@ void FileSystemDock::_search_changed(const String &p_text, const Control *p_from tree_search_box->set_text(searched_string); } - bool unfold_path = (p_text.is_empty() && !path.is_empty()); + bool unfold_path = (p_text.is_empty() && !current_path.is_empty()); switch (display_mode) { case DISPLAY_MODE_TREE_ONLY: { _update_tree(searched_string.length() == 0 ? uncollapsed_paths_before_search : Vector<String>(), false, false, unfold_path); @@ -2417,9 +2435,9 @@ void FileSystemDock::drop_data_fw(const Point2 &p_point, const Variant &p_data, } EditorSettings::get_singleton()->set_favorites(dirs); - _update_tree(_compute_uncollapsed_paths()); + _update_tree(get_uncollapsed_paths()); - if (display_mode == DISPLAY_MODE_SPLIT && path == "Favorites") { + if (display_mode == DISPLAY_MODE_SPLIT && current_path == "Favorites") { _update_file_list(true); } return; @@ -2467,7 +2485,7 @@ void FileSystemDock::drop_data_fw(const Point2 &p_point, const Variant &p_data, } } EditorSettings::get_singleton()->set_favorites(favorites_list); - _update_tree(_compute_uncollapsed_paths()); + _update_tree(get_uncollapsed_paths()); } } @@ -2491,7 +2509,7 @@ void FileSystemDock::_get_drag_target_folder(String &target, bool &target_favori } String ltarget = files->get_item_metadata(pos); - target = ltarget.ends_with("/") ? ltarget : path.get_base_dir(); + target = ltarget.ends_with("/") ? ltarget : current_path.get_base_dir(); return; } @@ -2674,7 +2692,7 @@ void FileSystemDock::_file_and_folders_fill_popup(PopupMenu *p_popup, Vector<Str } #endif - path = fpath; + current_path = fpath; } } @@ -2702,7 +2720,7 @@ void FileSystemDock::_tree_empty_click(const Vector2 &p_pos, MouseButton p_butto return; } // Right click is pressed in the empty space of the tree. - path = "res://"; + current_path = "res://"; tree_popup->clear(); tree_popup->reset_size(); tree_popup->add_icon_item(get_theme_icon(SNAME("Folder"), SNAME("EditorIcons")), TTR("New Folder..."), FILE_NEW_FOLDER); @@ -2763,7 +2781,7 @@ void FileSystemDock::_file_list_empty_clicked(const Vector2 &p_pos, MouseButton return; } - path = current_path->get_text(); + current_path = current_path_line_edit->get_text(); file_list_popup->clear(); file_list_popup->reset_size(); @@ -2791,9 +2809,9 @@ void FileSystemDock::_file_multi_selected(int p_index, bool p_selected) { if (current == p_index) { String fpath = files->get_item_metadata(current); if (!fpath.ends_with("/")) { - path = fpath; + current_path = fpath; if (display_mode == DISPLAY_MODE_SPLIT) { - _update_tree(_compute_uncollapsed_paths()); + _update_tree(get_uncollapsed_paths()); } } } @@ -3076,7 +3094,7 @@ void FileSystemDock::set_file_sort(FileSortOption p_file_sort) { file_sort = p_file_sort; // Update everything needed. - _update_tree(_compute_uncollapsed_paths()); + _update_tree(get_uncollapsed_paths()); _update_file_list(true); } @@ -3127,7 +3145,7 @@ void FileSystemDock::_bind_methods() { FileSystemDock::FileSystemDock() { singleton = this; set_name("FileSystem"); - path = "res://"; + current_path = "res://"; // `KeyModifierMask::CMD_OR_CTRL | Key::C` conflicts with other editor shortcuts. ED_SHORTCUT("filesystem_dock/copy_path", TTR("Copy Path"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::C); @@ -3163,11 +3181,11 @@ FileSystemDock::FileSystemDock() { button_hist_next->set_tooltip_text(TTR("Go to next selected folder/file.")); toolbar_hbc->add_child(button_hist_next); - current_path = memnew(LineEdit); - current_path->set_structured_text_bidi_override(TextServer::STRUCTURED_TEXT_FILE); - current_path->set_h_size_flags(SIZE_EXPAND_FILL); - _set_current_path_text(path); - toolbar_hbc->add_child(current_path); + current_path_line_edit = memnew(LineEdit); + current_path_line_edit->set_structured_text_bidi_override(TextServer::STRUCTURED_TEXT_FILE); + current_path_line_edit->set_h_size_flags(SIZE_EXPAND_FILL); + _set_current_path_line_edit_text(current_path); + toolbar_hbc->add_child(current_path_line_edit); button_reload = memnew(Button); button_reload->connect("pressed", callable_mp(this, &FileSystemDock::_rescan)); @@ -3226,6 +3244,7 @@ FileSystemDock::FileSystemDock() { tree->connect("nothing_selected", callable_mp(this, &FileSystemDock::_tree_empty_selected)); tree->connect("gui_input", callable_mp(this, &FileSystemDock::_tree_gui_input)); tree->connect("mouse_exited", callable_mp(this, &FileSystemDock::_tree_mouse_exited)); + tree->connect("item_edited", callable_mp(this, &FileSystemDock::_rename_operation_confirm)); file_list_vb = memnew(VBoxContainer); file_list_vb->set_v_size_flags(SIZE_EXPAND_FILL); @@ -3287,17 +3306,6 @@ FileSystemDock::FileSystemDock() { add_child(move_dialog); move_dialog->connect("dir_selected", callable_mp(this, &FileSystemDock::_move_dialog_confirm)); - rename_dialog = memnew(ConfirmationDialog); - VBoxContainer *rename_dialog_vb = memnew(VBoxContainer); - rename_dialog->add_child(rename_dialog_vb); - - rename_dialog_text = memnew(LineEdit); - rename_dialog_vb->add_margin_child(TTR("Name:"), rename_dialog_text); - rename_dialog->set_ok_button_text(TTR("Rename")); - add_child(rename_dialog); - rename_dialog->register_text_enter(rename_dialog_text); - rename_dialog->connect("confirmed", callable_mp(this, &FileSystemDock::_rename_operation_confirm)); - overwrite_dialog = memnew(ConfirmationDialog); add_child(overwrite_dialog); overwrite_dialog->set_ok_button_text(TTR("Overwrite")); diff --git a/editor/filesystem_dock.h b/editor/filesystem_dock.h index e47178d294..05a6372830 100644 --- a/editor/filesystem_dock.h +++ b/editor/filesystem_dock.h @@ -125,7 +125,7 @@ private: Button *button_file_list_display_mode = nullptr; Button *button_hist_next = nullptr; Button *button_hist_prev = nullptr; - LineEdit *current_path = nullptr; + LineEdit *current_path_line_edit = nullptr; HBoxContainer *toolbar2_hbc = nullptr; LineEdit *tree_search_box = nullptr; @@ -152,8 +152,6 @@ private: DependencyRemoveDialog *remove_dialog = nullptr; EditorDirDialog *move_dialog = nullptr; - ConfirmationDialog *rename_dialog = nullptr; - LineEdit *rename_dialog_text = nullptr; ConfirmationDialog *duplicate_dialog = nullptr; LineEdit *duplicate_dialog_text = nullptr; DirectoryCreateDialog *make_dir_dialog = nullptr; @@ -185,7 +183,7 @@ private: int history_pos; int history_max_size; - String path; + String current_path; bool initialized = false; @@ -204,7 +202,6 @@ private: Ref<Texture2D> _get_tree_item_icon(bool p_is_valid, String p_file_type); bool _create_tree(TreeItem *p_parent, EditorFileSystemDirectory *p_dir, Vector<String> &uncollapsed_paths, bool p_select_in_favorites, bool p_unfold_path = false); - Vector<String> _compute_uncollapsed_paths(); void _update_tree(const Vector<String> &p_uncollapsed_paths = Vector<String>(), bool p_uncollapse_root = false, bool p_select_in_favorites = false, bool p_unfold_path = false); void _navigate_to_path(const String &p_path, bool p_select_in_favorites = false); @@ -295,7 +292,7 @@ private: void _search(EditorFileSystemDirectory *p_path, List<FileInfo> *matches, int p_max_items); - void _set_current_path_text(const String &p_path); + void _set_current_path_line_edit_text(const String &p_path); Variant get_drag_data_fw(const Point2 &p_point, Control *p_from); bool can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const; @@ -327,6 +324,7 @@ protected: public: Vector<String> get_selected_paths() const; + Vector<String> get_uncollapsed_paths() const; String get_current_path() const; String get_current_directory() const; @@ -351,6 +349,8 @@ public: void set_file_list_display_mode(FileListDisplayMode p_mode); FileListDisplayMode get_file_list_display_mode() { return file_list_display_mode; }; + Tree *get_tree_control() { return tree; } + FileSystemDock(); ~FileSystemDock(); }; diff --git a/editor/icons/MakeFloating.svg b/editor/icons/MakeFloating.svg new file mode 100644 index 0000000000..57ccce38ea --- /dev/null +++ b/editor/icons/MakeFloating.svg @@ -0,0 +1 @@ +<svg height="16" viewBox="0 0 4.2333333 4.2333333" width="16" xmlns="http://www.w3.org/2000/svg"><g fill="#e0e0e0"><path d="m1.2907908.14089245c-.258841 0-.46866132.20982028-.46866132.46866151v.23432634h3.28061252v-.23432634c0-.25884123-.2098291-.46866151-.4686613-.46866151zm2.1089635.23433517h.2343264v.23432634h-.2343264zm-2.57762482.70298788v1.8746284c0 .2588412.20982912.4686614.46866132.4686614h2.3432899c.258841 0 .4686613-.2098202.4686613-.4686614v-1.8746284z" stroke-width=".23433"/><path d="m12.189144-6.0533422 5.5-5.4999998-2.44-2.439h7v6.9999998l-2.439-2.439-5.5 5.5z" stroke="#000" stroke-width="1.01435" transform="matrix(.19814944 0 0 .19814944 -2.163454 4.759098)"/></g></svg> diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp index f9ab37dce2..05a024f913 100644 --- a/editor/plugins/canvas_item_editor_plugin.cpp +++ b/editor/plugins/canvas_item_editor_plugin.cpp @@ -3981,10 +3981,6 @@ void CanvasItemEditor::edit(CanvasItem *p_canvas_item) { Array selection = editor_selection->get_selected_nodes(); if (selection.size() != 1 || Object::cast_to<Node>(selection[0]) != p_canvas_item) { _reset_drag(); - - // Clear the selection - editor_selection->clear(); //_clear_canvas_items(); - editor_selection->add_node(p_canvas_item); } } diff --git a/editor/plugins/curve_editor_plugin.cpp b/editor/plugins/curve_editor_plugin.cpp index 72cd1da9dd..7c87f009a6 100644 --- a/editor/plugins/curve_editor_plugin.cpp +++ b/editor/plugins/curve_editor_plugin.cpp @@ -798,7 +798,7 @@ bool CurvePreviewGenerator::handles(const String &p_type) const { return p_type == "Curve"; } -Ref<Texture2D> CurvePreviewGenerator::generate(const Ref<Resource> &p_from, const Size2 &p_size) const { +Ref<Texture2D> CurvePreviewGenerator::generate(const Ref<Resource> &p_from, const Size2 &p_size, Dictionary &p_metadata) const { Ref<Curve> curve_ref = p_from; ERR_FAIL_COND_V_MSG(curve_ref.is_null(), Ref<Texture2D>(), "It's not a reference to a valid Resource object."); Curve &curve = **curve_ref; diff --git a/editor/plugins/curve_editor_plugin.h b/editor/plugins/curve_editor_plugin.h index b0d666b847..903f8d593e 100644 --- a/editor/plugins/curve_editor_plugin.h +++ b/editor/plugins/curve_editor_plugin.h @@ -142,7 +142,7 @@ class CurvePreviewGenerator : public EditorResourcePreviewGenerator { public: virtual bool handles(const String &p_type) const override; - virtual Ref<Texture2D> generate(const Ref<Resource> &p_from, const Size2 &p_size) const override; + virtual Ref<Texture2D> generate(const Ref<Resource> &p_from, const Size2 &p_size, Dictionary &p_metadata) const override; }; #endif // CURVE_EDITOR_PLUGIN_H diff --git a/editor/plugins/debugger_editor_plugin.cpp b/editor/plugins/debugger_editor_plugin.cpp index aecf3d295c..3068ad3f93 100644 --- a/editor/plugins/debugger_editor_plugin.cpp +++ b/editor/plugins/debugger_editor_plugin.cpp @@ -77,6 +77,9 @@ DebuggerEditorPlugin::DebuggerEditorPlugin(PopupMenu *p_debug_menu) { debug_menu->add_check_shortcut(ED_SHORTCUT("editor/visible_navigation", TTR("Visible Navigation")), RUN_DEBUG_NAVIGATION); debug_menu->set_item_tooltip(-1, TTR("When this option is enabled, navigation meshes and polygons will be visible in the running project.")); + debug_menu->add_check_shortcut(ED_SHORTCUT("editor/visible_avoidance", TTR("Visible Avoidance")), RUN_DEBUG_AVOIDANCE); + debug_menu->set_item_tooltip(-1, + TTR("When this option is enabled, avoidance objects shapes, radius and velocities will be visible in the running project.")); debug_menu->add_separator(); debug_menu->add_check_shortcut(ED_SHORTCUT("editor/sync_scene_changes", TTR("Synchronize Scene Changes")), RUN_LIVE_DEBUG); debug_menu->set_item_tooltip(-1, @@ -168,6 +171,12 @@ void DebuggerEditorPlugin::_menu_option(int p_option) { EditorSettings::get_singleton()->set_project_metadata("debug_options", "run_debug_navigation", !ischecked); } break; + case RUN_DEBUG_AVOIDANCE: { + bool ischecked = debug_menu->is_item_checked(debug_menu->get_item_index(RUN_DEBUG_AVOIDANCE)); + debug_menu->set_item_checked(debug_menu->get_item_index(RUN_DEBUG_AVOIDANCE), !ischecked); + EditorSettings::get_singleton()->set_project_metadata("debug_options", "run_debug_avoidance", !ischecked); + + } break; case RUN_RELOAD_SCRIPTS: { bool ischecked = debug_menu->is_item_checked(debug_menu->get_item_index(RUN_RELOAD_SCRIPTS)); debug_menu->set_item_checked(debug_menu->get_item_index(RUN_RELOAD_SCRIPTS), !ischecked); @@ -205,6 +214,7 @@ void DebuggerEditorPlugin::_update_debug_options() { bool check_debug_collisions = EditorSettings::get_singleton()->get_project_metadata("debug_options", "run_debug_collisions", false); bool check_debug_paths = EditorSettings::get_singleton()->get_project_metadata("debug_options", "run_debug_paths", false); bool check_debug_navigation = EditorSettings::get_singleton()->get_project_metadata("debug_options", "run_debug_navigation", false); + bool check_debug_avoidance = EditorSettings::get_singleton()->get_project_metadata("debug_options", "run_debug_avoidance", false); bool check_live_debug = EditorSettings::get_singleton()->get_project_metadata("debug_options", "run_live_debug", true); bool check_reload_scripts = EditorSettings::get_singleton()->get_project_metadata("debug_options", "run_reload_scripts", true); bool check_server_keep_open = EditorSettings::get_singleton()->get_project_metadata("debug_options", "server_keep_open", false); @@ -225,6 +235,9 @@ void DebuggerEditorPlugin::_update_debug_options() { if (check_debug_navigation) { _menu_option(RUN_DEBUG_NAVIGATION); } + if (check_debug_avoidance) { + _menu_option(RUN_DEBUG_AVOIDANCE); + } if (check_live_debug) { _menu_option(RUN_LIVE_DEBUG); } diff --git a/editor/plugins/debugger_editor_plugin.h b/editor/plugins/debugger_editor_plugin.h index 6e2d2c02ee..eb8da7ca8e 100644 --- a/editor/plugins/debugger_editor_plugin.h +++ b/editor/plugins/debugger_editor_plugin.h @@ -51,6 +51,7 @@ private: RUN_DEBUG_COLLISIONS, RUN_DEBUG_PATHS, RUN_DEBUG_NAVIGATION, + RUN_DEBUG_AVOIDANCE, RUN_DEPLOY_REMOTE_DEBUG, RUN_RELOAD_SCRIPTS, SERVER_KEEP_OPEN, diff --git a/editor/plugins/editor_preview_plugins.cpp b/editor/plugins/editor_preview_plugins.cpp index 523e7703c4..2b0691b36f 100644 --- a/editor/plugins/editor_preview_plugins.cpp +++ b/editor/plugins/editor_preview_plugins.cpp @@ -79,7 +79,7 @@ bool EditorTexturePreviewPlugin::generate_small_preview_automatically() const { return true; } -Ref<Texture2D> EditorTexturePreviewPlugin::generate(const Ref<Resource> &p_from, const Size2 &p_size) const { +Ref<Texture2D> EditorTexturePreviewPlugin::generate(const Ref<Resource> &p_from, const Size2 &p_size, Dictionary &p_metadata) const { Ref<Image> img; Ref<AtlasTexture> atex = p_from; if (atex.is_valid()) { @@ -107,6 +107,7 @@ Ref<Texture2D> EditorTexturePreviewPlugin::generate(const Ref<Resource> &p_from, if (img.is_null() || img->is_empty()) { return Ref<Texture2D>(); } + p_metadata["dimensions"] = img->get_size(); img->clear_mipmaps(); @@ -141,7 +142,7 @@ bool EditorImagePreviewPlugin::handles(const String &p_type) const { return p_type == "Image"; } -Ref<Texture2D> EditorImagePreviewPlugin::generate(const Ref<Resource> &p_from, const Size2 &p_size) const { +Ref<Texture2D> EditorImagePreviewPlugin::generate(const Ref<Resource> &p_from, const Size2 &p_size, Dictionary &p_metadata) const { Ref<Image> img = p_from; if (img.is_null() || img->is_empty()) { @@ -185,7 +186,7 @@ bool EditorBitmapPreviewPlugin::handles(const String &p_type) const { return ClassDB::is_parent_class(p_type, "BitMap"); } -Ref<Texture2D> EditorBitmapPreviewPlugin::generate(const Ref<Resource> &p_from, const Size2 &p_size) const { +Ref<Texture2D> EditorBitmapPreviewPlugin::generate(const Ref<Resource> &p_from, const Size2 &p_size, Dictionary &p_metadata) const { Ref<BitMap> bm = p_from; if (bm->get_size() == Size2()) { @@ -246,11 +247,11 @@ bool EditorPackedScenePreviewPlugin::handles(const String &p_type) const { return ClassDB::is_parent_class(p_type, "PackedScene"); } -Ref<Texture2D> EditorPackedScenePreviewPlugin::generate(const Ref<Resource> &p_from, const Size2 &p_size) const { - return generate_from_path(p_from->get_path(), p_size); +Ref<Texture2D> EditorPackedScenePreviewPlugin::generate(const Ref<Resource> &p_from, const Size2 &p_size, Dictionary &p_metadata) const { + return generate_from_path(p_from->get_path(), p_size, p_metadata); } -Ref<Texture2D> EditorPackedScenePreviewPlugin::generate_from_path(const String &p_path, const Size2 &p_size) const { +Ref<Texture2D> EditorPackedScenePreviewPlugin::generate_from_path(const String &p_path, const Size2 &p_size, Dictionary &p_metadata) const { String temp_path = EditorPaths::get_singleton()->get_cache_dir(); String cache_base = ProjectSettings::get_singleton()->globalize_path(p_path).md5_text(); cache_base = temp_path.path_join("resthumb-" + cache_base); @@ -298,7 +299,7 @@ bool EditorMaterialPreviewPlugin::generate_small_preview_automatically() const { return true; } -Ref<Texture2D> EditorMaterialPreviewPlugin::generate(const Ref<Resource> &p_from, const Size2 &p_size) const { +Ref<Texture2D> EditorMaterialPreviewPlugin::generate(const Ref<Resource> &p_from, const Size2 &p_size, Dictionary &p_metadata) const { Ref<Material> material = p_from; ERR_FAIL_COND_V(material.is_null(), Ref<Texture2D>()); @@ -455,7 +456,7 @@ bool EditorScriptPreviewPlugin::handles(const String &p_type) const { return ClassDB::is_parent_class(p_type, "Script"); } -Ref<Texture2D> EditorScriptPreviewPlugin::generate(const Ref<Resource> &p_from, const Size2 &p_size) const { +Ref<Texture2D> EditorScriptPreviewPlugin::generate(const Ref<Resource> &p_from, const Size2 &p_size, Dictionary &p_metadata) const { Ref<Script> scr = p_from; if (scr.is_null()) { return Ref<Texture2D>(); @@ -590,7 +591,7 @@ bool EditorAudioStreamPreviewPlugin::handles(const String &p_type) const { return ClassDB::is_parent_class(p_type, "AudioStream"); } -Ref<Texture2D> EditorAudioStreamPreviewPlugin::generate(const Ref<Resource> &p_from, const Size2 &p_size) const { +Ref<Texture2D> EditorAudioStreamPreviewPlugin::generate(const Ref<Resource> &p_from, const Size2 &p_size, Dictionary &p_metadata) const { Ref<AudioStream> stream = p_from; ERR_FAIL_COND_V(stream.is_null(), Ref<Texture2D>()); @@ -680,7 +681,7 @@ bool EditorMeshPreviewPlugin::handles(const String &p_type) const { return ClassDB::is_parent_class(p_type, "Mesh"); // Any mesh. } -Ref<Texture2D> EditorMeshPreviewPlugin::generate(const Ref<Resource> &p_from, const Size2 &p_size) const { +Ref<Texture2D> EditorMeshPreviewPlugin::generate(const Ref<Resource> &p_from, const Size2 &p_size, Dictionary &p_metadata) const { Ref<Mesh> mesh = p_from; ERR_FAIL_COND_V(mesh.is_null(), Ref<Texture2D>()); @@ -797,7 +798,7 @@ bool EditorFontPreviewPlugin::handles(const String &p_type) const { return ClassDB::is_parent_class(p_type, "Font"); } -Ref<Texture2D> EditorFontPreviewPlugin::generate_from_path(const String &p_path, const Size2 &p_size) const { +Ref<Texture2D> EditorFontPreviewPlugin::generate_from_path(const String &p_path, const Size2 &p_size, Dictionary &p_metadata) const { Ref<Font> sampled_font = ResourceLoader::load(p_path); ERR_FAIL_COND_V(sampled_font.is_null(), Ref<Texture2D>()); @@ -846,12 +847,12 @@ Ref<Texture2D> EditorFontPreviewPlugin::generate_from_path(const String &p_path, return ImageTexture::create_from_image(img); } -Ref<Texture2D> EditorFontPreviewPlugin::generate(const Ref<Resource> &p_from, const Size2 &p_size) const { +Ref<Texture2D> EditorFontPreviewPlugin::generate(const Ref<Resource> &p_from, const Size2 &p_size, Dictionary &p_metadata) const { String path = p_from->get_path(); if (!FileAccess::exists(path)) { return Ref<Texture2D>(); } - return generate_from_path(path, p_size); + return generate_from_path(path, p_size, p_metadata); } EditorFontPreviewPlugin::EditorFontPreviewPlugin() { @@ -887,7 +888,7 @@ bool EditorGradientPreviewPlugin::generate_small_preview_automatically() const { return true; } -Ref<Texture2D> EditorGradientPreviewPlugin::generate(const Ref<Resource> &p_from, const Size2 &p_size) const { +Ref<Texture2D> EditorGradientPreviewPlugin::generate(const Ref<Resource> &p_from, const Size2 &p_size, Dictionary &p_metadata) const { Ref<Gradient> gradient = p_from; if (gradient.is_valid()) { Ref<GradientTexture1D> ptex; diff --git a/editor/plugins/editor_preview_plugins.h b/editor/plugins/editor_preview_plugins.h index 6e4b73481c..6534f31ad8 100644 --- a/editor/plugins/editor_preview_plugins.h +++ b/editor/plugins/editor_preview_plugins.h @@ -42,7 +42,7 @@ class EditorTexturePreviewPlugin : public EditorResourcePreviewGenerator { public: virtual bool handles(const String &p_type) const override; virtual bool generate_small_preview_automatically() const override; - virtual Ref<Texture2D> generate(const Ref<Resource> &p_from, const Size2 &p_size) const override; + virtual Ref<Texture2D> generate(const Ref<Resource> &p_from, const Size2 &p_size, Dictionary &p_metadata) const override; EditorTexturePreviewPlugin(); }; @@ -53,7 +53,7 @@ class EditorImagePreviewPlugin : public EditorResourcePreviewGenerator { public: virtual bool handles(const String &p_type) const override; virtual bool generate_small_preview_automatically() const override; - virtual Ref<Texture2D> generate(const Ref<Resource> &p_from, const Size2 &p_size) const override; + virtual Ref<Texture2D> generate(const Ref<Resource> &p_from, const Size2 &p_size, Dictionary &p_metadata) const override; EditorImagePreviewPlugin(); }; @@ -64,7 +64,7 @@ class EditorBitmapPreviewPlugin : public EditorResourcePreviewGenerator { public: virtual bool handles(const String &p_type) const override; virtual bool generate_small_preview_automatically() const override; - virtual Ref<Texture2D> generate(const Ref<Resource> &p_from, const Size2 &p_size) const override; + virtual Ref<Texture2D> generate(const Ref<Resource> &p_from, const Size2 &p_size, Dictionary &p_metadata) const override; EditorBitmapPreviewPlugin(); }; @@ -74,8 +74,8 @@ class EditorPackedScenePreviewPlugin : public EditorResourcePreviewGenerator { public: virtual bool handles(const String &p_type) const override; - virtual Ref<Texture2D> generate(const Ref<Resource> &p_from, const Size2 &p_size) const override; - virtual Ref<Texture2D> generate_from_path(const String &p_path, const Size2 &p_size) const override; + virtual Ref<Texture2D> generate(const Ref<Resource> &p_from, const Size2 &p_size, Dictionary &p_metadata) const override; + virtual Ref<Texture2D> generate_from_path(const String &p_path, const Size2 &p_size, Dictionary &p_metadata) const override; EditorPackedScenePreviewPlugin(); }; @@ -102,7 +102,7 @@ class EditorMaterialPreviewPlugin : public EditorResourcePreviewGenerator { public: virtual bool handles(const String &p_type) const override; virtual bool generate_small_preview_automatically() const override; - virtual Ref<Texture2D> generate(const Ref<Resource> &p_from, const Size2 &p_size) const override; + virtual Ref<Texture2D> generate(const Ref<Resource> &p_from, const Size2 &p_size, Dictionary &p_metadata) const override; EditorMaterialPreviewPlugin(); ~EditorMaterialPreviewPlugin(); @@ -113,7 +113,7 @@ class EditorScriptPreviewPlugin : public EditorResourcePreviewGenerator { public: virtual bool handles(const String &p_type) const override; - virtual Ref<Texture2D> generate(const Ref<Resource> &p_from, const Size2 &p_size) const override; + virtual Ref<Texture2D> generate(const Ref<Resource> &p_from, const Size2 &p_size, Dictionary &p_metadata) const override; EditorScriptPreviewPlugin(); }; @@ -123,7 +123,7 @@ class EditorAudioStreamPreviewPlugin : public EditorResourcePreviewGenerator { public: virtual bool handles(const String &p_type) const override; - virtual Ref<Texture2D> generate(const Ref<Resource> &p_from, const Size2 &p_size) const override; + virtual Ref<Texture2D> generate(const Ref<Resource> &p_from, const Size2 &p_size, Dictionary &p_metadata) const override; EditorAudioStreamPreviewPlugin(); }; @@ -148,7 +148,7 @@ class EditorMeshPreviewPlugin : public EditorResourcePreviewGenerator { public: virtual bool handles(const String &p_type) const override; - virtual Ref<Texture2D> generate(const Ref<Resource> &p_from, const Size2 &p_size) const override; + virtual Ref<Texture2D> generate(const Ref<Resource> &p_from, const Size2 &p_size, Dictionary &p_metadata) const override; EditorMeshPreviewPlugin(); ~EditorMeshPreviewPlugin(); @@ -168,8 +168,8 @@ class EditorFontPreviewPlugin : public EditorResourcePreviewGenerator { public: virtual bool handles(const String &p_type) const override; - virtual Ref<Texture2D> generate(const Ref<Resource> &p_from, const Size2 &p_size) const override; - virtual Ref<Texture2D> generate_from_path(const String &p_path, const Size2 &p_size) const override; + virtual Ref<Texture2D> generate(const Ref<Resource> &p_from, const Size2 &p_size, Dictionary &p_metadata) const override; + virtual Ref<Texture2D> generate_from_path(const String &p_path, const Size2 &p_size, Dictionary &p_metadata) const override; EditorFontPreviewPlugin(); ~EditorFontPreviewPlugin(); @@ -185,7 +185,7 @@ class EditorTileMapPatternPreviewPlugin : public EditorResourcePreviewGenerator public: virtual bool handles(const String &p_type) const override; - virtual Ref<Texture2D> generate(const Ref<Resource> &p_from, const Size2 &p_size) const override; + virtual Ref<Texture2D> generate(const Ref<Resource> &p_from, const Size2 &p_size, Dictionary &p_metadata) const override; EditorTileMapPatternPreviewPlugin(); ~EditorTileMapPatternPreviewPlugin(); @@ -197,7 +197,7 @@ class EditorGradientPreviewPlugin : public EditorResourcePreviewGenerator { public: virtual bool handles(const String &p_type) const override; virtual bool generate_small_preview_automatically() const override; - virtual Ref<Texture2D> generate(const Ref<Resource> &p_from, const Size2 &p_size) const override; + virtual Ref<Texture2D> generate(const Ref<Resource> &p_from, const Size2 &p_size, Dictionary &p_metadata) const override; EditorGradientPreviewPlugin(); }; diff --git a/editor/plugins/navigation_obstacle_2d_editor_plugin.cpp b/editor/plugins/navigation_obstacle_2d_editor_plugin.cpp new file mode 100644 index 0000000000..0cbc711982 --- /dev/null +++ b/editor/plugins/navigation_obstacle_2d_editor_plugin.cpp @@ -0,0 +1,66 @@ +/**************************************************************************/ +/* navigation_obstacle_2d_editor_plugin.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 "navigation_obstacle_2d_editor_plugin.h" + +#include "editor/editor_node.h" +#include "editor/editor_undo_redo_manager.h" + +Node2D *NavigationObstacle2DEditor::_get_node() const { + return node; +} + +void NavigationObstacle2DEditor::_set_node(Node *p_polygon) { + node = Object::cast_to<NavigationObstacle2D>(p_polygon); +} + +void NavigationObstacle2DEditor::_action_add_polygon(const Variant &p_polygon) { + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + undo_redo->add_do_method(node, "set_vertices", p_polygon); + undo_redo->add_undo_method(node, "set_vertices", node->get_vertices()); +} + +void NavigationObstacle2DEditor::_action_remove_polygon(int p_idx) { + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + undo_redo->add_do_method(node, "set_vertices", Variant(Vector<Vector2>())); + undo_redo->add_undo_method(node, "set_vertices", node->get_vertices()); +} + +void NavigationObstacle2DEditor::_action_set_polygon(int p_idx, const Variant &p_previous, const Variant &p_polygon) { + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + undo_redo->add_do_method(node, "set_vertices", p_polygon); + undo_redo->add_undo_method(node, "set_vertices", node->get_vertices()); +} + +NavigationObstacle2DEditor::NavigationObstacle2DEditor() {} + +NavigationObstacle2DEditorPlugin::NavigationObstacle2DEditorPlugin() : + AbstractPolygon2DEditorPlugin(memnew(NavigationObstacle2DEditor), "NavigationObstacle2D") { +} diff --git a/editor/plugins/navigation_obstacle_2d_editor_plugin.h b/editor/plugins/navigation_obstacle_2d_editor_plugin.h new file mode 100644 index 0000000000..5a2206b8df --- /dev/null +++ b/editor/plugins/navigation_obstacle_2d_editor_plugin.h @@ -0,0 +1,61 @@ +/**************************************************************************/ +/* navigation_obstacle_2d_editor_plugin.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 NAVIGATION_OBSTACLE_2D_EDITOR_PLUGIN_H +#define NAVIGATION_OBSTACLE_2D_EDITOR_PLUGIN_H + +#include "editor/plugins/abstract_polygon_2d_editor.h" +#include "scene/2d/navigation_obstacle_2d.h" + +class NavigationObstacle2DEditor : public AbstractPolygon2DEditor { + GDCLASS(NavigationObstacle2DEditor, AbstractPolygon2DEditor); + + NavigationObstacle2D *node = nullptr; + +protected: + virtual Node2D *_get_node() const override; + virtual void _set_node(Node *p_polygon) override; + + virtual void _action_add_polygon(const Variant &p_polygon) override; + virtual void _action_remove_polygon(int p_idx) override; + virtual void _action_set_polygon(int p_idx, const Variant &p_previous, const Variant &p_polygon) override; + +public: + NavigationObstacle2DEditor(); +}; + +class NavigationObstacle2DEditorPlugin : public AbstractPolygon2DEditorPlugin { + GDCLASS(NavigationObstacle2DEditorPlugin, AbstractPolygon2DEditorPlugin); + +public: + NavigationObstacle2DEditorPlugin(); +}; + +#endif // NAVIGATION_OBSTACLE_2D_EDITOR_PLUGIN_H diff --git a/editor/plugins/navigation_obstacle_3d_editor_plugin.cpp b/editor/plugins/navigation_obstacle_3d_editor_plugin.cpp new file mode 100644 index 0000000000..2e39d6f67c --- /dev/null +++ b/editor/plugins/navigation_obstacle_3d_editor_plugin.cpp @@ -0,0 +1,599 @@ +/**************************************************************************/ +/* navigation_obstacle_3d_editor_plugin.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 "navigation_obstacle_3d_editor_plugin.h" + +#include "canvas_item_editor_plugin.h" +#include "core/core_string_names.h" +#include "core/input/input.h" +#include "core/io/file_access.h" +#include "core/math/geometry_2d.h" +#include "core/os/keyboard.h" +#include "editor/editor_node.h" +#include "editor/editor_settings.h" +#include "editor/editor_undo_redo_manager.h" +#include "node_3d_editor_plugin.h" +#include "scene/3d/camera_3d.h" +#include "scene/gui/separator.h" + +void NavigationObstacle3DEditor::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_READY: { + button_create->set_icon(get_theme_icon(SNAME("Edit"), SNAME("EditorIcons"))); + button_edit->set_icon(get_theme_icon(SNAME("MovePoint"), SNAME("EditorIcons"))); + button_edit->set_pressed(true); + get_tree()->connect("node_removed", callable_mp(this, &NavigationObstacle3DEditor::_node_removed)); + + } break; + } +} + +void NavigationObstacle3DEditor::_node_removed(Node *p_node) { + if (p_node == obstacle_node) { + obstacle_node = nullptr; + if (point_lines_meshinstance->get_parent() == p_node) { + p_node->remove_child(point_lines_meshinstance); + } + hide(); + } +} + +void NavigationObstacle3DEditor::_menu_option(int p_option) { + switch (p_option) { + case MODE_CREATE: { + mode = MODE_CREATE; + button_create->set_pressed(true); + button_edit->set_pressed(false); + } break; + case MODE_EDIT: { + mode = MODE_EDIT; + button_create->set_pressed(false); + button_edit->set_pressed(true); + } break; + } +} + +void NavigationObstacle3DEditor::_wip_close() { + ERR_FAIL_COND_MSG(!obstacle_node, "Edited NavigationObstacle3D is not valid."); + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + undo_redo->create_action(TTR("Set NavigationObstacle3D Vertices")); + undo_redo->add_undo_method(obstacle_node, "set_vertices", obstacle_node->get_vertices()); + + PackedVector3Array polygon_3d_vertices; + Vector<int> triangulated_polygon_2d_indices = Geometry2D::triangulate_polygon(wip); + + if (!triangulated_polygon_2d_indices.is_empty()) { + polygon_3d_vertices.resize(wip.size()); + Vector3 *polygon_3d_vertices_ptr = polygon_3d_vertices.ptrw(); + for (int i = 0; i < wip.size(); i++) { + const Vector2 &vert = wip[i]; + polygon_3d_vertices_ptr[i] = Vector3(vert.x, 0.0, vert.y); + } + } + + undo_redo->add_do_method(obstacle_node, "set_vertices", polygon_3d_vertices); + undo_redo->add_do_method(this, "_polygon_draw"); + undo_redo->add_undo_method(this, "_polygon_draw"); + wip.clear(); + wip_active = false; + mode = MODE_EDIT; + button_edit->set_pressed(true); + button_create->set_pressed(false); + edited_point = -1; + undo_redo->commit_action(); +} + +EditorPlugin::AfterGUIInput NavigationObstacle3DEditor::forward_3d_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) { + if (!obstacle_node) { + return EditorPlugin::AFTER_GUI_INPUT_PASS; + } + + Transform3D gt = obstacle_node->get_global_transform(); + Transform3D gi = gt.affine_inverse(); + Plane p(Vector3(0.0, 1.0, 0.0), gt.origin); + + Ref<InputEventMouseButton> mb = p_event; + + if (mb.is_valid()) { + Vector2 gpoint = mb->get_position(); + Vector3 ray_from = p_camera->project_ray_origin(gpoint); + Vector3 ray_dir = p_camera->project_ray_normal(gpoint); + + Vector3 spoint; + + if (!p.intersects_ray(ray_from, ray_dir, &spoint)) { + return EditorPlugin::AFTER_GUI_INPUT_PASS; + } + + spoint = gi.xform(spoint); + + Vector2 cpoint(spoint.x, spoint.z); + + //DO NOT snap here, it's confusing in 3D for adding points. + //Let the snap happen when the point is being moved, instead. + //cpoint = CanvasItemEditor::get_singleton()->snap_point(cpoint); + + PackedVector2Array poly = _get_polygon(); + + //first check if a point is to be added (segment split) + real_t grab_threshold = EDITOR_GET("editors/polygon_editor/point_grab_radius"); + + switch (mode) { + case MODE_CREATE: { + if (mb->get_button_index() == MouseButton::LEFT && mb->is_pressed()) { + if (!wip_active) { + wip.clear(); + wip.push_back(cpoint); + wip_active = true; + edited_point_pos = cpoint; + snap_ignore = false; + _polygon_draw(); + edited_point = 1; + return EditorPlugin::AFTER_GUI_INPUT_STOP; + } else { + if (wip.size() > 1 && p_camera->unproject_position(gt.xform(Vector3(wip[0].x, 0.0, wip[0].y))).distance_to(gpoint) < grab_threshold) { + //wip closed + _wip_close(); + + return EditorPlugin::AFTER_GUI_INPUT_STOP; + } else { + wip.push_back(cpoint); + edited_point = wip.size(); + snap_ignore = false; + _polygon_draw(); + return EditorPlugin::AFTER_GUI_INPUT_STOP; + } + } + } else if (mb->get_button_index() == MouseButton::RIGHT && mb->is_pressed() && wip_active) { + _wip_close(); + } + + } break; + + case MODE_EDIT: { + if (mb->get_button_index() == MouseButton::LEFT) { + if (mb->is_pressed()) { + if (mb->is_ctrl_pressed()) { + if (poly.size() < 3) { + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + undo_redo->create_action(TTR("Edit Vertices")); + undo_redo->add_undo_method(obstacle_node, "set_vertices", obstacle_node->get_vertices()); + poly.push_back(cpoint); + undo_redo->add_do_method(this, "_polygon_draw"); + undo_redo->add_undo_method(this, "_polygon_draw"); + undo_redo->commit_action(); + return EditorPlugin::AFTER_GUI_INPUT_STOP; + } + + //search edges + int closest_idx = -1; + Vector2 closest_pos; + real_t closest_dist = 1e10; + for (int i = 0; i < poly.size(); i++) { + Vector2 points[2] = { + p_camera->unproject_position(gt.xform(Vector3(poly[i].x, 0.0, poly[i].y))), + p_camera->unproject_position(gt.xform(Vector3(poly[(i + 1) % poly.size()].x, 0.0, poly[(i + 1) % poly.size()].y))) + }; + + Vector2 cp = Geometry2D::get_closest_point_to_segment(gpoint, points); + if (cp.distance_squared_to(points[0]) < CMP_EPSILON2 || cp.distance_squared_to(points[1]) < CMP_EPSILON2) { + continue; //not valid to reuse point + } + + real_t d = cp.distance_to(gpoint); + if (d < closest_dist && d < grab_threshold) { + closest_dist = d; + closest_pos = cp; + closest_idx = i; + } + } + + if (closest_idx >= 0) { + pre_move_edit = poly; + poly.insert(closest_idx + 1, cpoint); + edited_point = closest_idx + 1; + edited_point_pos = cpoint; + _set_polygon(poly); + _polygon_draw(); + snap_ignore = true; + + return EditorPlugin::AFTER_GUI_INPUT_STOP; + } + } else { + //look for points to move + + int closest_idx = -1; + Vector2 closest_pos; + real_t closest_dist = 1e10; + for (int i = 0; i < poly.size(); i++) { + Vector2 cp = p_camera->unproject_position(gt.xform(Vector3(poly[i].x, 0.0, poly[i].y))); + + real_t d = cp.distance_to(gpoint); + if (d < closest_dist && d < grab_threshold) { + closest_dist = d; + closest_pos = cp; + closest_idx = i; + } + } + + if (closest_idx >= 0) { + pre_move_edit = poly; + edited_point = closest_idx; + edited_point_pos = poly[closest_idx]; + _polygon_draw(); + snap_ignore = false; + return EditorPlugin::AFTER_GUI_INPUT_STOP; + } + } + } else { + snap_ignore = false; + + if (edited_point != -1) { + //apply + + ERR_FAIL_INDEX_V(edited_point, poly.size(), EditorPlugin::AFTER_GUI_INPUT_PASS); + poly.write[edited_point] = edited_point_pos; + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + undo_redo->create_action(TTR("Edit Poly")); + //undo_redo->add_do_method(obj, "set_polygon", poly); + //undo_redo->add_undo_method(obj, "set_polygon", pre_move_edit); + undo_redo->add_do_method(this, "_polygon_draw"); + undo_redo->add_undo_method(this, "_polygon_draw"); + undo_redo->commit_action(); + + edited_point = -1; + return EditorPlugin::AFTER_GUI_INPUT_STOP; + } + } + } + if (mb->get_button_index() == MouseButton::RIGHT && mb->is_pressed() && edited_point == -1) { + int closest_idx = -1; + Vector2 closest_pos; + real_t closest_dist = 1e10; + for (int i = 0; i < poly.size(); i++) { + Vector2 cp = p_camera->unproject_position(gt.xform(Vector3(poly[i].x, 0.0, poly[i].y))); + + real_t d = cp.distance_to(gpoint); + if (d < closest_dist && d < grab_threshold) { + closest_dist = d; + closest_pos = cp; + closest_idx = i; + } + } + + if (closest_idx >= 0) { + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + undo_redo->create_action(TTR("Edit Poly (Remove Point)")); + //undo_redo->add_undo_method(obj, "set_polygon", poly); + poly.remove_at(closest_idx); + //undo_redo->add_do_method(obj, "set_polygon", poly); + undo_redo->add_do_method(this, "_polygon_draw"); + undo_redo->add_undo_method(this, "_polygon_draw"); + undo_redo->commit_action(); + return EditorPlugin::AFTER_GUI_INPUT_STOP; + } + } + + } break; + } + } + + Ref<InputEventMouseMotion> mm = p_event; + + if (mm.is_valid()) { + if (edited_point != -1 && (wip_active || mm->get_button_mask().has_flag(MouseButtonMask::LEFT))) { + Vector2 gpoint = mm->get_position(); + + Vector3 ray_from = p_camera->project_ray_origin(gpoint); + Vector3 ray_dir = p_camera->project_ray_normal(gpoint); + + Vector3 spoint; + + if (!p.intersects_ray(ray_from, ray_dir, &spoint)) { + return EditorPlugin::AFTER_GUI_INPUT_PASS; + } + + spoint = gi.xform(spoint); + + Vector2 cpoint(spoint.x, spoint.z); + + if (snap_ignore && !Input::get_singleton()->is_key_pressed(Key::CTRL)) { + snap_ignore = false; + } + + if (!snap_ignore && Node3DEditor::get_singleton()->is_snap_enabled()) { + cpoint = cpoint.snapped(Vector2( + Node3DEditor::get_singleton()->get_translate_snap(), + Node3DEditor::get_singleton()->get_translate_snap())); + } + edited_point_pos = cpoint; + + _polygon_draw(); + } + } + + return EditorPlugin::AFTER_GUI_INPUT_PASS; +} + +PackedVector2Array NavigationObstacle3DEditor::_get_polygon() { + ERR_FAIL_COND_V_MSG(!obstacle_node, PackedVector2Array(), "Edited object is not valid."); + return PackedVector2Array(obstacle_node->call("get_polygon")); +} + +void NavigationObstacle3DEditor::_set_polygon(PackedVector2Array p_poly) { + ERR_FAIL_COND_MSG(!obstacle_node, "Edited object is not valid."); + obstacle_node->call("set_polygon", p_poly); +} + +void NavigationObstacle3DEditor::_polygon_draw() { + if (!obstacle_node) { + return; + } + + PackedVector2Array poly; + PackedVector3Array polygon_3d_vertices; + + if (wip_active) { + poly = wip; + } else { + poly = _get_polygon(); + } + polygon_3d_vertices.resize(poly.size()); + Vector3 *polygon_3d_vertices_ptr = polygon_3d_vertices.ptrw(); + + for (int i = 0; i < poly.size(); i++) { + const Vector2 &vert = poly[i]; + polygon_3d_vertices_ptr[i] = Vector3(vert.x, 0.0, vert.y); + } + + point_handle_mesh->clear_surfaces(); + point_lines_mesh->clear_surfaces(); + point_lines_meshinstance->set_material_override(line_material); + point_lines_mesh->surface_begin(Mesh::PRIMITIVE_LINES); + + Rect2 rect; + + for (int i = 0; i < poly.size(); i++) { + Vector2 p, p2; + if (i == edited_point) { + p = edited_point_pos; + } else { + p = poly[i]; + } + + if ((wip_active && i == poly.size() - 1) || (((i + 1) % poly.size()) == edited_point)) { + p2 = edited_point_pos; + } else { + p2 = poly[(i + 1) % poly.size()]; + } + + if (i == 0) { + rect.position = p; + } else { + rect.expand_to(p); + } + + Vector3 point = Vector3(p.x, 0.0, p.y); + Vector3 next_point = Vector3(p2.x, 0.0, p2.y); + + point_lines_mesh->surface_set_color(Color(1, 0.3, 0.1, 0.8)); + point_lines_mesh->surface_add_vertex(point); + point_lines_mesh->surface_set_color(Color(1, 0.3, 0.1, 0.8)); + point_lines_mesh->surface_add_vertex(next_point); + + //Color col=Color(1,0.3,0.1,0.8); + //vpc->draw_line(point,next_point,col,2); + //vpc->draw_texture(handle,point-handle->get_size()*0.5); + } + + rect = rect.grow(1); + + AABB r; + r.position.x = rect.position.x; + r.position.y = 0.0; + r.position.z = rect.position.y; + r.size.x = rect.size.x; + r.size.y = 0; + r.size.z = rect.size.y; + + point_lines_mesh->surface_set_color(Color(0.8, 0.8, 0.8, 0.2)); + point_lines_mesh->surface_add_vertex(r.position); + point_lines_mesh->surface_set_color(Color(0.8, 0.8, 0.8, 0.2)); + point_lines_mesh->surface_add_vertex(r.position + Vector3(0.3, 0, 0)); + point_lines_mesh->surface_set_color(Color(0.8, 0.8, 0.8, 0.2)); + point_lines_mesh->surface_add_vertex(r.position); + point_lines_mesh->surface_set_color(Color(0.8, 0.8, 0.8, 0.2)); + point_lines_mesh->surface_add_vertex(r.position + Vector3(0.0, 0.3, 0)); + + point_lines_mesh->surface_set_color(Color(0.8, 0.8, 0.8, 0.2)); + point_lines_mesh->surface_add_vertex(r.position + Vector3(r.size.x, 0, 0)); + point_lines_mesh->surface_set_color(Color(0.8, 0.8, 0.8, 0.2)); + point_lines_mesh->surface_add_vertex(r.position + Vector3(r.size.x, 0, 0) - Vector3(0.3, 0, 0)); + point_lines_mesh->surface_set_color(Color(0.8, 0.8, 0.8, 0.2)); + point_lines_mesh->surface_add_vertex(r.position + Vector3(r.size.x, 0, 0)); + point_lines_mesh->surface_set_color(Color(0.8, 0.8, 0.8, 0.2)); + point_lines_mesh->surface_add_vertex(r.position + Vector3(r.size.x, 0, 0) + Vector3(0, 0.3, 0)); + + point_lines_mesh->surface_set_color(Color(0.8, 0.8, 0.8, 0.2)); + point_lines_mesh->surface_add_vertex(r.position + Vector3(0, r.size.y, 0)); + point_lines_mesh->surface_set_color(Color(0.8, 0.8, 0.8, 0.2)); + point_lines_mesh->surface_add_vertex(r.position + Vector3(0, r.size.y, 0) - Vector3(0, 0.3, 0)); + point_lines_mesh->surface_set_color(Color(0.8, 0.8, 0.8, 0.2)); + point_lines_mesh->surface_add_vertex(r.position + Vector3(0, r.size.y, 0)); + point_lines_mesh->surface_set_color(Color(0.8, 0.8, 0.8, 0.2)); + point_lines_mesh->surface_add_vertex(r.position + Vector3(0, r.size.y, 0) + Vector3(0.3, 0, 0)); + + point_lines_mesh->surface_set_color(Color(0.8, 0.8, 0.8, 0.2)); + point_lines_mesh->surface_add_vertex(r.position + r.size); + point_lines_mesh->surface_set_color(Color(0.8, 0.8, 0.8, 0.2)); + point_lines_mesh->surface_add_vertex(r.position + r.size - Vector3(0.3, 0, 0)); + point_lines_mesh->surface_set_color(Color(0.8, 0.8, 0.8, 0.2)); + point_lines_mesh->surface_add_vertex(r.position + r.size); + point_lines_mesh->surface_set_color(Color(0.8, 0.8, 0.8, 0.2)); + point_lines_mesh->surface_add_vertex(r.position + r.size - Vector3(0.0, 0.3, 0)); + + point_lines_mesh->surface_end(); + + if (poly.size() == 0) { + return; + } + + Array point_handle_mesh_array; + point_handle_mesh_array.resize(Mesh::ARRAY_MAX); + Vector<Vector3> point_handle_mesh_vertices; + + point_handle_mesh_vertices.resize(poly.size()); + Vector3 *point_handle_mesh_vertices_ptr = point_handle_mesh_vertices.ptrw(); + + for (int i = 0; i < poly.size(); i++) { + Vector2 point_2d; + Vector2 p2; + + if (i == edited_point) { + point_2d = edited_point_pos; + } else { + point_2d = poly[i]; + } + + Vector3 point_handle_3d = Vector3(point_2d.x, 0.0, point_2d.y); + point_handle_mesh_vertices_ptr[i] = point_handle_3d; + } + + point_handle_mesh_array[Mesh::ARRAY_VERTEX] = point_handle_mesh_vertices; + point_handle_mesh->add_surface_from_arrays(Mesh::PRIMITIVE_POINTS, point_handle_mesh_array); + point_handle_mesh->surface_set_material(0, handle_material); +} + +void NavigationObstacle3DEditor::edit(Node *p_node) { + obstacle_node = Object::cast_to<NavigationObstacle3D>(p_node); + + if (obstacle_node) { + //Enable the pencil tool if the polygon is empty + if (_get_polygon().is_empty()) { + _menu_option(MODE_CREATE); + } + wip.clear(); + wip_active = false; + edited_point = -1; + p_node->add_child(point_lines_meshinstance); + _polygon_draw(); + + } else { + obstacle_node = nullptr; + + if (point_lines_meshinstance->get_parent()) { + point_lines_meshinstance->get_parent()->remove_child(point_lines_meshinstance); + } + } +} + +void NavigationObstacle3DEditor::_bind_methods() { + ClassDB::bind_method(D_METHOD("_polygon_draw"), &NavigationObstacle3DEditor::_polygon_draw); +} + +NavigationObstacle3DEditor::NavigationObstacle3DEditor() { + obstacle_node = nullptr; + + add_child(memnew(VSeparator)); + button_create = memnew(Button); + button_create->set_flat(true); + add_child(button_create); + button_create->connect("pressed", callable_mp(this, &NavigationObstacle3DEditor::_menu_option).bind(MODE_CREATE)); + button_create->set_toggle_mode(true); + + button_edit = memnew(Button); + button_edit->set_flat(true); + add_child(button_edit); + button_edit->connect("pressed", callable_mp(this, &NavigationObstacle3DEditor::_menu_option).bind(MODE_EDIT)); + button_edit->set_toggle_mode(true); + + mode = MODE_EDIT; + wip_active = false; + point_lines_meshinstance = memnew(MeshInstance3D); + point_lines_mesh.instantiate(); + point_lines_meshinstance->set_mesh(point_lines_mesh); + point_lines_meshinstance->set_transform(Transform3D(Basis(), Vector3(0, 0, 0.00001))); + + line_material = Ref<StandardMaterial3D>(memnew(StandardMaterial3D)); + line_material->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); + line_material->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA); + line_material->set_flag(StandardMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true); + line_material->set_flag(StandardMaterial3D::FLAG_SRGB_VERTEX_COLOR, true); + line_material->set_albedo(Color(1, 1, 1)); + + handle_material = Ref<StandardMaterial3D>(memnew(StandardMaterial3D)); + handle_material->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); + handle_material->set_flag(StandardMaterial3D::FLAG_USE_POINT_SIZE, true); + handle_material->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA); + handle_material->set_flag(StandardMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true); + handle_material->set_flag(StandardMaterial3D::FLAG_SRGB_VERTEX_COLOR, true); + Ref<Texture2D> handle = EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Editor3DHandle"), SNAME("EditorIcons")); + handle_material->set_point_size(handle->get_width()); + handle_material->set_texture(StandardMaterial3D::TEXTURE_ALBEDO, handle); + + point_handles_meshinstance = memnew(MeshInstance3D); + point_lines_meshinstance->add_child(point_handles_meshinstance); + point_handle_mesh.instantiate(); + point_handles_meshinstance->set_mesh(point_handle_mesh); + point_handles_meshinstance->set_transform(Transform3D(Basis(), Vector3(0, 0, 0.00001))); + + snap_ignore = false; +} + +NavigationObstacle3DEditor::~NavigationObstacle3DEditor() { + memdelete(point_lines_meshinstance); +} + +void NavigationObstacle3DEditorPlugin::edit(Object *p_object) { + obstacle_editor->edit(Object::cast_to<Node>(p_object)); +} + +bool NavigationObstacle3DEditorPlugin::handles(Object *p_object) const { + return Object::cast_to<NavigationObstacle3D>(p_object); +} + +void NavigationObstacle3DEditorPlugin::make_visible(bool p_visible) { + if (p_visible) { + obstacle_editor->show(); + } else { + obstacle_editor->hide(); + obstacle_editor->edit(nullptr); + } +} + +NavigationObstacle3DEditorPlugin::NavigationObstacle3DEditorPlugin() { + obstacle_editor = memnew(NavigationObstacle3DEditor); + Node3DEditor::get_singleton()->add_control_to_menu_panel(obstacle_editor); + + obstacle_editor->hide(); +} + +NavigationObstacle3DEditorPlugin::~NavigationObstacle3DEditorPlugin() { +} diff --git a/editor/plugins/navigation_obstacle_3d_editor_plugin.h b/editor/plugins/navigation_obstacle_3d_editor_plugin.h new file mode 100644 index 0000000000..1b125873d1 --- /dev/null +++ b/editor/plugins/navigation_obstacle_3d_editor_plugin.h @@ -0,0 +1,117 @@ +/**************************************************************************/ +/* navigation_obstacle_3d_editor_plugin.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 NAVIGATION_OBSTACLE_3D_EDITOR_PLUGIN_H +#define NAVIGATION_OBSTACLE_3D_EDITOR_PLUGIN_H + +#include "editor/editor_plugin.h" +#include "scene/3d/collision_polygon_3d.h" +#include "scene/3d/mesh_instance_3d.h" +#include "scene/gui/box_container.h" +#include "scene/resources/immediate_mesh.h" + +#include "scene/3d/navigation_obstacle_3d.h" + +class CanvasItemEditor; +class MenuButton; + +class NavigationObstacle3DEditor : public HBoxContainer { + GDCLASS(NavigationObstacle3DEditor, HBoxContainer); + + enum Mode { + MODE_CREATE, + MODE_EDIT, + + }; + + Mode mode; + + Button *button_create = nullptr; + Button *button_edit = nullptr; + + Ref<StandardMaterial3D> line_material; + Ref<StandardMaterial3D> handle_material; + + Panel *panel = nullptr; + NavigationObstacle3D *obstacle_node = nullptr; + Ref<ImmediateMesh> point_lines_mesh; + MeshInstance3D *point_lines_meshinstance = nullptr; + MeshInstance3D *point_handles_meshinstance = nullptr; + Ref<ArrayMesh> point_handle_mesh; + + MenuButton *options = nullptr; + + int edited_point = 0; + Vector2 edited_point_pos; + PackedVector2Array pre_move_edit; + PackedVector2Array wip; + bool wip_active; + bool snap_ignore; + + float prev_depth = 0.0f; + + void _wip_close(); + void _polygon_draw(); + void _menu_option(int p_option); + + PackedVector2Array _get_polygon(); + void _set_polygon(PackedVector2Array p_poly); + +protected: + void _notification(int p_what); + void _node_removed(Node *p_node); + static void _bind_methods(); + +public: + virtual EditorPlugin::AfterGUIInput forward_3d_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event); + void edit(Node *p_node); + NavigationObstacle3DEditor(); + ~NavigationObstacle3DEditor(); +}; + +class NavigationObstacle3DEditorPlugin : public EditorPlugin { + GDCLASS(NavigationObstacle3DEditorPlugin, EditorPlugin); + + NavigationObstacle3DEditor *obstacle_editor = nullptr; + +public: + virtual EditorPlugin::AfterGUIInput forward_3d_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) override { return obstacle_editor->forward_3d_gui_input(p_camera, p_event); } + + virtual String get_name() const override { return "NavigationObstacle3DEditor"; } + bool has_main_screen() const override { return false; } + virtual void edit(Object *p_object) override; + virtual bool handles(Object *p_object) const override; + virtual void make_visible(bool p_visible) override; + + NavigationObstacle3DEditorPlugin(); + ~NavigationObstacle3DEditorPlugin(); +}; + +#endif // NAVIGATION_OBSTACLE_3D_EDITOR_PLUGIN_H diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp index a726ab09d5..1e577b7737 100644 --- a/editor/plugins/node_3d_editor_plugin.cpp +++ b/editor/plugins/node_3d_editor_plugin.cpp @@ -3299,6 +3299,7 @@ void Node3DEditorViewport::_menu_option(int p_option) { orthogonal = false; auto_orthogonal = false; call_deferred(SNAME("update_transform_gizmo_view")); + _update_camera(0); _update_name(); } break; @@ -3308,8 +3309,8 @@ void Node3DEditorViewport::_menu_option(int p_option) { orthogonal = true; auto_orthogonal = false; call_deferred(SNAME("update_transform_gizmo_view")); + _update_camera(0); _update_name(); - } break; case VIEW_SWITCH_PERSPECTIVE_ORTHOGONAL: { _menu_option(orthogonal ? VIEW_PERSPECTIVE : VIEW_ORTHOGONAL); @@ -3402,7 +3403,7 @@ void Node3DEditorViewport::_menu_option(int p_option) { case VIEW_DISPLAY_NORMAL: case VIEW_DISPLAY_WIREFRAME: case VIEW_DISPLAY_OVERDRAW: - case VIEW_DISPLAY_SHADELESS: + case VIEW_DISPLAY_UNSHADED: case VIEW_DISPLAY_LIGHTING: case VIEW_DISPLAY_NORMAL_BUFFER: case VIEW_DISPLAY_DEBUG_SHADOW_ATLAS: @@ -3429,7 +3430,7 @@ void Node3DEditorViewport::_menu_option(int p_option) { VIEW_DISPLAY_NORMAL, VIEW_DISPLAY_WIREFRAME, VIEW_DISPLAY_OVERDRAW, - VIEW_DISPLAY_SHADELESS, + VIEW_DISPLAY_UNSHADED, VIEW_DISPLAY_LIGHTING, VIEW_DISPLAY_NORMAL_BUFFER, VIEW_DISPLAY_DEBUG_SHADOW_ATLAS, @@ -3778,15 +3779,9 @@ void Node3DEditorViewport::set_state(const Dictionary &p_state) { if (p_state.has("distance")) { cursor.distance = p_state["distance"]; } - - if (p_state.has("use_orthogonal")) { - bool orth = p_state["use_orthogonal"]; - - if (orth) { - _menu_option(VIEW_ORTHOGONAL); - } else { - _menu_option(VIEW_PERSPECTIVE); - } + if (p_state.has("orthogonal")) { + bool orth = p_state["orthogonal"]; + _menu_option(orth ? VIEW_ORTHOGONAL : VIEW_PERSPECTIVE); } if (p_state.has("view_type")) { view_type = ViewType(p_state["view_type"].operator int()); @@ -3804,8 +3799,13 @@ void Node3DEditorViewport::set_state(const Dictionary &p_state) { int display = p_state["display_mode"]; int idx = view_menu->get_popup()->get_item_index(display); - if (!view_menu->get_popup()->is_item_checked(idx)) { + if (idx != -1 && !view_menu->get_popup()->is_item_checked(idx)) { _menu_option(display); + } else { + idx = display_submenu->get_item_index(display); + if (idx != -1 && !display_submenu->is_item_checked(idx)) { + _menu_option(display); + } } } if (p_state.has("lock_rotation")) { @@ -3864,6 +3864,7 @@ void Node3DEditorViewport::set_state(const Dictionary &p_state) { int idx = view_menu->get_popup()->get_item_index(VIEW_HALF_RESOLUTION); view_menu->get_popup()->set_item_checked(idx, half_res); + _update_shrink(); } if (p_state.has("cinematic_preview")) { previewing_cinema = p_state["cinematic_preview"]; @@ -3896,19 +3897,27 @@ Dictionary Node3DEditorViewport::get_state() const { d["y_rotation"] = cursor.y_rot; d["distance"] = cursor.distance; d["use_environment"] = camera->get_environment().is_valid(); - d["use_orthogonal"] = camera->get_projection() == Camera3D::PROJECTION_ORTHOGONAL; + d["orthogonal"] = camera->get_projection() == Camera3D::PROJECTION_ORTHOGONAL; d["view_type"] = view_type; d["auto_orthogonal"] = auto_orthogonal; d["auto_orthogonal_enabled"] = view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(VIEW_AUTO_ORTHOGONAL)); - if (view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(VIEW_DISPLAY_NORMAL))) { - d["display_mode"] = VIEW_DISPLAY_NORMAL; - } else if (view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(VIEW_DISPLAY_WIREFRAME))) { - d["display_mode"] = VIEW_DISPLAY_WIREFRAME; - } else if (view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(VIEW_DISPLAY_OVERDRAW))) { - d["display_mode"] = VIEW_DISPLAY_OVERDRAW; - } else if (view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(VIEW_DISPLAY_SHADELESS))) { - d["display_mode"] = VIEW_DISPLAY_SHADELESS; + + // Find selected display mode. + int display_mode = VIEW_DISPLAY_NORMAL; + for (int i = VIEW_DISPLAY_NORMAL; i < VIEW_DISPLAY_ADVANCED; i++) { + if (view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(i))) { + display_mode = i; + break; + } } + for (int i = VIEW_DISPLAY_ADVANCED + 1; i < VIEW_DISPLAY_MAX; i++) { + if (display_submenu->is_item_checked(display_submenu->get_item_index(i))) { + display_mode = i; + break; + } + } + d["display_mode"] = display_mode; + d["listener"] = viewport->is_audio_listener_3d(); d["doppler"] = view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(VIEW_AUDIO_DOPPLER)); d["gizmos"] = view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(VIEW_GIZMOS)); @@ -5005,7 +5014,7 @@ Node3DEditorViewport::Node3DEditorViewport(Node3DEditor *p_spatial_editor, int p view_menu->get_popup()->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/view_display_wireframe", TTR("Display Wireframe")), VIEW_DISPLAY_WIREFRAME); view_menu->get_popup()->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/view_display_overdraw", TTR("Display Overdraw")), VIEW_DISPLAY_OVERDRAW); view_menu->get_popup()->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/view_display_lighting", TTR("Display Lighting")), VIEW_DISPLAY_LIGHTING); - view_menu->get_popup()->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/view_display_unshaded", TTR("Display Unshaded")), VIEW_DISPLAY_SHADELESS); + view_menu->get_popup()->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/view_display_unshaded", TTR("Display Unshaded")), VIEW_DISPLAY_UNSHADED); view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(VIEW_DISPLAY_NORMAL), true); display_submenu->set_hide_on_checkable_item_selection(false); display_submenu->add_radio_check_item(TTR("Directional Shadow Splits"), VIEW_DISPLAY_DEBUG_PSSM_SPLITS); @@ -5074,7 +5083,7 @@ Node3DEditorViewport::Node3DEditorViewport(Node3DEditor *p_spatial_editor, int p const int normal_idx = view_menu->get_popup()->get_item_index(VIEW_DISPLAY_NORMAL); const int wireframe_idx = view_menu->get_popup()->get_item_index(VIEW_DISPLAY_WIREFRAME); const int overdraw_idx = view_menu->get_popup()->get_item_index(VIEW_DISPLAY_OVERDRAW); - const int shadeless_idx = view_menu->get_popup()->get_item_index(VIEW_DISPLAY_SHADELESS); + const int shadeless_idx = view_menu->get_popup()->get_item_index(VIEW_DISPLAY_UNSHADED); const String unsupported_tooltip = TTR("Not available when using the OpenGL renderer."); view_menu->get_popup()->set_item_disabled(normal_idx, true); diff --git a/editor/plugins/node_3d_editor_plugin.h b/editor/plugins/node_3d_editor_plugin.h index c4e1070d84..24a70680b7 100644 --- a/editor/plugins/node_3d_editor_plugin.h +++ b/editor/plugins/node_3d_editor_plugin.h @@ -125,25 +125,28 @@ class Node3DEditorViewport : public Control { VIEW_GIZMOS, VIEW_INFORMATION, VIEW_FRAME_TIME, + + // < Keep in sync with menu. VIEW_DISPLAY_NORMAL, VIEW_DISPLAY_WIREFRAME, VIEW_DISPLAY_OVERDRAW, - VIEW_DISPLAY_SHADELESS, VIEW_DISPLAY_LIGHTING, + VIEW_DISPLAY_UNSHADED, VIEW_DISPLAY_ADVANCED, + // Advanced menu: + VIEW_DISPLAY_DEBUG_PSSM_SPLITS, VIEW_DISPLAY_NORMAL_BUFFER, VIEW_DISPLAY_DEBUG_SHADOW_ATLAS, VIEW_DISPLAY_DEBUG_DIRECTIONAL_SHADOW_ATLAS, + VIEW_DISPLAY_DEBUG_DECAL_ATLAS, VIEW_DISPLAY_DEBUG_VOXEL_GI_ALBEDO, VIEW_DISPLAY_DEBUG_VOXEL_GI_LIGHTING, VIEW_DISPLAY_DEBUG_VOXEL_GI_EMISSION, + VIEW_DISPLAY_DEBUG_SDFGI, + VIEW_DISPLAY_DEBUG_SDFGI_PROBES, VIEW_DISPLAY_DEBUG_SCENE_LUMINANCE, VIEW_DISPLAY_DEBUG_SSAO, VIEW_DISPLAY_DEBUG_SSIL, - VIEW_DISPLAY_DEBUG_PSSM_SPLITS, - VIEW_DISPLAY_DEBUG_DECAL_ATLAS, - VIEW_DISPLAY_DEBUG_SDFGI, - VIEW_DISPLAY_DEBUG_SDFGI_PROBES, VIEW_DISPLAY_DEBUG_GI_BUFFER, VIEW_DISPLAY_DEBUG_DISABLE_LOD, VIEW_DISPLAY_DEBUG_CLUSTER_OMNI_LIGHTS, @@ -152,6 +155,8 @@ class Node3DEditorViewport : public Control { VIEW_DISPLAY_DEBUG_CLUSTER_REFLECTION_PROBES, VIEW_DISPLAY_DEBUG_OCCLUDERS, VIEW_DISPLAY_MOTION_VECTORS, + VIEW_DISPLAY_MAX, + // > Keep in sync with menu. VIEW_LOCK_ROTATION, VIEW_CINEMATIC_PREVIEW, diff --git a/editor/plugins/physical_bone_3d_editor_plugin.cpp b/editor/plugins/physical_bone_3d_editor_plugin.cpp index b716cee3ff..a8d3ab948f 100644 --- a/editor/plugins/physical_bone_3d_editor_plugin.cpp +++ b/editor/plugins/physical_bone_3d_editor_plugin.cpp @@ -96,8 +96,9 @@ void PhysicalBone3DEditorPlugin::make_visible(bool p_visible) { } void PhysicalBone3DEditorPlugin::edit(Object *p_node) { - selected = static_cast<PhysicalBone3D *>(p_node); // Trust it - ERR_FAIL_COND(!selected); - - physical_bone_editor.set_selected(selected); + PhysicalBone3D *bone = Object::cast_to<PhysicalBone3D>(p_node); + if (bone) { + selected = bone; + physical_bone_editor.set_selected(selected); + } } diff --git a/editor/plugins/script_editor_plugin.cpp b/editor/plugins/script_editor_plugin.cpp index c605844728..51550acb94 100644 --- a/editor/plugins/script_editor_plugin.cpp +++ b/editor/plugins/script_editor_plugin.cpp @@ -40,7 +40,9 @@ #include "core/version.h" #include "editor/debugger/editor_debugger_node.h" #include "editor/debugger/script_editor_debugger.h" +#include "editor/editor_command_palette.h" #include "editor/editor_help_search.h" +#include "editor/editor_interface.h" #include "editor/editor_node.h" #include "editor/editor_paths.h" #include "editor/editor_scale.h" @@ -54,6 +56,8 @@ #include "editor/node_dock.h" #include "editor/plugins/shader_editor_plugin.h" #include "editor/plugins/text_shader_editor.h" +#include "editor/window_wrapper.h" +#include "scene/main/node.h" #include "scene/main/window.h" #include "scene/scene_string_names.h" #include "script_text_editor.h" @@ -1584,23 +1588,10 @@ void ScriptEditor::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: { EditorRunBar::get_singleton()->connect("stop_pressed", callable_mp(this, &ScriptEditor::_editor_stop)); - EditorNode::get_singleton()->connect("script_add_function_request", callable_mp(this, &ScriptEditor::_add_callback)); - EditorNode::get_singleton()->connect("resource_saved", callable_mp(this, &ScriptEditor::_res_saved_callback)); - EditorNode::get_singleton()->connect("scene_saved", callable_mp(this, &ScriptEditor::_scene_saved_callback)); - FileSystemDock::get_singleton()->connect("files_moved", callable_mp(this, &ScriptEditor::_files_moved)); - FileSystemDock::get_singleton()->connect("file_removed", callable_mp(this, &ScriptEditor::_file_removed)); - script_list->connect("item_selected", callable_mp(this, &ScriptEditor::_script_selected)); - - members_overview->connect("item_selected", callable_mp(this, &ScriptEditor::_members_overview_selected)); - help_overview->connect("item_selected", callable_mp(this, &ScriptEditor::_help_overview_selected)); - script_split->connect("dragged", callable_mp(this, &ScriptEditor::_split_dragged)); - list_split->connect("dragged", callable_mp(this, &ScriptEditor::_split_dragged)); - - EditorSettings::get_singleton()->connect("settings_changed", callable_mp(this, &ScriptEditor::_editor_settings_changed)); - EditorFileSystem::get_singleton()->connect("filesystem_changed", callable_mp(this, &ScriptEditor::_filesystem_changed)); _editor_settings_changed(); [[fallthrough]]; } + case NOTIFICATION_TRANSLATION_CHANGED: case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: case NOTIFICATION_THEME_CHANGED: { @@ -1635,6 +1626,20 @@ void ScriptEditor::_notification(int p_what) { InspectorDock::get_singleton()->connect("request_help", callable_mp(this, &ScriptEditor::_help_class_open)); EditorNode::get_singleton()->connect("request_help_search", callable_mp(this, &ScriptEditor::_help_search)); EditorNode::get_singleton()->connect("scene_closed", callable_mp(this, &ScriptEditor::_close_builtin_scripts_from_scene)); + EditorNode::get_singleton()->connect("script_add_function_request", callable_mp(this, &ScriptEditor::_add_callback)); + EditorNode::get_singleton()->connect("resource_saved", callable_mp(this, &ScriptEditor::_res_saved_callback)); + EditorNode::get_singleton()->connect("scene_saved", callable_mp(this, &ScriptEditor::_scene_saved_callback)); + FileSystemDock::get_singleton()->connect("files_moved", callable_mp(this, &ScriptEditor::_files_moved)); + FileSystemDock::get_singleton()->connect("file_removed", callable_mp(this, &ScriptEditor::_file_removed)); + script_list->connect("item_selected", callable_mp(this, &ScriptEditor::_script_selected)); + + members_overview->connect("item_selected", callable_mp(this, &ScriptEditor::_members_overview_selected)); + help_overview->connect("item_selected", callable_mp(this, &ScriptEditor::_help_overview_selected)); + script_split->connect("dragged", callable_mp(this, &ScriptEditor::_split_dragged)); + list_split->connect("dragged", callable_mp(this, &ScriptEditor::_split_dragged)); + + EditorSettings::get_singleton()->connect("settings_changed", callable_mp(this, &ScriptEditor::_editor_settings_changed)); + EditorFileSystem::get_singleton()->connect("filesystem_changed", callable_mp(this, &ScriptEditor::_filesystem_changed)); } break; case NOTIFICATION_EXIT_TREE: { @@ -2320,7 +2325,6 @@ bool ScriptEditor::edit(const Ref<Resource> &p_resource, int p_line, int p_col, if (tab_container->get_current_tab() != i) { _go_to_tab(i); - _update_script_names(); } if (is_visible_in_tree()) { se->ensure_focus(); @@ -2702,7 +2706,7 @@ void ScriptEditor::_save_layout() { return; } - EditorNode::get_singleton()->save_layout(); + EditorNode::get_singleton()->save_editor_layout_delayed(); } void ScriptEditor::_editor_settings_changed() { @@ -3246,12 +3250,24 @@ void ScriptEditor::set_window_layout(Ref<ConfigFile> p_layout) { restoring_layout = false; _update_script_names(); + + if (p_layout->has_section_key("ScriptEditor", "selected_script")) { + String selected_script = p_layout->get_value("ScriptEditor", "selected_script"); + // If the selected script is not in the list of open scripts, select nothing. + for (int i = 0; i < tab_container->get_tab_count(); i++) { + ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i)); + if (se && se->get_edited_resource()->get_path() == selected_script) { + _go_to_tab(i); + break; + } + } + } } void ScriptEditor::get_window_layout(Ref<ConfigFile> p_layout) { Array scripts; Array helps; - + String selected_script; for (int i = 0; i < tab_container->get_tab_count(); i++) { ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i)); if (se) { @@ -3260,6 +3276,10 @@ void ScriptEditor::get_window_layout(Ref<ConfigFile> p_layout) { continue; } + if (tab_container->get_current_tab_control() == tab_container->get_tab_control(i)) { + selected_script = path; + } + _save_editor_state(se); scripts.push_back(path); } @@ -3272,6 +3292,7 @@ void ScriptEditor::get_window_layout(Ref<ConfigFile> p_layout) { } p_layout->set_value("ScriptEditor", "open_scripts", scripts); + p_layout->set_value("ScriptEditor", "selected_script", selected_script); p_layout->set_value("ScriptEditor", "open_help", helps); p_layout->set_value("ScriptEditor", "script_split_offset", script_split->get_split_offset()); p_layout->set_value("ScriptEditor", "list_split_offset", list_split->get_split_offset()); @@ -3711,6 +3732,10 @@ void ScriptEditor::_on_find_in_files_modified_files(PackedStringArray paths) { _update_modified_scripts_for_external_editor(); } +void ScriptEditor::_window_changed(bool p_visible) { + make_floating->set_visible(!p_visible); +} + void ScriptEditor::_filter_scripts_text_changed(const String &p_newtext) { _update_script_names(); } @@ -3747,7 +3772,8 @@ void ScriptEditor::_bind_methods() { ADD_SIGNAL(MethodInfo("script_close", PropertyInfo(Variant::OBJECT, "script", PROPERTY_HINT_RESOURCE_TYPE, "Script"))); } -ScriptEditor::ScriptEditor() { +ScriptEditor::ScriptEditor(WindowWrapper *p_wrapper) { + window_wrapper = p_wrapper; current_theme = ""; script_editor_cache.instantiate(); @@ -3973,6 +3999,16 @@ ScriptEditor::ScriptEditor() { menu_hb->add_child(help_search); help_search->set_tooltip_text(TTR("Search the reference documentation.")); + if (p_wrapper->is_window_available()) { + make_floating = memnew(ScreenSelect); + make_floating->set_flat(true); + make_floating->set_tooltip_text(TTR("Make the script editor floating.")); + make_floating->connect("request_open_in_screen", callable_mp(window_wrapper, &WindowWrapper::enable_window_on_screen).bind(true)); + + menu_hb->add_child(make_floating); + p_wrapper->connect("window_visibility_changed", callable_mp(this, &ScriptEditor::_window_changed)); + } + menu_hb->add_child(memnew(VSeparator)); script_back = memnew(Button); @@ -4079,6 +4115,39 @@ ScriptEditor::~ScriptEditor() { memdelete(completion_cache); } +void ScriptEditorPlugin::_focus_another_editor() { + if (window_wrapper->get_window_enabled()) { + ERR_FAIL_COND(last_editor.is_empty()); + EditorInterface::get_singleton()->set_main_screen_editor(last_editor); + } +} + +void ScriptEditorPlugin::_save_last_editor(String p_editor) { + if (p_editor != get_name()) { + last_editor = p_editor; + } +} + +void ScriptEditorPlugin::_window_visibility_changed(bool p_visible) { + _focus_another_editor(); + if (p_visible) { + script_editor->add_theme_style_override("panel", script_editor->get_theme_stylebox("ScriptEditorPanelFloating", "EditorStyles")); + } else { + script_editor->add_theme_style_override("panel", script_editor->get_theme_stylebox("ScriptEditorPanel", "EditorStyles")); + } +} + +void ScriptEditorPlugin::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + connect("main_screen_changed", callable_mp(this, &ScriptEditorPlugin::_save_last_editor)); + } break; + case NOTIFICATION_EXIT_TREE: { + disconnect("main_screen_changed", callable_mp(this, &ScriptEditorPlugin::_save_last_editor)); + } break; + } +} + void ScriptEditorPlugin::edit(Object *p_object) { if (Object::cast_to<Script>(p_object)) { Script *p_script = Object::cast_to<Script>(p_object); @@ -4119,17 +4188,20 @@ bool ScriptEditorPlugin::handles(Object *p_object) const { void ScriptEditorPlugin::make_visible(bool p_visible) { if (p_visible) { - script_editor->show(); + window_wrapper->show(); script_editor->set_process(true); script_editor->ensure_select_current(); } else { - script_editor->hide(); - script_editor->set_process(false); + window_wrapper->hide(); + if (!window_wrapper->get_window_enabled()) { + script_editor->set_process(false); + } } } void ScriptEditorPlugin::selected_notify() { script_editor->ensure_select_current(); + _focus_another_editor(); } void ScriptEditorPlugin::save_external_data() { @@ -4142,10 +4214,37 @@ void ScriptEditorPlugin::apply_changes() { void ScriptEditorPlugin::set_window_layout(Ref<ConfigFile> p_layout) { script_editor->set_window_layout(p_layout); + + if (EDITOR_GET("interface/multi_window/restore_windows_on_load") && window_wrapper->is_window_available() && p_layout->has_section_key("ScriptEditor", "window_rect")) { + window_wrapper->restore_window_from_saved_position( + p_layout->get_value("ScriptEditor", "window_rect", Rect2i()), + p_layout->get_value("ScriptEditor", "window_screen", -1), + p_layout->get_value("ScriptEditor", "window_screen_rect", Rect2i())); + } else { + window_wrapper->set_window_enabled(false); + } } void ScriptEditorPlugin::get_window_layout(Ref<ConfigFile> p_layout) { script_editor->get_window_layout(p_layout); + + if (window_wrapper->get_window_enabled()) { + p_layout->set_value("ScriptEditor", "window_rect", window_wrapper->get_window_rect()); + int screen = window_wrapper->get_window_screen(); + p_layout->set_value("ScriptEditor", "window_screen", screen); + p_layout->set_value("ScriptEditor", "window_screen_rect", DisplayServer::get_singleton()->screen_get_usable_rect(screen)); + + } else { + if (p_layout->has_section_key("ScriptEditor", "window_rect")) { + p_layout->erase_section_key("ScriptEditor", "window_rect"); + } + if (p_layout->has_section_key("ScriptEditor", "window_screen")) { + p_layout->erase_section_key("ScriptEditor", "window_screen"); + } + if (p_layout->has_section_key("ScriptEditor", "window_screen_rect")) { + p_layout->erase_section_key("ScriptEditor", "window_screen_rect"); + } + } } void ScriptEditorPlugin::get_breakpoints(List<String> *p_breakpoints) { @@ -4157,11 +4256,18 @@ void ScriptEditorPlugin::edited_scene_changed() { } ScriptEditorPlugin::ScriptEditorPlugin() { - script_editor = memnew(ScriptEditor); - EditorNode::get_singleton()->get_main_screen_control()->add_child(script_editor); - script_editor->set_v_size_flags(Control::SIZE_EXPAND_FILL); - - script_editor->hide(); + window_wrapper = memnew(WindowWrapper); + window_wrapper->set_window_title(TTR("Script Editor - Godot Engine")); + window_wrapper->set_margins_enabled(true); + + script_editor = memnew(ScriptEditor(window_wrapper)); + Ref<Shortcut> make_floating_shortcut = ED_SHORTCUT_AND_COMMAND("script_editor/make_floating", TTR("Make Floating")); + window_wrapper->set_wrapped_control(script_editor, make_floating_shortcut); + + EditorNode::get_singleton()->get_main_screen_control()->add_child(window_wrapper); + window_wrapper->set_v_size_flags(Control::SIZE_EXPAND_FILL); + window_wrapper->hide(); + window_wrapper->connect("window_visibility_changed", callable_mp(this, &ScriptEditorPlugin::_window_visibility_changed)); EDITOR_GET("text_editor/behavior/files/auto_reload_scripts_on_external_change"); ScriptServer::set_reload_scripts_on_save(EDITOR_DEF("text_editor/behavior/files/auto_reload_and_parse_scripts_on_save", true)); diff --git a/editor/plugins/script_editor_plugin.h b/editor/plugins/script_editor_plugin.h index 5cbe56b68b..e879920e41 100644 --- a/editor/plugins/script_editor_plugin.h +++ b/editor/plugins/script_editor_plugin.h @@ -47,6 +47,7 @@ class TabContainer; class TextureRect; class Tree; class VSplitContainer; +class WindowWrapper; class EditorSyntaxHighlighter : public SyntaxHighlighter { GDCLASS(EditorSyntaxHighlighter, SyntaxHighlighter) @@ -236,7 +237,7 @@ class ScriptEditor : public PanelContainer { WINDOW_NEXT, WINDOW_PREV, WINDOW_SORT, - WINDOW_SELECT_BASE = 100 + WINDOW_SELECT_BASE = 100, }; enum { @@ -272,6 +273,7 @@ class ScriptEditor : public PanelContainer { Button *help_search = nullptr; Button *site_search = nullptr; + Button *make_floating = nullptr; EditorHelpSearch *help_search_dialog = nullptr; ItemList *script_list = nullptr; @@ -308,6 +310,8 @@ class ScriptEditor : public PanelContainer { FindInFilesPanel *find_in_files = nullptr; Button *find_in_files_button = nullptr; + WindowWrapper *window_wrapper = nullptr; + enum { SCRIPT_EDITOR_FUNC_MAX = 32, }; @@ -479,6 +483,8 @@ class ScriptEditor : public PanelContainer { void _start_find_in_files(bool with_replace); void _on_find_in_files_modified_files(PackedStringArray paths); + void _window_changed(bool p_visible); + static void _open_script_request(const String &p_path); void _close_builtin_scripts_from_scene(const String &p_scene); @@ -538,7 +544,7 @@ public: static void register_create_script_editor_function(CreateScriptEditorFunc p_func); - ScriptEditor(); + ScriptEditor(WindowWrapper *p_wrapper); ~ScriptEditor(); }; @@ -546,6 +552,17 @@ class ScriptEditorPlugin : public EditorPlugin { GDCLASS(ScriptEditorPlugin, EditorPlugin); ScriptEditor *script_editor = nullptr; + WindowWrapper *window_wrapper = nullptr; + + String last_editor; + + void _focus_another_editor(); + + void _save_last_editor(String p_editor); + void _window_visibility_changed(bool p_visible); + +protected: + void _notification(int p_what); public: virtual String get_name() const override { return "Script"; } diff --git a/editor/plugins/shader_editor_plugin.cpp b/editor/plugins/shader_editor_plugin.cpp index 5cb014e5c7..e7d2d7a11f 100644 --- a/editor/plugins/shader_editor_plugin.cpp +++ b/editor/plugins/shader_editor_plugin.cpp @@ -30,6 +30,7 @@ #include "shader_editor_plugin.h" +#include "editor/editor_command_palette.h" #include "editor/editor_node.h" #include "editor/editor_scale.h" #include "editor/editor_undo_redo_manager.h" @@ -38,6 +39,7 @@ #include "editor/plugins/text_shader_editor.h" #include "editor/plugins/visual_shader_editor_plugin.h" #include "editor/shader_create_dialog.h" +#include "editor/window_wrapper.h" #include "scene/gui/item_list.h" #include "scene/gui/texture_rect.h" @@ -171,7 +173,7 @@ bool ShaderEditorPlugin::handles(Object *p_object) const { void ShaderEditorPlugin::make_visible(bool p_visible) { if (p_visible) { - EditorNode::get_singleton()->make_bottom_panel_item_visible(main_split); + EditorNode::get_singleton()->make_bottom_panel_item_visible(window_wrapper); } } @@ -196,6 +198,88 @@ VisualShaderEditor *ShaderEditorPlugin::get_visual_shader_editor(const Ref<Shade return nullptr; } +void ShaderEditorPlugin::set_window_layout(Ref<ConfigFile> p_layout) { + if (EDITOR_GET("interface/multi_window/restore_windows_on_load") && window_wrapper->is_window_available() && p_layout->has_section_key("ShaderEditor", "window_rect")) { + window_wrapper->restore_window_from_saved_position( + p_layout->get_value("ShaderEditor", "window_rect", Rect2i()), + p_layout->get_value("ShaderEditor", "window_screen", -1), + p_layout->get_value("ShaderEditor", "window_screen_rect", Rect2i())); + } else { + window_wrapper->set_window_enabled(false); + } + + if (!bool(EDITOR_GET("editors/shader_editor/behavior/files/restore_shaders_on_load"))) { + return; + } + if (!p_layout->has_section("ShaderEditor")) { + return; + } + if (!p_layout->has_section_key("ShaderEditor", "open_shaders") || + !p_layout->has_section_key("ShaderEditor", "selected_shader")) { + return; + } + + Array shaders = p_layout->get_value("ShaderEditor", "open_shaders"); + int selected_shader_idx = 0; + String selected_shader = p_layout->get_value("ShaderEditor", "selected_shader"); + for (int i = 0; i < shaders.size(); i++) { + String path = shaders[i]; + Ref<Resource> res = ResourceLoader::load(path); + if (res.is_valid()) { + edit(res.ptr()); + } + if (selected_shader == path) { + selected_shader_idx = i; + } + } + + if (p_layout->has_section_key("ShaderEditor", "split_offset")) { + main_split->set_split_offset(p_layout->get_value("ShaderEditor", "split_offset")); + } + + _update_shader_list(); + _shader_selected(selected_shader_idx); +} + +void ShaderEditorPlugin::get_window_layout(Ref<ConfigFile> p_layout) { + if (window_wrapper->get_window_enabled()) { + p_layout->set_value("ShaderEditor", "window_rect", window_wrapper->get_window_rect()); + int screen = window_wrapper->get_window_screen(); + p_layout->set_value("ShaderEditor", "window_screen", screen); + p_layout->set_value("ShaderEditor", "window_screen_rect", DisplayServer::get_singleton()->screen_get_usable_rect(screen)); + + } else { + if (p_layout->has_section_key("ShaderEditor", "window_rect")) { + p_layout->erase_section_key("ShaderEditor", "window_rect"); + } + if (p_layout->has_section_key("ShaderEditor", "window_screen")) { + p_layout->erase_section_key("ShaderEditor", "window_screen"); + } + if (p_layout->has_section_key("ShaderEditor", "window_screen_rect")) { + p_layout->erase_section_key("ShaderEditor", "window_screen_rect"); + } + } + + Array shaders; + String selected_shader; + for (int i = 0; i < shader_tabs->get_tab_count(); i++) { + EditedShader edited_shader = edited_shaders[i]; + if (edited_shader.shader_editor || edited_shader.visual_shader_editor) { + shaders.push_back(edited_shader.shader->get_path()); + + TextShaderEditor *shader_editor = Object::cast_to<TextShaderEditor>(shader_tabs->get_current_tab_control()); + VisualShaderEditor *visual_shader_editor = Object::cast_to<VisualShaderEditor>(shader_tabs->get_current_tab_control()); + + if ((shader_editor && edited_shader.shader_editor == shader_editor) || (visual_shader_editor && edited_shader.visual_shader_editor == visual_shader_editor)) { + selected_shader = edited_shader.shader->get_path(); + } + } + } + p_layout->set_value("ShaderEditor", "open_shaders", shaders); + p_layout->set_value("ShaderEditor", "split_offset", main_split->get_split_offset()); + p_layout->set_value("ShaderEditor", "selected_shader", selected_shader); +} + void ShaderEditorPlugin::save_external_data() { for (EditedShader &edited_shader : edited_shaders) { if (edited_shader.shader_editor) { @@ -214,6 +298,10 @@ void ShaderEditorPlugin::apply_changes() { } void ShaderEditorPlugin::_shader_selected(int p_index) { + if (p_index >= (int)edited_shaders.size()) { + return; + } + if (edited_shaders[p_index].shader_editor) { edited_shaders[p_index].shader_editor->validate_script(); } @@ -450,6 +538,10 @@ void ShaderEditorPlugin::drop_data_fw(const Point2 &p_point, const Variant &p_da } } +void ShaderEditorPlugin::_window_changed(bool p_visible) { + make_floating->set_visible(!p_visible); +} + void ShaderEditorPlugin::_notification(int p_what) { switch (p_what) { case NOTIFICATION_READY: { @@ -459,12 +551,18 @@ void ShaderEditorPlugin::_notification(int p_what) { } ShaderEditorPlugin::ShaderEditorPlugin() { + window_wrapper = memnew(WindowWrapper); + window_wrapper->set_window_title(TTR("Shader Editor - Godot Engine")); + window_wrapper->set_margins_enabled(true); + main_split = memnew(HSplitContainer); + Ref<Shortcut> make_floating_shortcut = ED_SHORTCUT_AND_COMMAND("shader_editor/make_floating", TTR("Make Floating")); + window_wrapper->set_wrapped_control(main_split, make_floating_shortcut); VBoxContainer *vb = memnew(VBoxContainer); - HBoxContainer *file_hb = memnew(HBoxContainer); - vb->add_child(file_hb); + HBoxContainer *menu_hb = memnew(HBoxContainer); + vb->add_child(menu_hb); file_menu = memnew(MenuButton); file_menu->set_text(TTR("File")); file_menu->get_popup()->add_item(TTR("New Shader"), FILE_NEW); @@ -479,12 +577,26 @@ ShaderEditorPlugin::ShaderEditorPlugin() { file_menu->get_popup()->add_separator(); file_menu->get_popup()->add_item(TTR("Close File"), FILE_CLOSE); file_menu->get_popup()->connect("id_pressed", callable_mp(this, &ShaderEditorPlugin::_menu_item_pressed)); - file_hb->add_child(file_menu); + menu_hb->add_child(file_menu); for (int i = FILE_SAVE; i < FILE_MAX; i++) { file_menu->get_popup()->set_item_disabled(file_menu->get_popup()->get_item_index(i), true); } + if (window_wrapper->is_window_available()) { + Control *padding = memnew(Control); + padding->set_h_size_flags(Control::SIZE_EXPAND_FILL); + menu_hb->add_child(padding); + + make_floating = memnew(ScreenSelect); + make_floating->set_flat(true); + make_floating->set_tooltip_text(TTR("Make the shader editor floating.")); + make_floating->connect("request_open_in_screen", callable_mp(window_wrapper, &WindowWrapper::enable_window_on_screen).bind(true)); + + menu_hb->add_child(make_floating); + window_wrapper->connect("window_visibility_changed", callable_mp(this, &ShaderEditorPlugin::_window_changed)); + } + shader_list = memnew(ItemList); shader_list->set_v_size_flags(Control::SIZE_EXPAND_FILL); vb->add_child(shader_list); @@ -503,7 +615,7 @@ ShaderEditorPlugin::ShaderEditorPlugin() { empty.instantiate(); shader_tabs->add_theme_style_override("panel", empty); - button = EditorNode::get_singleton()->add_bottom_panel_item(TTR("Shader Editor"), main_split); + button = EditorNode::get_singleton()->add_bottom_panel_item(TTR("Shader Editor"), window_wrapper); // Defer connect because Editor class is not in the binding system yet. EditorNode::get_singleton()->call_deferred("connect", "resource_saved", callable_mp(this, &ShaderEditorPlugin::_resource_saved), CONNECT_DEFERRED); diff --git a/editor/plugins/shader_editor_plugin.h b/editor/plugins/shader_editor_plugin.h index 299d5975d2..45b48a2f91 100644 --- a/editor/plugins/shader_editor_plugin.h +++ b/editor/plugins/shader_editor_plugin.h @@ -40,6 +40,7 @@ class ShaderCreateDialog; class TabContainer; class TextShaderEditor; class VisualShaderEditor; +class WindowWrapper; class ShaderEditorPlugin : public EditorPlugin { GDCLASS(ShaderEditorPlugin, EditorPlugin); @@ -74,6 +75,9 @@ class ShaderEditorPlugin : public EditorPlugin { Button *button = nullptr; MenuButton *file_menu = nullptr; + WindowWrapper *window_wrapper = nullptr; + Button *make_floating = nullptr; + ShaderCreateDialog *shader_create_dialog = nullptr; void _update_shader_list(); @@ -93,6 +97,8 @@ class ShaderEditorPlugin : public EditorPlugin { bool can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const; void drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from); + void _window_changed(bool p_visible); + protected: void _notification(int p_what); @@ -106,6 +112,9 @@ public: TextShaderEditor *get_shader_editor(const Ref<Shader> &p_for_shader); VisualShaderEditor *get_visual_shader_editor(const Ref<Shader> &p_for_shader); + virtual void set_window_layout(Ref<ConfigFile> p_layout) override; + virtual void get_window_layout(Ref<ConfigFile> p_layout) override; + virtual void save_external_data() override; virtual void apply_changes() override; diff --git a/editor/plugins/skeleton_3d_editor_plugin.cpp b/editor/plugins/skeleton_3d_editor_plugin.cpp index 85906ef9d3..0cfeacc9b1 100644 --- a/editor/plugins/skeleton_3d_editor_plugin.cpp +++ b/editor/plugins/skeleton_3d_editor_plugin.cpp @@ -832,8 +832,8 @@ void Skeleton3DEditor::create_editors() { void Skeleton3DEditor::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: { + create_editors(); update_joint_tree(); - update_editors(); joint_tree->connect("item_selected", callable_mp(this, &Skeleton3DEditor::_joint_tree_selection_changed)); joint_tree->connect("item_mouse_selected", callable_mp(this, &Skeleton3DEditor::_joint_tree_rmb_select)); @@ -946,8 +946,6 @@ void fragment() { handles_mesh_instance->set_cast_shadows_setting(GeometryInstance3D::SHADOW_CASTING_SETTING_OFF); handles_mesh.instantiate(); handles_mesh_instance->set_mesh(handles_mesh); - - create_editors(); } void Skeleton3DEditor::update_bone_original() { diff --git a/editor/register_editor_types.cpp b/editor/register_editor_types.cpp index 0a63c40bf6..1096003c40 100644 --- a/editor/register_editor_types.cpp +++ b/editor/register_editor_types.cpp @@ -81,6 +81,8 @@ #include "editor/plugins/mesh_library_editor_plugin.h" #include "editor/plugins/multimesh_editor_plugin.h" #include "editor/plugins/navigation_link_2d_editor_plugin.h" +#include "editor/plugins/navigation_obstacle_2d_editor_plugin.h" +#include "editor/plugins/navigation_obstacle_3d_editor_plugin.h" #include "editor/plugins/navigation_polygon_editor_plugin.h" #include "editor/plugins/node_3d_editor_gizmos.h" #include "editor/plugins/occluder_instance_3d_editor_plugin.h" @@ -185,6 +187,7 @@ void register_editor_types() { EditorPlugins::add_by_type<MeshInstance3DEditorPlugin>(); EditorPlugins::add_by_type<MeshLibraryEditorPlugin>(); EditorPlugins::add_by_type<MultiMeshEditorPlugin>(); + EditorPlugins::add_by_type<NavigationObstacle3DEditorPlugin>(); EditorPlugins::add_by_type<OccluderInstance3DEditorPlugin>(); EditorPlugins::add_by_type<PackedSceneEditorPlugin>(); EditorPlugins::add_by_type<Path3DEditorPlugin>(); @@ -213,6 +216,7 @@ void register_editor_types() { EditorPlugins::add_by_type<LightOccluder2DEditorPlugin>(); EditorPlugins::add_by_type<Line2DEditorPlugin>(); EditorPlugins::add_by_type<NavigationLink2DEditorPlugin>(); + EditorPlugins::add_by_type<NavigationObstacle2DEditorPlugin>(); EditorPlugins::add_by_type<NavigationPolygonEditorPlugin>(); EditorPlugins::add_by_type<Path2DEditorPlugin>(); EditorPlugins::add_by_type<Polygon2DEditorPlugin>(); diff --git a/editor/window_wrapper.cpp b/editor/window_wrapper.cpp new file mode 100644 index 0000000000..21b0a1a21d --- /dev/null +++ b/editor/window_wrapper.cpp @@ -0,0 +1,474 @@ +/**************************************************************************/ +/* window_wrapper.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 "window_wrapper.h" + +#include "editor/editor_node.h" +#include "editor/editor_scale.h" +#include "editor/editor_settings.h" +#include "scene/gui/box_container.h" +#include "scene/gui/label.h" +#include "scene/gui/panel.h" +#include "scene/gui/popup.h" +#include "scene/main/window.h" + +// WindowWrapper + +// Capture all shortcut events not handled by other nodes. +class ShortcutBin : public Node { + GDCLASS(ShortcutBin, Node); + + virtual void _notification(int what) { + switch (what) { + case NOTIFICATION_READY: + set_process_shortcut_input(true); + break; + } + } + + virtual void shortcut_input(const Ref<InputEvent> &p_event) override { + if (!get_window()->is_visible()) { + return; + } + Window *grandparent_window = get_window()->get_parent_visible_window(); + ERR_FAIL_COND(!grandparent_window); + + if (Object::cast_to<InputEventKey>(p_event.ptr()) || Object::cast_to<InputEventShortcut>(p_event.ptr())) { + // HACK: Propagate the window input to the editor main window to handle global shortcuts. + grandparent_window->push_input(p_event); + + if (grandparent_window->is_input_handled()) { + get_viewport()->set_input_as_handled(); + } + } + } +}; + +Rect2 WindowWrapper::_get_default_window_rect() const { + // Assume that the control rect is the desidered one for the window. + return wrapped_control->get_screen_rect(); +} + +Node *WindowWrapper::_get_wrapped_control_parent() const { + if (margins) { + return margins; + } + return window; +} + +void WindowWrapper::_set_window_enabled_with_rect(bool p_visible, const Rect2 p_rect) { + ERR_FAIL_NULL(wrapped_control); + + if (!is_window_available()) { + return; + } + + if (window->is_visible() == p_visible) { + if (p_visible) { + window->grab_focus(); + } + return; + } + + Node *parent = _get_wrapped_control_parent(); + + if (wrapped_control->get_parent() != parent) { + // Move the control to the window. + wrapped_control->reparent(parent, false); + + _set_window_rect(p_rect); + wrapped_control->set_anchors_and_offsets_preset(PRESET_FULL_RECT); + + } else if (!p_visible) { + // Remove control from window. + wrapped_control->reparent(this, false); + } + + window->set_visible(p_visible); + if (!p_visible) { + emit_signal("window_close_requested"); + } + emit_signal("window_visibility_changed", p_visible); +} + +void WindowWrapper::_set_window_rect(const Rect2 p_rect) { + // Set the window rect even when the window is maximized to have a good default size + // when the user remove the maximized mode. + window->set_position(p_rect.position); + window->set_size(p_rect.size); + + if (EDITOR_GET("interface/multi_window/maximize_window")) { + window->set_mode(Window::MODE_MAXIMIZED); + } +} + +void WindowWrapper::_bind_methods() { + ADD_SIGNAL(MethodInfo("window_visibility_changed", PropertyInfo(Variant::BOOL, "visible"))); + ADD_SIGNAL(MethodInfo("window_close_requested")); +} + +void WindowWrapper::_notification(int p_what) { + if (!is_window_available()) { + return; + } + switch (p_what) { + case NOTIFICATION_VISIBILITY_CHANGED: { + if (get_window_enabled() && is_visible()) { + // Grab the focus when WindowWrapper.set_visible(true) is called + // and the window is showing. + window->grab_focus(); + } + } break; + case NOTIFICATION_READY: { + set_process_shortcut_input(true); + } break; + case NOTIFICATION_THEME_CHANGED: { + window_background->add_theme_style_override("panel", get_theme_stylebox("PanelForeground", "EditorStyles")); + } break; + } +} + +void WindowWrapper::shortcut_input(const Ref<InputEvent> &p_event) { + if (enable_shortcut.is_valid() && enable_shortcut->matches_event(p_event)) { + set_window_enabled(true); + } +} + +void WindowWrapper::set_wrapped_control(Control *p_control, const Ref<Shortcut> &p_enable_shortcut) { + ERR_FAIL_NULL(p_control); + ERR_FAIL_COND(wrapped_control); + + wrapped_control = p_control; + enable_shortcut = p_enable_shortcut; + add_child(p_control); +} + +Control *WindowWrapper::get_wrapped_control() const { + return wrapped_control; +} + +Control *WindowWrapper::release_wrapped_control() { + set_window_enabled(false); + if (wrapped_control) { + Control *old_wrapped = wrapped_control; + wrapped_control->get_parent()->remove_child(wrapped_control); + wrapped_control = nullptr; + + return old_wrapped; + } + return nullptr; +} + +bool WindowWrapper::is_window_available() const { + return window != nullptr; +} + +bool WindowWrapper::get_window_enabled() const { + return is_window_available() ? window->is_visible() : false; +} + +void WindowWrapper::set_window_enabled(bool p_enabled) { + _set_window_enabled_with_rect(p_enabled, _get_default_window_rect()); +} + +Rect2i WindowWrapper::get_window_rect() const { + ERR_FAIL_COND_V(!get_window_enabled(), Rect2i()); + return Rect2i(window->get_position(), window->get_size()); +} + +int WindowWrapper::get_window_screen() const { + ERR_FAIL_COND_V(!get_window_enabled(), -1); + return window->get_current_screen(); +} + +void WindowWrapper::restore_window(const Rect2i &p_rect, int p_screen) { + ERR_FAIL_COND(!is_window_available()); + ERR_FAIL_INDEX(p_screen, DisplayServer::get_singleton()->get_screen_count()); + + _set_window_enabled_with_rect(true, p_rect); + window->set_current_screen(p_screen); +} + +void WindowWrapper::restore_window_from_saved_position(const Rect2 p_window_rect, int p_screen, const Rect2 p_screen_rect) { + ERR_FAIL_COND(!is_window_available()); + + Rect2 window_rect = p_window_rect; + int screen = p_screen; + Rect2 restored_screen_rect = p_screen_rect; + + if (screen < 0 || screen >= DisplayServer::get_singleton()->get_screen_count()) { + // Fallback to the main window screen if the saved screen is not available. + screen = get_window()->get_window_id(); + } + + Rect2i real_screen_rect = DisplayServer::get_singleton()->screen_get_usable_rect(screen); + + if (restored_screen_rect == Rect2i()) { + // Fallback to the target screen rect. + restored_screen_rect = real_screen_rect; + } + + if (window_rect == Rect2i()) { + // Fallback to a standard rect. + window_rect = Rect2i(restored_screen_rect.position + restored_screen_rect.size / 4, restored_screen_rect.size / 2); + } + + // Adjust the window rect size in case the resolution changes. + Vector2 screen_ratio = Vector2(real_screen_rect.size) / Vector2(restored_screen_rect.size); + + // The screen positioning may change, so remove the original screen position. + window_rect.position -= restored_screen_rect.position; + window_rect = Rect2i(window_rect.position * screen_ratio, window_rect.size * screen_ratio); + window_rect.position += real_screen_rect.position; + + // All good, restore the window. + window->set_current_screen(p_screen); + if (window->is_visible()) { + _set_window_rect(window_rect); + } else { + _set_window_enabled_with_rect(true, window_rect); + } +} + +void WindowWrapper::enable_window_on_screen(int p_screen, bool p_auto_scale) { + int current_screen = Object::cast_to<Window>(get_viewport())->get_current_screen(); + int screen = p_screen < 0 ? current_screen : p_screen; + + bool auto_scale = p_auto_scale && !EDITOR_GET("interface/multi_window/maximize_window"); + + if (auto_scale && current_screen != screen) { + Rect2 control_rect = _get_default_window_rect(); + + Rect2i source_screen_rect = DisplayServer::get_singleton()->screen_get_usable_rect(current_screen); + Rect2i dest_screen_rect = DisplayServer::get_singleton()->screen_get_usable_rect(screen); + + // Adjust the window rect size in case the resolution changes. + Vector2 screen_ratio = Vector2(source_screen_rect.size) / Vector2(dest_screen_rect.size); + + // The screen positioning may change, so remove the original screen position. + control_rect.position -= source_screen_rect.position; + control_rect = Rect2i(control_rect.position * screen_ratio, control_rect.size * screen_ratio); + control_rect.position += dest_screen_rect.position; + + restore_window(control_rect, p_screen); + } else { + window->set_current_screen(p_screen); + set_window_enabled(true); + } +} + +void WindowWrapper::set_window_title(const String p_title) { + if (!is_window_available()) { + return; + } + window->set_title(p_title); +} + +void WindowWrapper::set_margins_enabled(bool p_enabled) { + if (!is_window_available()) { + return; + } + + if (!p_enabled && margins) { + margins->queue_free(); + margins = nullptr; + } else if (p_enabled && !margins) { + Size2 borders = Size2(4, 4) * EDSCALE; + margins = memnew(MarginContainer); + margins->add_theme_constant_override("margin_right", borders.width); + margins->add_theme_constant_override("margin_top", borders.height); + margins->add_theme_constant_override("margin_left", borders.width); + margins->add_theme_constant_override("margin_bottom", borders.height); + + window->add_child(margins); + margins->set_anchors_and_offsets_preset(PRESET_FULL_RECT); + } +} + +WindowWrapper::WindowWrapper() { + if (SceneTree::get_singleton()->get_root()->is_embedding_subwindows() || !EDITOR_GET("interface/multi_window/enable")) { + return; + } + + window = memnew(Window); + window->set_wrap_controls(true); + + add_child(window); + window->hide(); + + window->connect("close_requested", callable_mp(this, &WindowWrapper::set_window_enabled).bind(false)); + + ShortcutBin *capturer = memnew(ShortcutBin); + window->add_child(capturer); + + window_background = memnew(Panel); + window_background->set_anchors_and_offsets_preset(PRESET_FULL_RECT); + window->add_child(window_background); +} + +// ScreenSelect + +void ScreenSelect::_build_advanced_menu() { + // Clear old screen list. + while (screen_list->get_child_count(false) > 0) { + Node *child = screen_list->get_child(0); + screen_list->remove_child(child); + child->queue_free(); + } + + // Populate screen list. + const real_t height = real_t(get_theme_font_size("font_size")) * 1.5; + + int current_screen = get_window()->get_current_screen(); + for (int i = 0; i < DisplayServer::get_singleton()->get_screen_count(); i++) { + Button *button = memnew(Button); + + Size2 screen_size = Size2(DisplayServer::get_singleton()->screen_get_size(i)); + Size2 button_size = Size2(height * (screen_size.x / screen_size.y), height); + button->set_custom_minimum_size(button_size); + screen_list->add_child(button); + + button->set_text(itos(i)); + button->set_text_alignment(HORIZONTAL_ALIGNMENT_CENTER); + button->set_tooltip_text(vformat(TTR("Make this panel floating in the screen %d."), i)); + + if (i == current_screen) { + Color accent_color = get_theme_color("accent_color", "Editor"); + button->add_theme_color_override("font_color", accent_color); + } + + button->connect("pressed", callable_mp(this, &ScreenSelect::_emit_screen_signal).bind(i)); + button->connect("pressed", callable_mp(static_cast<BaseButton *>(this), &ScreenSelect::set_pressed).bind(false)); + button->connect("pressed", callable_mp(static_cast<Window *>(popup), &Popup::hide)); + } +} + +void ScreenSelect::_emit_screen_signal(int p_screen_idx) { + emit_signal("request_open_in_screen", p_screen_idx); +} + +void ScreenSelect::_bind_methods() { + ADD_SIGNAL(MethodInfo("request_open_in_screen", PropertyInfo(Variant::INT, "screen"))); +} + +void ScreenSelect::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_READY: { + connect("gui_input", callable_mp(this, &ScreenSelect::_handle_mouse_shortcut)); + } break; + case NOTIFICATION_THEME_CHANGED: { + set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon("MakeFloating", "EditorIcons")); + popup_background->add_theme_style_override("panel", get_theme_stylebox("PanelForeground", "EditorStyles")); + + const real_t popup_height = real_t(get_theme_font_size("font_size")) * 2.0; + popup->set_min_size(Size2(0, popup_height * 3)); + } break; + } +} + +void ScreenSelect::_handle_mouse_shortcut(const Ref<InputEvent> &p_event) { + const Ref<InputEventMouseButton> mouse_button = p_event; + if (mouse_button.is_valid()) { + if (mouse_button->is_pressed() && mouse_button->get_button_index() == MouseButton::LEFT) { + _emit_screen_signal(get_window()->get_current_screen()); + accept_event(); + } + } +} + +void ScreenSelect::_show_popup() { + // Adapted from /scene/gui/menu_button.cpp::show_popup + if (!get_viewport()) { + return; + } + + Size2 size = get_size() * get_viewport()->get_canvas_transform().get_scale(); + + popup->set_size(Size2(size.width, 0)); + Point2 gp = get_screen_position(); + gp.y += size.y; + if (is_layout_rtl()) { + gp.x += size.width - popup->get_size().width; + } + popup->set_position(gp); + popup->popup(); +} + +void ScreenSelect::pressed() { + if (popup->is_visible()) { + popup->hide(); + return; + } + + _build_advanced_menu(); + _show_popup(); +} + +ScreenSelect::ScreenSelect() { + set_text(TTR("Make Floating")); + set_tooltip_text(TTR("Make this panel floating.\nRight click to open the screen selector.")); + set_button_mask(MouseButtonMask::RIGHT); + set_flat(true); + set_toggle_mode(true); + set_focus_mode(FOCUS_NONE); + set_action_mode(ACTION_MODE_BUTTON_PRESS); + + // Create the popup. + const Size2 borders = Size2(4, 4) * EDSCALE; + + popup = memnew(Popup); + popup->connect("popup_hide", callable_mp(static_cast<BaseButton *>(this), &ScreenSelect::set_pressed).bind(false)); + add_child(popup); + + popup_background = memnew(Panel); + popup_background->set_anchors_and_offsets_preset(PRESET_FULL_RECT); + popup->add_child(popup_background); + + MarginContainer *popup_root = memnew(MarginContainer); + popup_root->add_theme_constant_override("margin_right", borders.width); + popup_root->add_theme_constant_override("margin_top", borders.height); + popup_root->add_theme_constant_override("margin_left", borders.width); + popup_root->add_theme_constant_override("margin_bottom", borders.height); + popup->add_child(popup_root); + + VBoxContainer *vb = memnew(VBoxContainer); + vb->set_alignment(BoxContainer::ALIGNMENT_CENTER); + popup_root->add_child(vb); + + Label *description = memnew(Label(TTR("Select Screen"))); + description->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER); + vb->add_child(description); + + screen_list = memnew(HBoxContainer); + screen_list->set_alignment(BoxContainer::ALIGNMENT_CENTER); + vb->add_child(screen_list); + + popup_root->set_anchors_and_offsets_preset(PRESET_FULL_RECT); +} diff --git a/editor/window_wrapper.h b/editor/window_wrapper.h new file mode 100644 index 0000000000..e8fcb13c92 --- /dev/null +++ b/editor/window_wrapper.h @@ -0,0 +1,110 @@ +/**************************************************************************/ +/* window_wrapper.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 WINDOW_WRAPPER_H +#define WINDOW_WRAPPER_H + +#include "core/math/rect2.h" +#include "scene/gui/margin_container.h" +#include "scene/gui/menu_button.h" + +class Window; +class HBoxContainer; + +class WindowWrapper : public MarginContainer { + GDCLASS(WindowWrapper, MarginContainer); + + Control *wrapped_control = nullptr; + MarginContainer *margins = nullptr; + Window *window = nullptr; + + Panel *window_background = nullptr; + + Ref<Shortcut> enable_shortcut; + + Rect2 _get_default_window_rect() const; + Node *_get_wrapped_control_parent() const; + + void _set_window_enabled_with_rect(bool p_visible, const Rect2 p_rect); + void _set_window_rect(const Rect2 p_rect); + +protected: + static void _bind_methods(); + void _notification(int p_what); + + virtual void shortcut_input(const Ref<InputEvent> &p_event) override; + +public: + void set_wrapped_control(Control *p_control, const Ref<Shortcut> &p_enable_shortcut = Ref<Shortcut>()); + Control *get_wrapped_control() const; + Control *release_wrapped_control(); + + bool is_window_available() const; + + bool get_window_enabled() const; + void set_window_enabled(bool p_enabled); + + Rect2i get_window_rect() const; + int get_window_screen() const; + + void restore_window(const Rect2i &p_rect, int p_screen = -1); + void restore_window_from_saved_position(const Rect2 p_window_rect, int p_screen, const Rect2 p_screen_rect); + void enable_window_on_screen(int p_screen = -1, bool p_auto_scale = false); + + void set_window_title(const String p_title); + void set_margins_enabled(bool p_enabled); + + WindowWrapper(); +}; + +class ScreenSelect : public Button { + GDCLASS(ScreenSelect, Button); + + Popup *popup = nullptr; + Panel *popup_background = nullptr; + HBoxContainer *screen_list = nullptr; + + void _build_advanced_menu(); + + void _emit_screen_signal(int p_screen_idx); + void _handle_mouse_shortcut(const Ref<InputEvent> &p_event); + void _show_popup(); + +protected: + virtual void pressed() override; + static void _bind_methods(); + + void _notification(int p_what); + +public: + ScreenSelect(); +}; + +#endif // WINDOW_WRAPPER_H diff --git a/main/main.cpp b/main/main.cpp index b657fc61bb..86de6497d0 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -182,6 +182,7 @@ static int converter_max_line_length = 100000; HashMap<Main::CLIScope, Vector<String>> forwardable_cli_arguments; #endif +static bool single_threaded_scene = false; bool use_startup_benchmark = false; String startup_benchmark_file; @@ -209,6 +210,7 @@ static bool use_debug_profiler = false; static bool debug_collisions = false; static bool debug_paths = false; static bool debug_navigation = false; +static bool debug_avoidance = false; #endif static int frame_delay = 0; static bool disable_render_loop = false; @@ -452,10 +454,12 @@ void Main::print_help(const char *p_binary) { 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(" --remote-debug <uri> Remote debug (<protocol>://<host/IP>[:<port>], e.g. tcp://127.0.0.1:6007).\n"); + OS::get_singleton()->print(" --single-threaded-scene Scene tree runs in single-threaded mode. Sub-thread groups are disabled and run on the main thread.\n"); #if defined(DEBUG_ENABLED) OS::get_singleton()->print(" --debug-collisions Show collision shapes when running the scene.\n"); OS::get_singleton()->print(" --debug-paths Show path lines when running the scene.\n"); OS::get_singleton()->print(" --debug-navigation Show navigation polygons when running the scene.\n"); + OS::get_singleton()->print(" --debug-avoidance Show navigation polygons when running the scene.\n"); OS::get_singleton()->print(" --debug-stringnames Print all StringName allocations to stdout when the engine quits.\n"); #endif OS::get_singleton()->print(" --frame-delay <ms> Simulate high CPU load (delay each frame by <ms> milliseconds).\n"); @@ -1140,6 +1144,8 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph OS::get_singleton()->print("Missing remote debug server uri, aborting.\n"); goto error; } + } else if (I->get() == "--single-threaded-scene") { + single_threaded_scene = true; } else if (I->get() == "--build-solutions") { // Build the scripting solution such C# auto_build_solutions = true; @@ -1308,6 +1314,8 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph debug_paths = true; } else if (I->get() == "--debug-navigation") { debug_navigation = true; + } else if (I->get() == "--debug-avoidance") { + debug_avoidance = true; } else if (I->get() == "--debug-stringnames") { StringName::set_debug_stringnames(true); #endif @@ -2846,11 +2854,26 @@ bool Main::start() { } if (debug_navigation) { sml->set_debug_navigation_hint(true); + } + if (debug_avoidance) { + sml->set_debug_avoidance_hint(true); + } + if (debug_navigation || debug_avoidance) { NavigationServer3D::get_singleton()->set_active(true); NavigationServer3D::get_singleton()->set_debug_enabled(true); + if (debug_navigation) { + NavigationServer3D::get_singleton()->set_debug_navigation_enabled(true); + } + if (debug_avoidance) { + NavigationServer3D::get_singleton()->set_debug_avoidance_enabled(true); + } } #endif + if (single_threaded_scene) { + sml->set_disable_node_threading(true); + } + bool embed_subwindows = GLOBAL_GET("display/window/subwindows/embed_subwindows"); if (single_window || (!project_manager && !editor && embed_subwindows) || !DisplayServer::get_singleton()->has_feature(DisplayServer::Feature::FEATURE_SUBWINDOWS)) { @@ -3423,6 +3446,8 @@ void Main::cleanup(bool p_force) { movie_writer->end(); } + ResourceLoader::clear_thread_load_tasks(); + ResourceLoader::remove_custom_loaders(); ResourceSaver::remove_custom_savers(); @@ -3439,8 +3464,6 @@ void Main::cleanup(bool p_force) { ResourceLoader::clear_translation_remaps(); ResourceLoader::clear_path_remaps(); - ResourceLoader::clear_thread_load_tasks(); - ScriptServer::finish_languages(); // Sync pending commands that may have been queued from a different thread during ScriptServer finalization diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml index e3129e848c..e3f5502391 100644 --- a/modules/gdscript/doc_classes/@GDScript.xml +++ b/modules/gdscript/doc_classes/@GDScript.xml @@ -458,6 +458,16 @@ [/codeblock] </description> </annotation> + <annotation name="@export_flags_avoidance"> + <return type="void" /> + <description> + Export an integer property as a bit flag field for navigation avoidance layers. The widget in the Inspector dock will use the layer names defined in [member ProjectSettings.layer_names/avoidance/layer_1]. + See also [constant PROPERTY_HINT_LAYERS_AVOIDANCE]. + [codeblock] + @export_flags_avoidance var avoidance_layers: int + [/codeblock] + </description> + </annotation> <annotation name="@export_global_dir"> <return type="void" /> <description> diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index d3529154cf..3bce258072 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -104,6 +104,7 @@ GDScriptParser::GDScriptParser() { register_annotation(MethodInfo("@export_flags_3d_render"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_3D_RENDER, Variant::INT>); register_annotation(MethodInfo("@export_flags_3d_physics"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_3D_PHYSICS, Variant::INT>); register_annotation(MethodInfo("@export_flags_3d_navigation"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_3D_NAVIGATION, Variant::INT>); + register_annotation(MethodInfo("@export_flags_avoidance"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_AVOIDANCE, Variant::INT>); // Export grouping annotations. register_annotation(MethodInfo("@export_category", PropertyInfo(Variant::STRING, "name")), AnnotationInfo::STANDALONE, &GDScriptParser::export_group_annotations<PROPERTY_USAGE_CATEGORY>); register_annotation(MethodInfo("@export_group", PropertyInfo(Variant::STRING, "name"), PropertyInfo(Variant::STRING, "prefix")), AnnotationInfo::STANDALONE, &GDScriptParser::export_group_annotations<PROPERTY_USAGE_GROUP>, varray("")); diff --git a/modules/gridmap/grid_map.cpp b/modules/gridmap/grid_map.cpp index db8c645558..c77fa98be2 100644 --- a/modules/gridmap/grid_map.cpp +++ b/modules/gridmap/grid_map.cpp @@ -907,7 +907,7 @@ void GridMap::_notification(int p_what) { #ifdef DEBUG_ENABLED case NOTIFICATION_ENTER_TREE: { - if (bake_navigation && NavigationServer3D::get_singleton()->get_debug_enabled()) { + if (bake_navigation && NavigationServer3D::get_singleton()->get_debug_navigation_enabled()) { _update_navigation_debug_edge_connections(); } } break; @@ -1352,7 +1352,7 @@ void GridMap::_update_octant_navigation_debug_edge_connections_mesh(const Octant ERR_FAIL_COND(!octant_map.has(p_key)); Octant &g = *octant_map[p_key]; - if (!NavigationServer3D::get_singleton()->get_debug_enabled()) { + if (!NavigationServer3D::get_singleton()->get_debug_navigation_enabled()) { if (g.navigation_debug_edge_connections_instance.is_valid()) { RS::get_singleton()->instance_set_visible(g.navigation_debug_edge_connections_instance, false); } diff --git a/modules/mbedtls/SCsub b/modules/mbedtls/SCsub index 9133fdef35..7c1204d2b7 100644 --- a/modules/mbedtls/SCsub +++ b/modules/mbedtls/SCsub @@ -100,10 +100,14 @@ if env["builtin_mbedtls"]: thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources] env_mbed_tls.Prepend(CPPPATH=["#thirdparty/mbedtls/include/"]) + env_mbed_tls.Append( + CPPDEFINES=[("MBEDTLS_CONFIG_FILE", '\\"thirdparty/mbedtls/include/godot_module_mbedtls_config.h\\"')] + ) env_thirdparty = env_mbed_tls.Clone() env_thirdparty.disable_warnings() env_thirdparty.add_source_files(thirdparty_obj, thirdparty_sources) + env_thirdparty.Depends(thirdparty_obj, "#thirdparty/mbedtls/include/godot_module_mbedtls_config.h") env.modules_sources += thirdparty_obj diff --git a/modules/mbedtls/packet_peer_mbed_dtls.cpp b/modules/mbedtls/packet_peer_mbed_dtls.cpp index e8eb32f88d..ed1a97cc2c 100644 --- a/modules/mbedtls/packet_peer_mbed_dtls.cpp +++ b/modules/mbedtls/packet_peer_mbed_dtls.cpp @@ -29,7 +29,6 @@ /**************************************************************************/ #include "packet_peer_mbed_dtls.h" -#include "mbedtls/platform_util.h" #include "core/io/file_access.h" #include "core/io/stream_peer_tls.h" diff --git a/modules/navigation/SCsub b/modules/navigation/SCsub index a9277657f4..46bcb0fba4 100644 --- a/modules/navigation/SCsub +++ b/modules/navigation/SCsub @@ -33,12 +33,14 @@ if env["builtin_recastnavigation"]: env_thirdparty.disable_warnings() env_thirdparty.add_source_files(thirdparty_obj, thirdparty_sources) -# RVO Thirdparty source files -if env["builtin_rvo2"]: - thirdparty_dir = "#thirdparty/rvo2/" +# RVO 2D Thirdparty source files +if env["builtin_rvo2_2d"]: + thirdparty_dir = "#thirdparty/rvo2/rvo2_2d/" thirdparty_sources = [ - "Agent.cpp", - "KdTree.cpp", + "Agent2d.cpp", + "Obstacle2d.cpp", + "KdTree2d.cpp", + "RVOSimulator2d.cpp", ] thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources] @@ -48,9 +50,24 @@ if env["builtin_rvo2"]: env_thirdparty.disable_warnings() env_thirdparty.add_source_files(thirdparty_obj, thirdparty_sources) +# RVO 3D Thirdparty source files +if env["builtin_rvo2_3d"]: + thirdparty_dir = "#thirdparty/rvo2/rvo2_3d/" + thirdparty_sources = [ + "Agent3d.cpp", + "KdTree3d.cpp", + "RVOSimulator3d.cpp", + ] + thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources] -env.modules_sources += thirdparty_obj + env_navigation.Prepend(CPPPATH=[thirdparty_dir]) + env_thirdparty = env_navigation.Clone() + env_thirdparty.disable_warnings() + env_thirdparty.add_source_files(thirdparty_obj, thirdparty_sources) + + +env.modules_sources += thirdparty_obj # Godot source files diff --git a/modules/navigation/godot_navigation_server.cpp b/modules/navigation/godot_navigation_server.cpp index ee9687cf3d..63423e655c 100644 --- a/modules/navigation/godot_navigation_server.cpp +++ b/modules/navigation/godot_navigation_server.cpp @@ -271,6 +271,18 @@ TypedArray<RID> GodotNavigationServer::map_get_agents(RID p_map) const { return agents_rids; } +TypedArray<RID> GodotNavigationServer::map_get_obstacles(RID p_map) const { + TypedArray<RID> obstacles_rids; + const NavMap *map = map_owner.get_or_null(p_map); + ERR_FAIL_COND_V(map == nullptr, obstacles_rids); + const LocalVector<NavObstacle *> obstacles = map->get_obstacles(); + obstacles_rids.resize(obstacles.size()); + for (uint32_t i = 0; i < obstacles.size(); i++) { + obstacles_rids[i] = obstacles[i]->get_self(); + } + return obstacles_rids; +} + RID GodotNavigationServer::region_get_map(RID p_region) const { NavRegion *region = region_owner.get_or_null(p_region); ERR_FAIL_COND_V(region == nullptr, RID()); @@ -584,6 +596,34 @@ RID GodotNavigationServer::agent_create() { return rid; } +COMMAND_2(agent_set_avoidance_enabled, RID, p_agent, bool, p_enabled) { + NavAgent *agent = agent_owner.get_or_null(p_agent); + ERR_FAIL_COND(agent == nullptr); + + agent->set_avoidance_enabled(p_enabled); +} + +bool GodotNavigationServer::agent_get_avoidance_enabled(RID p_agent) const { + NavAgent *agent = agent_owner.get_or_null(p_agent); + ERR_FAIL_COND_V(agent == nullptr, false); + + return agent->is_avoidance_enabled(); +} + +COMMAND_2(agent_set_use_3d_avoidance, RID, p_agent, bool, p_enabled) { + NavAgent *agent = agent_owner.get_or_null(p_agent); + ERR_FAIL_COND(agent == nullptr); + + agent->set_use_3d_avoidance(p_enabled); +} + +bool GodotNavigationServer::agent_get_use_3d_avoidance(RID p_agent) const { + NavAgent *agent = agent_owner.get_or_null(p_agent); + ERR_FAIL_COND_V(agent == nullptr, false); + + return agent->get_use_3d_avoidance(); +} + COMMAND_2(agent_set_map, RID, p_agent, RID, p_map) { NavAgent *agent = agent_owner.get_or_null(p_agent); ERR_FAIL_COND(agent == nullptr); @@ -605,7 +645,7 @@ COMMAND_2(agent_set_map, RID, p_agent, RID, p_map) { agent->set_map(map); map->add_agent(agent); - if (agent->has_callback()) { + if (agent->has_avoidance_callback()) { map->set_agent_as_controlled(agent); } } @@ -615,63 +655,75 @@ COMMAND_2(agent_set_neighbor_distance, RID, p_agent, real_t, p_distance) { NavAgent *agent = agent_owner.get_or_null(p_agent); ERR_FAIL_COND(agent == nullptr); - agent->get_agent()->neighborDist_ = p_distance; + agent->set_neighbor_distance(p_distance); } COMMAND_2(agent_set_max_neighbors, RID, p_agent, int, p_count) { NavAgent *agent = agent_owner.get_or_null(p_agent); ERR_FAIL_COND(agent == nullptr); - agent->get_agent()->maxNeighbors_ = p_count; + agent->set_max_neighbors(p_count); } -COMMAND_2(agent_set_time_horizon, RID, p_agent, real_t, p_time) { +COMMAND_2(agent_set_time_horizon_agents, RID, p_agent, real_t, p_time_horizon) { + ERR_FAIL_COND_MSG(p_time_horizon < 0.0, "Time horizion must be positive."); NavAgent *agent = agent_owner.get_or_null(p_agent); ERR_FAIL_COND(agent == nullptr); - agent->get_agent()->timeHorizon_ = p_time; + agent->set_time_horizon_agents(p_time_horizon); +} + +COMMAND_2(agent_set_time_horizon_obstacles, RID, p_agent, real_t, p_time_horizon) { + ERR_FAIL_COND_MSG(p_time_horizon < 0.0, "Time horizion must be positive."); + NavAgent *agent = agent_owner.get_or_null(p_agent); + ERR_FAIL_COND(agent == nullptr); + + agent->set_time_horizon_obstacles(p_time_horizon); } COMMAND_2(agent_set_radius, RID, p_agent, real_t, p_radius) { + ERR_FAIL_COND_MSG(p_radius < 0.0, "Radius must be positive."); NavAgent *agent = agent_owner.get_or_null(p_agent); ERR_FAIL_COND(agent == nullptr); - agent->get_agent()->radius_ = p_radius; + agent->set_radius(p_radius); } -COMMAND_2(agent_set_max_speed, RID, p_agent, real_t, p_max_speed) { +COMMAND_2(agent_set_height, RID, p_agent, real_t, p_height) { + ERR_FAIL_COND_MSG(p_height < 0.0, "Height must be positive."); NavAgent *agent = agent_owner.get_or_null(p_agent); ERR_FAIL_COND(agent == nullptr); - agent->get_agent()->maxSpeed_ = p_max_speed; + agent->set_height(p_height); } -COMMAND_2(agent_set_velocity, RID, p_agent, Vector3, p_velocity) { +COMMAND_2(agent_set_max_speed, RID, p_agent, real_t, p_max_speed) { + ERR_FAIL_COND_MSG(p_max_speed < 0.0, "Max speed must be positive."); NavAgent *agent = agent_owner.get_or_null(p_agent); ERR_FAIL_COND(agent == nullptr); - agent->get_agent()->velocity_ = RVO::Vector3(p_velocity.x, p_velocity.y, p_velocity.z); + agent->set_max_speed(p_max_speed); } -COMMAND_2(agent_set_target_velocity, RID, p_agent, Vector3, p_velocity) { +COMMAND_2(agent_set_velocity, RID, p_agent, Vector3, p_velocity) { NavAgent *agent = agent_owner.get_or_null(p_agent); ERR_FAIL_COND(agent == nullptr); - agent->get_agent()->prefVelocity_ = RVO::Vector3(p_velocity.x, p_velocity.y, p_velocity.z); + agent->set_velocity(p_velocity); } -COMMAND_2(agent_set_position, RID, p_agent, Vector3, p_position) { +COMMAND_2(agent_set_velocity_forced, RID, p_agent, Vector3, p_velocity) { NavAgent *agent = agent_owner.get_or_null(p_agent); ERR_FAIL_COND(agent == nullptr); - agent->get_agent()->position_ = RVO::Vector3(p_position.x, p_position.y, p_position.z); + agent->set_velocity_forced(p_velocity); } -COMMAND_2(agent_set_ignore_y, RID, p_agent, bool, p_ignore) { +COMMAND_2(agent_set_position, RID, p_agent, Vector3, p_position) { NavAgent *agent = agent_owner.get_or_null(p_agent); ERR_FAIL_COND(agent == nullptr); - agent->get_agent()->ignore_y_ = p_ignore; + agent->set_position(p_position); } bool GodotNavigationServer::agent_is_map_changed(RID p_agent) const { @@ -681,11 +733,11 @@ bool GodotNavigationServer::agent_is_map_changed(RID p_agent) const { return agent->is_map_changed(); } -COMMAND_2(agent_set_callback, RID, p_agent, Callable, p_callback) { +COMMAND_2(agent_set_avoidance_callback, RID, p_agent, Callable, p_callback) { NavAgent *agent = agent_owner.get_or_null(p_agent); ERR_FAIL_COND(agent == nullptr); - agent->set_callback(p_callback); + agent->set_avoidance_callback(p_callback); if (agent->get_map()) { if (p_callback.is_valid()) { @@ -696,6 +748,91 @@ COMMAND_2(agent_set_callback, RID, p_agent, Callable, p_callback) { } } +COMMAND_2(agent_set_avoidance_layers, RID, p_agent, uint32_t, p_layers) { + NavAgent *agent = agent_owner.get_or_null(p_agent); + ERR_FAIL_COND(agent == nullptr); + agent->set_avoidance_layers(p_layers); +} + +COMMAND_2(agent_set_avoidance_mask, RID, p_agent, uint32_t, p_mask) { + NavAgent *agent = agent_owner.get_or_null(p_agent); + ERR_FAIL_COND(agent == nullptr); + agent->set_avoidance_mask(p_mask); +} + +COMMAND_2(agent_set_avoidance_priority, RID, p_agent, real_t, p_priority) { + ERR_FAIL_COND_MSG(p_priority < 0.0, "Avoidance priority must be between 0.0 and 1.0 inclusive."); + ERR_FAIL_COND_MSG(p_priority > 1.0, "Avoidance priority must be between 0.0 and 1.0 inclusive."); + NavAgent *agent = agent_owner.get_or_null(p_agent); + ERR_FAIL_COND(agent == nullptr); + agent->set_avoidance_priority(p_priority); +} + +RID GodotNavigationServer::obstacle_create() { + GodotNavigationServer *mut_this = const_cast<GodotNavigationServer *>(this); + MutexLock lock(mut_this->operations_mutex); + RID rid = obstacle_owner.make_rid(); + NavObstacle *obstacle = obstacle_owner.get_or_null(rid); + obstacle->set_self(rid); + return rid; +} + +COMMAND_2(obstacle_set_map, RID, p_obstacle, RID, p_map) { + NavObstacle *obstacle = obstacle_owner.get_or_null(p_obstacle); + ERR_FAIL_COND(obstacle == nullptr); + + if (obstacle->get_map()) { + if (obstacle->get_map()->get_self() == p_map) { + return; // Pointless + } + + obstacle->get_map()->remove_obstacle(obstacle); + } + + obstacle->set_map(nullptr); + + if (p_map.is_valid()) { + NavMap *map = map_owner.get_or_null(p_map); + ERR_FAIL_COND(map == nullptr); + + obstacle->set_map(map); + map->add_obstacle(obstacle); + } +} + +RID GodotNavigationServer::obstacle_get_map(RID p_obstacle) const { + NavObstacle *obstacle = obstacle_owner.get_or_null(p_obstacle); + ERR_FAIL_COND_V(obstacle == nullptr, RID()); + if (obstacle->get_map()) { + return obstacle->get_map()->get_self(); + } + return RID(); +} + +COMMAND_2(obstacle_set_height, RID, p_obstacle, real_t, p_height) { + NavObstacle *obstacle = obstacle_owner.get_or_null(p_obstacle); + ERR_FAIL_COND(obstacle == nullptr); + obstacle->set_height(p_height); +} + +COMMAND_2(obstacle_set_position, RID, p_obstacle, Vector3, p_position) { + NavObstacle *obstacle = obstacle_owner.get_or_null(p_obstacle); + ERR_FAIL_COND(obstacle == nullptr); + obstacle->set_position(p_position); +} + +void GodotNavigationServer::obstacle_set_vertices(RID p_obstacle, const Vector<Vector3> &p_vertices) { + NavObstacle *obstacle = obstacle_owner.get_or_null(p_obstacle); + ERR_FAIL_COND(obstacle == nullptr); + obstacle->set_vertices(p_vertices); +} + +COMMAND_2(obstacle_set_avoidance_layers, RID, p_obstacle, uint32_t, p_layers) { + NavObstacle *obstacle = obstacle_owner.get_or_null(p_obstacle); + ERR_FAIL_COND(obstacle == nullptr); + obstacle->set_avoidance_layers(p_layers); +} + COMMAND_1(free, RID, p_object) { if (map_owner.owns(p_object)) { NavMap *map = map_owner.get_or_null(p_object); @@ -718,6 +855,12 @@ COMMAND_1(free, RID, p_object) { agent->set_map(nullptr); } + // Remove any assigned obstacles + for (NavObstacle *obstacle : map->get_obstacles()) { + map->remove_obstacle(obstacle); + obstacle->set_map(nullptr); + } + int map_index = active_maps.find(map); active_maps.remove_at(map_index); active_maps_update_id.remove_at(map_index); @@ -756,6 +899,17 @@ COMMAND_1(free, RID, p_object) { agent_owner.free(p_object); + } else if (obstacle_owner.owns(p_object)) { + NavObstacle *obstacle = obstacle_owner.get_or_null(p_object); + + // Removes this agent from the map if assigned + if (obstacle->get_map() != nullptr) { + obstacle->get_map()->remove_obstacle(obstacle); + obstacle->set_map(nullptr); + } + + obstacle_owner.free(p_object); + } else { ERR_PRINT("Attempted to free a NavigationServer RID that did not exist (or was already freed)."); } diff --git a/modules/navigation/godot_navigation_server.h b/modules/navigation/godot_navigation_server.h index 0b113b77d4..ee9b1f05b7 100644 --- a/modules/navigation/godot_navigation_server.h +++ b/modules/navigation/godot_navigation_server.h @@ -39,6 +39,7 @@ #include "nav_agent.h" #include "nav_link.h" #include "nav_map.h" +#include "nav_obstacle.h" #include "nav_region.h" /// The commands are functions executed during the `sync` phase. @@ -72,6 +73,7 @@ class GodotNavigationServer : public NavigationServer3D { mutable RID_Owner<NavMap> map_owner; mutable RID_Owner<NavRegion> region_owner; mutable RID_Owner<NavAgent> agent_owner; + mutable RID_Owner<NavObstacle> obstacle_owner; bool active = true; LocalVector<NavMap *> active_maps; @@ -121,6 +123,7 @@ public: virtual TypedArray<RID> map_get_links(RID p_map) const override; virtual TypedArray<RID> map_get_regions(RID p_map) const override; virtual TypedArray<RID> map_get_agents(RID p_map) const override; + virtual TypedArray<RID> map_get_obstacles(RID p_map) const override; virtual void map_force_update(RID p_map) override; @@ -166,19 +169,35 @@ public: virtual ObjectID link_get_owner_id(RID p_link) const override; virtual RID agent_create() override; + COMMAND_2(agent_set_avoidance_enabled, RID, p_agent, bool, p_enabled); + virtual bool agent_get_avoidance_enabled(RID p_agent) const override; + COMMAND_2(agent_set_use_3d_avoidance, RID, p_agent, bool, p_enabled); + virtual bool agent_get_use_3d_avoidance(RID p_agent) const override; COMMAND_2(agent_set_map, RID, p_agent, RID, p_map); virtual RID agent_get_map(RID p_agent) const override; COMMAND_2(agent_set_neighbor_distance, RID, p_agent, real_t, p_distance); COMMAND_2(agent_set_max_neighbors, RID, p_agent, int, p_count); - COMMAND_2(agent_set_time_horizon, RID, p_agent, real_t, p_time); + COMMAND_2(agent_set_time_horizon_agents, RID, p_agent, real_t, p_time_horizon); + COMMAND_2(agent_set_time_horizon_obstacles, RID, p_agent, real_t, p_time_horizon); COMMAND_2(agent_set_radius, RID, p_agent, real_t, p_radius); + COMMAND_2(agent_set_height, RID, p_agent, real_t, p_height); COMMAND_2(agent_set_max_speed, RID, p_agent, real_t, p_max_speed); COMMAND_2(agent_set_velocity, RID, p_agent, Vector3, p_velocity); - COMMAND_2(agent_set_target_velocity, RID, p_agent, Vector3, p_velocity); + COMMAND_2(agent_set_velocity_forced, RID, p_agent, Vector3, p_velocity); COMMAND_2(agent_set_position, RID, p_agent, Vector3, p_position); - COMMAND_2(agent_set_ignore_y, RID, p_agent, bool, p_ignore); virtual bool agent_is_map_changed(RID p_agent) const override; - COMMAND_2(agent_set_callback, RID, p_agent, Callable, p_callback); + COMMAND_2(agent_set_avoidance_callback, RID, p_agent, Callable, p_callback); + COMMAND_2(agent_set_avoidance_layers, RID, p_agent, uint32_t, p_layers); + COMMAND_2(agent_set_avoidance_mask, RID, p_agent, uint32_t, p_mask); + COMMAND_2(agent_set_avoidance_priority, RID, p_agent, real_t, p_priority); + + virtual RID obstacle_create() override; + COMMAND_2(obstacle_set_map, RID, p_obstacle, RID, p_map); + virtual RID obstacle_get_map(RID p_obstacle) const override; + COMMAND_2(obstacle_set_position, RID, p_obstacle, Vector3, p_position); + COMMAND_2(obstacle_set_height, RID, p_obstacle, real_t, p_height); + virtual void obstacle_set_vertices(RID p_obstacle, const Vector<Vector3> &p_vertices) override; + COMMAND_2(obstacle_set_avoidance_layers, RID, p_obstacle, uint32_t, p_layers); COMMAND_1(free, RID, p_object); diff --git a/modules/navigation/nav_agent.cpp b/modules/navigation/nav_agent.cpp index 293544c0a5..a0efe4c74c 100644 --- a/modules/navigation/nav_agent.cpp +++ b/modules/navigation/nav_agent.cpp @@ -32,8 +32,66 @@ #include "nav_map.h" +NavAgent::NavAgent() { +} + +void NavAgent::set_avoidance_enabled(bool p_enabled) { + avoidance_enabled = p_enabled; + _update_rvo_agent_properties(); +} + +void NavAgent::set_use_3d_avoidance(bool p_enabled) { + use_3d_avoidance = p_enabled; + _update_rvo_agent_properties(); +} + +void NavAgent::_update_rvo_agent_properties() { + if (use_3d_avoidance) { + rvo_agent_3d.neighborDist_ = neighbor_distance; + rvo_agent_3d.maxNeighbors_ = max_neighbors; + rvo_agent_3d.timeHorizon_ = time_horizon_agents; + rvo_agent_3d.timeHorizonObst_ = time_horizon_obstacles; + rvo_agent_3d.radius_ = radius; + rvo_agent_3d.maxSpeed_ = max_speed; + rvo_agent_3d.position_ = RVO3D::Vector3(position.x, position.y, position.z); + // Replacing the internal velocity directly causes major jitter / bugs due to unpredictable velocity jumps, left line here for testing. + //rvo_agent_3d.velocity_ = RVO3D::Vector3(velocity.x, velocity.y ,velocity.z); + rvo_agent_3d.prefVelocity_ = RVO3D::Vector3(velocity.x, velocity.y, velocity.z); + rvo_agent_3d.height_ = height; + rvo_agent_3d.avoidance_layers_ = avoidance_layers; + rvo_agent_3d.avoidance_mask_ = avoidance_mask; + rvo_agent_3d.avoidance_priority_ = avoidance_priority; + } else { + rvo_agent_2d.neighborDist_ = neighbor_distance; + rvo_agent_2d.maxNeighbors_ = max_neighbors; + rvo_agent_2d.timeHorizon_ = time_horizon_agents; + rvo_agent_2d.timeHorizonObst_ = time_horizon_obstacles; + rvo_agent_2d.radius_ = radius; + rvo_agent_2d.maxSpeed_ = max_speed; + rvo_agent_2d.position_ = RVO2D::Vector2(position.x, position.z); + rvo_agent_2d.elevation_ = position.y; + // Replacing the internal velocity directly causes major jitter / bugs due to unpredictable velocity jumps, left line here for testing. + //rvo_agent_2d.velocity_ = RVO2D::Vector2(velocity.x, velocity.z); + rvo_agent_2d.prefVelocity_ = RVO2D::Vector2(velocity.x, velocity.z); + rvo_agent_2d.height_ = height; + rvo_agent_2d.avoidance_layers_ = avoidance_layers; + rvo_agent_2d.avoidance_mask_ = avoidance_mask; + rvo_agent_2d.avoidance_priority_ = avoidance_priority; + } + + if (map != nullptr) { + if (avoidance_enabled) { + map->set_agent_as_controlled(this); + } else { + map->remove_agent_as_controlled(this); + } + } + agent_dirty = true; +} + void NavAgent::set_map(NavMap *p_map) { map = p_map; + agent_dirty = true; } bool NavAgent::is_map_changed() { @@ -46,25 +104,241 @@ bool NavAgent::is_map_changed() { } } -void NavAgent::set_callback(Callable p_callback) { - callback = p_callback; +void NavAgent::set_avoidance_callback(Callable p_callback) { + avoidance_callback = p_callback; } -bool NavAgent::has_callback() const { - return callback.is_valid(); +bool NavAgent::has_avoidance_callback() const { + return avoidance_callback.is_valid(); } -void NavAgent::dispatch_callback() { - if (!callback.is_valid()) { +void NavAgent::dispatch_avoidance_callback() { + if (!avoidance_callback.is_valid()) { return; } - Vector3 new_velocity = Vector3(agent.newVelocity_.x(), agent.newVelocity_.y(), agent.newVelocity_.z()); + Vector3 new_velocity; + + if (use_3d_avoidance) { + new_velocity = Vector3(rvo_agent_3d.velocity_.x(), rvo_agent_3d.velocity_.y(), rvo_agent_3d.velocity_.z()); + } else { + new_velocity = Vector3(rvo_agent_2d.velocity_.x(), 0.0, rvo_agent_2d.velocity_.y()); + } + + if (clamp_speed) { + new_velocity = new_velocity.limit_length(max_speed); + } // Invoke the callback with the new velocity. Variant args[] = { new_velocity }; const Variant *args_p[] = { &args[0] }; Variant return_value; Callable::CallError call_error; - callback.callp(args_p, 1, return_value, call_error); + + avoidance_callback.callp(args_p, 1, return_value, call_error); +} + +void NavAgent::set_neighbor_distance(real_t p_neighbor_distance) { + neighbor_distance = p_neighbor_distance; + if (use_3d_avoidance) { + rvo_agent_3d.neighborDist_ = neighbor_distance; + } else { + rvo_agent_2d.neighborDist_ = neighbor_distance; + } + agent_dirty = true; +} + +void NavAgent::set_max_neighbors(int p_max_neighbors) { + max_neighbors = p_max_neighbors; + if (use_3d_avoidance) { + rvo_agent_3d.maxNeighbors_ = max_neighbors; + } else { + rvo_agent_2d.maxNeighbors_ = max_neighbors; + } + agent_dirty = true; +} + +void NavAgent::set_time_horizon_agents(real_t p_time_horizon) { + time_horizon_agents = p_time_horizon; + if (use_3d_avoidance) { + rvo_agent_3d.timeHorizon_ = time_horizon_agents; + } else { + rvo_agent_2d.timeHorizon_ = time_horizon_agents; + } + agent_dirty = true; +} + +void NavAgent::set_time_horizon_obstacles(real_t p_time_horizon) { + time_horizon_obstacles = p_time_horizon; + if (use_3d_avoidance) { + rvo_agent_3d.timeHorizonObst_ = time_horizon_obstacles; + } else { + rvo_agent_2d.timeHorizonObst_ = time_horizon_obstacles; + } + agent_dirty = true; +} + +void NavAgent::set_radius(real_t p_radius) { + radius = p_radius; + if (use_3d_avoidance) { + rvo_agent_3d.radius_ = radius; + } else { + rvo_agent_2d.radius_ = radius; + } + agent_dirty = true; +} + +void NavAgent::set_height(real_t p_height) { + height = p_height; + if (use_3d_avoidance) { + rvo_agent_3d.height_ = height; + } else { + rvo_agent_2d.height_ = height; + } + agent_dirty = true; +} + +void NavAgent::set_max_speed(real_t p_max_speed) { + max_speed = p_max_speed; + if (avoidance_enabled) { + if (use_3d_avoidance) { + rvo_agent_3d.maxSpeed_ = max_speed; + } else { + rvo_agent_2d.maxSpeed_ = max_speed; + } + } + agent_dirty = true; +} + +void NavAgent::set_position(const Vector3 p_position) { + position = p_position; + if (avoidance_enabled) { + if (use_3d_avoidance) { + rvo_agent_3d.position_ = RVO3D::Vector3(p_position.x, p_position.y, p_position.z); + } else { + rvo_agent_2d.elevation_ = p_position.y; + rvo_agent_2d.position_ = RVO2D::Vector2(p_position.x, p_position.z); + } + } + agent_dirty = true; +} + +void NavAgent::set_target_position(const Vector3 p_target_position) { + target_position = p_target_position; +} + +void NavAgent::set_velocity(const Vector3 p_velocity) { + // Sets the "wanted" velocity for an agent as a suggestion + // This velocity is not guaranteed, RVO simulation will only try to fulfill it + velocity = p_velocity; + if (avoidance_enabled) { + if (use_3d_avoidance) { + rvo_agent_3d.prefVelocity_ = RVO3D::Vector3(velocity.x, velocity.y, velocity.z); + } else { + rvo_agent_2d.prefVelocity_ = RVO2D::Vector2(velocity.x, velocity.z); + } + } + agent_dirty = true; +} + +void NavAgent::set_velocity_forced(const Vector3 p_velocity) { + // This function replaces the internal rvo simulation velocity + // should only be used after the agent was teleported + // as it destroys consistency in movement in cramped situations + // use velocity instead to update with a safer "suggestion" + velocity_forced = p_velocity; + if (avoidance_enabled) { + if (use_3d_avoidance) { + rvo_agent_3d.velocity_ = RVO3D::Vector3(p_velocity.x, p_velocity.y, p_velocity.z); + } else { + rvo_agent_2d.velocity_ = RVO2D::Vector2(p_velocity.x, p_velocity.z); + } + } + agent_dirty = true; +} + +void NavAgent::update() { + // Updates this agent with the calculated results from the rvo simulation + if (avoidance_enabled) { + if (use_3d_avoidance) { + velocity = Vector3(rvo_agent_3d.velocity_.x(), rvo_agent_3d.velocity_.y(), rvo_agent_3d.velocity_.z()); + } else { + velocity = Vector3(rvo_agent_2d.velocity_.x(), 0.0, rvo_agent_2d.velocity_.y()); + } + } +} + +void NavAgent::set_avoidance_mask(uint32_t p_mask) { + avoidance_mask = p_mask; + if (use_3d_avoidance) { + rvo_agent_3d.avoidance_mask_ = avoidance_mask; + } else { + rvo_agent_2d.avoidance_mask_ = avoidance_mask; + } + agent_dirty = true; +} + +void NavAgent::set_avoidance_layers(uint32_t p_layers) { + avoidance_layers = p_layers; + if (use_3d_avoidance) { + rvo_agent_3d.avoidance_layers_ = avoidance_layers; + } else { + rvo_agent_2d.avoidance_layers_ = avoidance_layers; + } + agent_dirty = true; +} + +void NavAgent::set_avoidance_priority(real_t p_priority) { + ERR_FAIL_COND_MSG(p_priority < 0.0, "Avoidance priority must be between 0.0 and 1.0 inclusive."); + ERR_FAIL_COND_MSG(p_priority > 1.0, "Avoidance priority must be between 0.0 and 1.0 inclusive."); + avoidance_priority = p_priority; + if (use_3d_avoidance) { + rvo_agent_3d.avoidance_priority_ = avoidance_priority; + } else { + rvo_agent_2d.avoidance_priority_ = avoidance_priority; + } + agent_dirty = true; +}; + +bool NavAgent::check_dirty() { + const bool was_dirty = agent_dirty; + agent_dirty = false; + return was_dirty; +} + +const Dictionary NavAgent::get_avoidance_data() const { + // Returns debug data from RVO simulation internals of this agent. + Dictionary _avoidance_data; + if (use_3d_avoidance) { + _avoidance_data["max_neighbors"] = int(rvo_agent_3d.maxNeighbors_); + _avoidance_data["max_speed"] = float(rvo_agent_3d.maxSpeed_); + _avoidance_data["neighbor_distance"] = float(rvo_agent_3d.neighborDist_); + _avoidance_data["new_velocity"] = Vector3(rvo_agent_3d.newVelocity_.x(), rvo_agent_3d.newVelocity_.y(), rvo_agent_3d.newVelocity_.z()); + _avoidance_data["velocity"] = Vector3(rvo_agent_3d.velocity_.x(), rvo_agent_3d.velocity_.y(), rvo_agent_3d.velocity_.z()); + _avoidance_data["position"] = Vector3(rvo_agent_3d.position_.x(), rvo_agent_3d.position_.y(), rvo_agent_3d.position_.z()); + _avoidance_data["prefered_velocity"] = Vector3(rvo_agent_3d.prefVelocity_.x(), rvo_agent_3d.prefVelocity_.y(), rvo_agent_3d.prefVelocity_.z()); + _avoidance_data["radius"] = float(rvo_agent_3d.radius_); + _avoidance_data["time_horizon_agents"] = float(rvo_agent_3d.timeHorizon_); + _avoidance_data["time_horizon_obstacles"] = 0.0; + _avoidance_data["height"] = float(rvo_agent_3d.height_); + _avoidance_data["avoidance_layers"] = int(rvo_agent_3d.avoidance_layers_); + _avoidance_data["avoidance_mask"] = int(rvo_agent_3d.avoidance_mask_); + _avoidance_data["avoidance_priority"] = float(rvo_agent_3d.avoidance_priority_); + } else { + _avoidance_data["max_neighbors"] = int(rvo_agent_2d.maxNeighbors_); + _avoidance_data["max_speed"] = float(rvo_agent_2d.maxSpeed_); + _avoidance_data["neighbor_distance"] = float(rvo_agent_2d.neighborDist_); + _avoidance_data["new_velocity"] = Vector3(rvo_agent_2d.newVelocity_.x(), 0.0, rvo_agent_2d.newVelocity_.y()); + _avoidance_data["velocity"] = Vector3(rvo_agent_2d.velocity_.x(), 0.0, rvo_agent_2d.velocity_.y()); + _avoidance_data["position"] = Vector3(rvo_agent_2d.position_.x(), 0.0, rvo_agent_2d.position_.y()); + _avoidance_data["prefered_velocity"] = Vector3(rvo_agent_2d.prefVelocity_.x(), 0.0, rvo_agent_2d.prefVelocity_.y()); + _avoidance_data["radius"] = float(rvo_agent_2d.radius_); + _avoidance_data["time_horizon_agents"] = float(rvo_agent_2d.timeHorizon_); + _avoidance_data["time_horizon_obstacles"] = float(rvo_agent_2d.timeHorizonObst_); + _avoidance_data["height"] = float(rvo_agent_2d.height_); + _avoidance_data["avoidance_layers"] = int(rvo_agent_2d.avoidance_layers_); + _avoidance_data["avoidance_mask"] = int(rvo_agent_2d.avoidance_mask_); + _avoidance_data["avoidance_priority"] = float(rvo_agent_2d.avoidance_priority_); + } + return _avoidance_data; } diff --git a/modules/navigation/nav_agent.h b/modules/navigation/nav_agent.h index f154ce14d9..497b239f84 100644 --- a/modules/navigation/nav_agent.h +++ b/modules/navigation/nav_agent.h @@ -32,34 +32,121 @@ #define NAV_AGENT_H #include "core/object/class_db.h" +#include "core/templates/local_vector.h" +#include "nav_agent.h" #include "nav_rid.h" -#include <Agent.h> +#include <Agent2d.h> +#include <Agent3d.h> class NavMap; class NavAgent : public NavRid { + Vector3 position; + Vector3 target_position; + Vector3 velocity; + Vector3 velocity_forced; + real_t height = 1.0; + real_t radius = 1.0; + real_t max_speed = 1.0; + real_t time_horizon_agents = 1.0; + real_t time_horizon_obstacles = 0.0; + int max_neighbors = 5; + real_t neighbor_distance = 5.0; + Vector3 safe_velocity; + bool clamp_speed = true; // Experimental, clamps velocity to max_speed. + NavMap *map = nullptr; - RVO::Agent agent; - Callable callback = Callable(); + + RVO2D::Agent2D rvo_agent_2d; + RVO3D::Agent3D rvo_agent_3d; + bool use_3d_avoidance = false; + bool avoidance_enabled = false; + + uint32_t avoidance_layers = 1; + uint32_t avoidance_mask = 1; + real_t avoidance_priority = 1.0; + + Callable avoidance_callback = Callable(); + + bool agent_dirty = true; + uint32_t map_update_id = 0; public: - void set_map(NavMap *p_map); - NavMap *get_map() { - return map; - } + NavAgent(); + + void set_avoidance_enabled(bool p_enabled); + bool is_avoidance_enabled() { return avoidance_enabled; } - RVO::Agent *get_agent() { - return &agent; - } + void set_use_3d_avoidance(bool p_enabled); + bool get_use_3d_avoidance() { return use_3d_avoidance; } + + void set_map(NavMap *p_map); + NavMap *get_map() { return map; } bool is_map_changed(); - void set_callback(Callable p_callback); - bool has_callback() const; + RVO2D::Agent2D *get_rvo_agent_2d() { return &rvo_agent_2d; } + RVO3D::Agent3D *get_rvo_agent_3d() { return &rvo_agent_3d; } + + void set_avoidance_callback(Callable p_callback); + bool has_avoidance_callback() const; + + void dispatch_avoidance_callback(); + + void set_neighbor_distance(real_t p_neighbor_distance); + real_t get_neighbor_distance() const { return neighbor_distance; } + + void set_max_neighbors(int p_max_neighbors); + int get_max_neighbors() const { return max_neighbors; } + + void set_time_horizon_agents(real_t p_time_horizon); + real_t get_time_horizon_agents() const { return time_horizon_agents; } + + void set_time_horizon_obstacles(real_t p_time_horizon); + real_t get_time_horizon_obstacles() const { return time_horizon_obstacles; } + + void set_radius(real_t p_radius); + real_t get_radius() const { return radius; } + + void set_height(real_t p_height); + real_t get_height() const { return height; } + + void set_max_speed(real_t p_max_speed); + real_t get_max_speed() const { return max_speed; } + + void set_position(const Vector3 p_position); + const Vector3 &get_position() const { return position; } + + void set_target_position(const Vector3 p_target_position); + const Vector3 &get_target_position() const { return target_position; } + + void set_velocity(const Vector3 p_velocity); + const Vector3 &get_velocity() const { return velocity; } + + void set_velocity_forced(const Vector3 p_velocity); + const Vector3 &get_velocity_forced() const { return velocity_forced; } + + void set_avoidance_layers(uint32_t p_layers); + uint32_t get_avoidance_layers() const { return avoidance_layers; }; + + void set_avoidance_mask(uint32_t p_mask); + uint32_t get_avoidance_mask() const { return avoidance_mask; }; + + void set_avoidance_priority(real_t p_priority); + real_t get_avoidance_priority() const { return avoidance_priority; }; + + bool check_dirty(); + + // Updates this agent with rvo data after the rvo simulation avoidance step. + void update(); + + // RVO debug data from the last frame update. + const Dictionary get_avoidance_data() const; - void dispatch_callback(); +private: + void _update_rvo_agent_properties(); }; #endif // NAV_AGENT_H diff --git a/modules/navigation/nav_link.cpp b/modules/navigation/nav_link.cpp index ad87cc0b05..5607a3253e 100644 --- a/modules/navigation/nav_link.cpp +++ b/modules/navigation/nav_link.cpp @@ -33,21 +33,33 @@ #include "nav_map.h" void NavLink::set_map(NavMap *p_map) { + if (map == p_map) { + return; + } map = p_map; link_dirty = true; } void NavLink::set_bidirectional(bool p_bidirectional) { + if (bidirectional == p_bidirectional) { + return; + } bidirectional = p_bidirectional; link_dirty = true; } void NavLink::set_start_position(const Vector3 p_position) { + if (start_position == p_position) { + return; + } start_position = p_position; link_dirty = true; } void NavLink::set_end_position(const Vector3 p_position) { + if (end_position == p_position) { + return; + } end_position = p_position; link_dirty = true; } diff --git a/modules/navigation/nav_map.cpp b/modules/navigation/nav_map.cpp index 91b13ba9c4..57d702e846 100644 --- a/modules/navigation/nav_map.cpp +++ b/modules/navigation/nav_map.cpp @@ -30,11 +30,14 @@ #include "nav_map.h" +#include "core/config/project_settings.h" #include "core/object/worker_thread_pool.h" #include "nav_agent.h" #include "nav_link.h" +#include "nav_obstacle.h" #include "nav_region.h" -#include <algorithm> + +#include <Obstacle2d.h> #define THREE_POINTS_CROSS_PRODUCT(m_a, m_b, m_c) (((m_c) - (m_a)).cross((m_b) - (m_a))) @@ -51,21 +54,33 @@ } void NavMap::set_up(Vector3 p_up) { + if (up == p_up) { + return; + } up = p_up; regenerate_polygons = true; } void NavMap::set_cell_size(real_t p_cell_size) { + if (cell_size == p_cell_size) { + return; + } cell_size = p_cell_size; regenerate_polygons = true; } void NavMap::set_edge_connection_margin(real_t p_edge_connection_margin) { + if (edge_connection_margin == p_edge_connection_margin) { + return; + } edge_connection_margin = p_edge_connection_margin; regenerate_links = true; } void NavMap::set_link_connection_radius(real_t p_link_connection_radius) { + if (link_connection_radius == p_link_connection_radius) { + return; + } link_connection_radius = p_link_connection_radius; regenerate_links = true; } @@ -522,9 +537,7 @@ gd::ClosestPointQueryResult NavMap::get_closest_point_info(const Vector3 &p_poin gd::ClosestPointQueryResult result; real_t closest_point_ds = FLT_MAX; - for (size_t i(0); i < polygons.size(); i++) { - const gd::Polygon &p = polygons[i]; - + for (const gd::Polygon &p : polygons) { // For each face check the distance to the point for (size_t point_id = 2; point_id < p.points.size(); point_id += 1) { const Face3 f(p.points[0].pos, p.points[point_id - 1].pos, p.points[point_id].pos); @@ -549,7 +562,7 @@ void NavMap::add_region(NavRegion *p_region) { void NavMap::remove_region(NavRegion *p_region) { int64_t region_index = regions.find(p_region); - if (region_index != -1) { + if (region_index >= 0) { regions.remove_at_unordered(region_index); regenerate_links = true; } @@ -562,14 +575,14 @@ void NavMap::add_link(NavLink *p_link) { void NavMap::remove_link(NavLink *p_link) { int64_t link_index = links.find(p_link); - if (link_index != -1) { + if (link_index >= 0) { links.remove_at_unordered(link_index); regenerate_links = true; } } bool NavMap::has_agent(NavAgent *agent) const { - return (agents.find(agent) != -1); + return (agents.find(agent) >= 0); } void NavMap::add_agent(NavAgent *agent) { @@ -582,25 +595,57 @@ void NavMap::add_agent(NavAgent *agent) { void NavMap::remove_agent(NavAgent *agent) { remove_agent_as_controlled(agent); int64_t agent_index = agents.find(agent); - if (agent_index != -1) { + if (agent_index >= 0) { agents.remove_at_unordered(agent_index); agents_dirty = true; } } +bool NavMap::has_obstacle(NavObstacle *obstacle) const { + return (obstacles.find(obstacle) >= 0); +} + +void NavMap::add_obstacle(NavObstacle *obstacle) { + if (!has_obstacle(obstacle)) { + obstacles.push_back(obstacle); + obstacles_dirty = true; + } +} + +void NavMap::remove_obstacle(NavObstacle *obstacle) { + int64_t obstacle_index = obstacles.find(obstacle); + if (obstacle_index >= 0) { + obstacles.remove_at_unordered(obstacle_index); + obstacles_dirty = true; + } +} + void NavMap::set_agent_as_controlled(NavAgent *agent) { - const bool exist = (controlled_agents.find(agent) != -1); - if (!exist) { - ERR_FAIL_COND(!has_agent(agent)); - controlled_agents.push_back(agent); - agents_dirty = true; + remove_agent_as_controlled(agent); + if (agent->get_use_3d_avoidance()) { + int64_t agent_3d_index = active_3d_avoidance_agents.find(agent); + if (agent_3d_index < 0) { + active_3d_avoidance_agents.push_back(agent); + agents_dirty = true; + } + } else { + int64_t agent_2d_index = active_2d_avoidance_agents.find(agent); + if (agent_2d_index < 0) { + active_2d_avoidance_agents.push_back(agent); + agents_dirty = true; + } } } void NavMap::remove_agent_as_controlled(NavAgent *agent) { - int64_t active_avoidance_agent_index = controlled_agents.find(agent); - if (active_avoidance_agent_index != -1) { - controlled_agents.remove_at_unordered(active_avoidance_agent_index); + int64_t agent_3d_index = active_3d_avoidance_agents.find(agent); + if (agent_3d_index >= 0) { + active_3d_avoidance_agents.remove_at_unordered(agent_3d_index); + agents_dirty = true; + } + int64_t agent_2d_index = active_2d_avoidance_agents.find(agent); + if (agent_2d_index >= 0) { + active_2d_avoidance_agents.remove_at_unordered(agent_2d_index); agents_dirty = true; } } @@ -891,22 +936,30 @@ void NavMap::sync() { map_update_id = (map_update_id + 1) % 9999999; } - // Update agents tree. - if (agents_dirty) { - // cannot use LocalVector here as RVO library expects std::vector to build KdTree - std::vector<RVO::Agent *> raw_agents; - raw_agents.reserve(controlled_agents.size()); - for (NavAgent *controlled_agent : controlled_agents) { - raw_agents.push_back(controlled_agent->get_agent()); + // Do we have modified obstacle positions? + for (NavObstacle *obstacle : obstacles) { + if (obstacle->check_dirty()) { + obstacles_dirty = true; + } + } + // Do we have modified agent arrays? + for (NavAgent *agent : agents) { + if (agent->check_dirty()) { + agents_dirty = true; } - rvo.buildAgentTree(raw_agents); + } + + // Update avoidance worlds. + if (obstacles_dirty || agents_dirty) { + _update_rvo_simulation(); } regenerate_polygons = false; regenerate_links = false; + obstacles_dirty = false; agents_dirty = false; - // Performance Monitor + // Performance Monitor. pm_region_count = _new_pm_region_count; pm_agent_count = _new_pm_agent_count; pm_link_count = _new_pm_link_count; @@ -917,22 +970,155 @@ void NavMap::sync() { pm_edge_free_count = _new_pm_edge_free_count; } -void NavMap::compute_single_step(uint32_t index, NavAgent **agent) { - (*(agent + index))->get_agent()->computeNeighbors(&rvo); - (*(agent + index))->get_agent()->computeNewVelocity(deltatime); +void NavMap::_update_rvo_obstacles_tree_2d() { + int obstacle_vertex_count = 0; + for (NavObstacle *obstacle : obstacles) { + obstacle_vertex_count += obstacle->get_vertices().size(); + } + + // Cannot use LocalVector here as RVO library expects std::vector to build KdTree + std::vector<RVO2D::Obstacle2D *> raw_obstacles; + raw_obstacles.reserve(obstacle_vertex_count); + + // The following block is modified copy from RVO2D::AddObstacle() + // Obstacles are linked and depend on all other obstacles. + for (NavObstacle *obstacle : obstacles) { + const Vector3 &_obstacle_position = obstacle->get_position(); + const Vector<Vector3> &_obstacle_vertices = obstacle->get_vertices(); + + if (_obstacle_vertices.size() < 2) { + continue; + } + + std::vector<RVO2D::Vector2> rvo_2d_vertices; + rvo_2d_vertices.reserve(_obstacle_vertices.size()); + + uint32_t _obstacle_avoidance_layers = obstacle->get_avoidance_layers(); + + for (const Vector3 &_obstacle_vertex : _obstacle_vertices) { + rvo_2d_vertices.push_back(RVO2D::Vector2(_obstacle_vertex.x + _obstacle_position.x, _obstacle_vertex.z + _obstacle_position.z)); + } + + const size_t obstacleNo = raw_obstacles.size(); + + for (size_t i = 0; i < rvo_2d_vertices.size(); i++) { + RVO2D::Obstacle2D *rvo_2d_obstacle = new RVO2D::Obstacle2D(); + rvo_2d_obstacle->point_ = rvo_2d_vertices[i]; + rvo_2d_obstacle->avoidance_layers_ = _obstacle_avoidance_layers; + + if (i != 0) { + rvo_2d_obstacle->prevObstacle_ = raw_obstacles.back(); + rvo_2d_obstacle->prevObstacle_->nextObstacle_ = rvo_2d_obstacle; + } + + if (i == rvo_2d_vertices.size() - 1) { + rvo_2d_obstacle->nextObstacle_ = raw_obstacles[obstacleNo]; + rvo_2d_obstacle->nextObstacle_->prevObstacle_ = rvo_2d_obstacle; + } + + rvo_2d_obstacle->unitDir_ = normalize(rvo_2d_vertices[(i == rvo_2d_vertices.size() - 1 ? 0 : i + 1)] - rvo_2d_vertices[i]); + + if (rvo_2d_vertices.size() == 2) { + rvo_2d_obstacle->isConvex_ = true; + } else { + rvo_2d_obstacle->isConvex_ = (leftOf(rvo_2d_vertices[(i == 0 ? rvo_2d_vertices.size() - 1 : i - 1)], rvo_2d_vertices[i], rvo_2d_vertices[(i == rvo_2d_vertices.size() - 1 ? 0 : i + 1)]) >= 0.0f); + } + + rvo_2d_obstacle->id_ = raw_obstacles.size(); + + raw_obstacles.push_back(rvo_2d_obstacle); + } + } + + rvo_simulation_2d.kdTree_->buildObstacleTree(raw_obstacles); +} + +void NavMap::_update_rvo_agents_tree_2d() { + // Cannot use LocalVector here as RVO library expects std::vector to build KdTree. + std::vector<RVO2D::Agent2D *> raw_agents; + raw_agents.reserve(active_2d_avoidance_agents.size()); + for (NavAgent *agent : active_2d_avoidance_agents) { + raw_agents.push_back(agent->get_rvo_agent_2d()); + } + rvo_simulation_2d.kdTree_->buildAgentTree(raw_agents); +} + +void NavMap::_update_rvo_agents_tree_3d() { + // Cannot use LocalVector here as RVO library expects std::vector to build KdTree. + std::vector<RVO3D::Agent3D *> raw_agents; + raw_agents.reserve(active_3d_avoidance_agents.size()); + for (NavAgent *agent : active_3d_avoidance_agents) { + raw_agents.push_back(agent->get_rvo_agent_3d()); + } + rvo_simulation_3d.kdTree_->buildAgentTree(raw_agents); +} + +void NavMap::_update_rvo_simulation() { + if (obstacles_dirty) { + _update_rvo_obstacles_tree_2d(); + } + if (agents_dirty) { + _update_rvo_agents_tree_2d(); + _update_rvo_agents_tree_3d(); + } +} + +void NavMap::compute_single_avoidance_step_2d(uint32_t index, NavAgent **agent) { + (*(agent + index))->get_rvo_agent_2d()->computeNeighbors(&rvo_simulation_2d); + (*(agent + index))->get_rvo_agent_2d()->computeNewVelocity(&rvo_simulation_2d); + (*(agent + index))->get_rvo_agent_2d()->update(&rvo_simulation_2d); + (*(agent + index))->update(); +} + +void NavMap::compute_single_avoidance_step_3d(uint32_t index, NavAgent **agent) { + (*(agent + index))->get_rvo_agent_3d()->computeNeighbors(&rvo_simulation_3d); + (*(agent + index))->get_rvo_agent_3d()->computeNewVelocity(&rvo_simulation_3d); + (*(agent + index))->get_rvo_agent_3d()->update(&rvo_simulation_3d); + (*(agent + index))->update(); } void NavMap::step(real_t p_deltatime) { deltatime = p_deltatime; - if (controlled_agents.size() > 0) { - WorkerThreadPool::GroupID group_task = WorkerThreadPool::get_singleton()->add_template_group_task(this, &NavMap::compute_single_step, controlled_agents.ptr(), controlled_agents.size(), -1, true, SNAME("NavigationMapAgents")); - WorkerThreadPool::get_singleton()->wait_for_group_task_completion(group_task); + + rvo_simulation_2d.setTimeStep(float(deltatime)); + rvo_simulation_3d.setTimeStep(float(deltatime)); + + if (active_2d_avoidance_agents.size() > 0) { + if (use_threads && avoidance_use_multiple_threads) { + WorkerThreadPool::GroupID group_task = WorkerThreadPool::get_singleton()->add_template_group_task(this, &NavMap::compute_single_avoidance_step_2d, active_2d_avoidance_agents.ptr(), active_2d_avoidance_agents.size(), -1, true, SNAME("RVOAvoidanceAgents2D")); + WorkerThreadPool::get_singleton()->wait_for_group_task_completion(group_task); + } else { + for (NavAgent *agent : active_2d_avoidance_agents) { + agent->get_rvo_agent_2d()->computeNeighbors(&rvo_simulation_2d); + agent->get_rvo_agent_2d()->computeNewVelocity(&rvo_simulation_2d); + agent->get_rvo_agent_2d()->update(&rvo_simulation_2d); + agent->update(); + } + } + } + + if (active_3d_avoidance_agents.size() > 0) { + if (use_threads && avoidance_use_multiple_threads) { + WorkerThreadPool::GroupID group_task = WorkerThreadPool::get_singleton()->add_template_group_task(this, &NavMap::compute_single_avoidance_step_3d, active_3d_avoidance_agents.ptr(), active_3d_avoidance_agents.size(), -1, true, SNAME("RVOAvoidanceAgents3D")); + WorkerThreadPool::get_singleton()->wait_for_group_task_completion(group_task); + } else { + for (NavAgent *agent : active_3d_avoidance_agents) { + agent->get_rvo_agent_3d()->computeNeighbors(&rvo_simulation_3d); + agent->get_rvo_agent_3d()->computeNewVelocity(&rvo_simulation_3d); + agent->get_rvo_agent_3d()->update(&rvo_simulation_3d); + agent->update(); + } + } } } void NavMap::dispatch_callbacks() { - for (NavAgent *agent : controlled_agents) { - agent->dispatch_callback(); + for (NavAgent *agent : active_2d_avoidance_agents) { + agent->dispatch_avoidance_callback(); + } + + for (NavAgent *agent : active_3d_avoidance_agents) { + agent->dispatch_avoidance_callback(); } } @@ -970,6 +1156,8 @@ void NavMap::clip_path(const LocalVector<gd::NavigationPoly> &p_navigation_polys } NavMap::NavMap() { + avoidance_use_multiple_threads = GLOBAL_GET("navigation/avoidance/thread_model/avoidance_use_multiple_threads"); + avoidance_use_high_priority_threads = GLOBAL_GET("navigation/avoidance/thread_model/avoidance_use_high_priority_threads"); } NavMap::~NavMap() { diff --git a/modules/navigation/nav_map.h b/modules/navigation/nav_map.h index 5ec2c2826c..343f53760b 100644 --- a/modules/navigation/nav_map.h +++ b/modules/navigation/nav_map.h @@ -38,11 +38,16 @@ #include "core/templates/rb_map.h" #include "nav_utils.h" -#include <KdTree.h> +#include <KdTree2d.h> +#include <RVOSimulator2d.h> + +#include <KdTree3d.h> +#include <RVOSimulator3d.h> class NavLink; class NavRegion; class NavAgent; +class NavObstacle; class NavMap : public NavRid { /// Map Up @@ -71,17 +76,25 @@ class NavMap : public NavRid { /// Map polygons LocalVector<gd::Polygon> polygons; - /// Rvo world - RVO::KdTree rvo; + /// RVO avoidance worlds + RVO2D::RVOSimulator2D rvo_simulation_2d; + RVO3D::RVOSimulator3D rvo_simulation_3d; + + /// avoidance controlled agents + LocalVector<NavAgent *> active_2d_avoidance_agents; + LocalVector<NavAgent *> active_3d_avoidance_agents; - /// Is agent array modified? - bool agents_dirty = false; + /// dirty flag when one of the agent's arrays are modified + bool agents_dirty = true; /// All the Agents (even the controlled one) LocalVector<NavAgent *> agents; - /// Controlled agents - LocalVector<NavAgent *> controlled_agents; + /// All the avoidance obstacles (both static and dynamic) + LocalVector<NavObstacle *> obstacles; + + /// Are rvo obstacles modified? + bool obstacles_dirty = true; /// Physics delta time real_t deltatime = 0.0; @@ -89,6 +102,10 @@ class NavMap : public NavRid { /// Change the id each time the map is updated. uint32_t map_update_id = 0; + bool use_threads = true; + bool avoidance_use_multiple_threads = true; + bool avoidance_use_high_priority_threads = true; + // Performance Monitor int pm_region_count = 0; int pm_agent_count = 0; @@ -154,6 +171,13 @@ public: void set_agent_as_controlled(NavAgent *agent); void remove_agent_as_controlled(NavAgent *agent); + bool has_obstacle(NavObstacle *obstacle) const; + void add_obstacle(NavObstacle *obstacle); + void remove_obstacle(NavObstacle *obstacle); + const LocalVector<NavObstacle *> &get_obstacles() const { + return obstacles; + } + uint32_t get_map_update_id() const { return map_update_id; } @@ -174,7 +198,15 @@ public: private: void compute_single_step(uint32_t index, NavAgent **agent); + + void compute_single_avoidance_step_2d(uint32_t index, NavAgent **agent); + void compute_single_avoidance_step_3d(uint32_t index, NavAgent **agent); + void clip_path(const LocalVector<gd::NavigationPoly> &p_navigation_polys, Vector<Vector3> &path, const gd::NavigationPoly *from_poly, const Vector3 &p_to_point, const gd::NavigationPoly *p_to_poly, Vector<int32_t> *r_path_types, TypedArray<RID> *r_path_rids, Vector<int64_t> *r_path_owners) const; + void _update_rvo_simulation(); + void _update_rvo_obstacles_tree_2d(); + void _update_rvo_agents_tree_2d(); + void _update_rvo_agents_tree_3d(); }; #endif // NAV_MAP_H diff --git a/modules/navigation/nav_obstacle.cpp b/modules/navigation/nav_obstacle.cpp new file mode 100644 index 0000000000..5d0bc59cbb --- /dev/null +++ b/modules/navigation/nav_obstacle.cpp @@ -0,0 +1,86 @@ +/**************************************************************************/ +/* nav_obstacle.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 "nav_obstacle.h" + +#include "nav_map.h" + +NavObstacle::NavObstacle() {} + +NavObstacle::~NavObstacle() {} + +void NavObstacle::set_map(NavMap *p_map) { + if (map != p_map) { + map = p_map; + obstacle_dirty = true; + } +} + +void NavObstacle::set_position(const Vector3 p_position) { + if (position != p_position) { + position = p_position; + obstacle_dirty = true; + } +} + +void NavObstacle::set_height(const real_t p_height) { + if (height != p_height) { + height = p_height; + obstacle_dirty = true; + } +} + +void NavObstacle::set_vertices(const Vector<Vector3> &p_vertices) { + if (vertices != p_vertices) { + vertices = p_vertices; + obstacle_dirty = true; + } +} + +bool NavObstacle::is_map_changed() { + if (map) { + bool is_changed = map->get_map_update_id() != map_update_id; + map_update_id = map->get_map_update_id(); + return is_changed; + } else { + return false; + } +} + +void NavObstacle::set_avoidance_layers(uint32_t p_layers) { + avoidance_layers = p_layers; + obstacle_dirty = true; +} + +bool NavObstacle::check_dirty() { + const bool was_dirty = obstacle_dirty; + obstacle_dirty = false; + return was_dirty; +} diff --git a/modules/navigation/nav_obstacle.h b/modules/navigation/nav_obstacle.h new file mode 100644 index 0000000000..f59eba5200 --- /dev/null +++ b/modules/navigation/nav_obstacle.h @@ -0,0 +1,78 @@ +/**************************************************************************/ +/* nav_obstacle.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 NAV_OBSTACLE_H +#define NAV_OBSTACLE_H + +#include "core/object/class_db.h" +#include "core/templates/local_vector.h" +#include "nav_agent.h" +#include "nav_rid.h" + +class NavMap; + +class NavObstacle : public NavRid { + NavMap *map = nullptr; + Vector3 position; + Vector<Vector3> vertices; + + real_t height = 0.0; + + uint32_t avoidance_layers = 1; + + bool obstacle_dirty = true; + + uint32_t map_update_id = 0; + +public: + NavObstacle(); + ~NavObstacle(); + + void set_map(NavMap *p_map); + NavMap *get_map() { return map; } + + void set_position(const Vector3 p_position); + const Vector3 &get_position() const { return position; } + + void set_height(const real_t p_height); + real_t get_height() const { return height; } + + void set_vertices(const Vector<Vector3> &p_vertices); + const Vector<Vector3> &get_vertices() const { return vertices; } + + bool is_map_changed(); + + void set_avoidance_layers(uint32_t p_layers); + uint32_t get_avoidance_layers() const { return avoidance_layers; }; + + bool check_dirty(); +}; + +#endif // NAV_OBSTACLE_H diff --git a/modules/navigation/nav_region.cpp b/modules/navigation/nav_region.cpp index cad4678e5a..bcee6ed751 100644 --- a/modules/navigation/nav_region.cpp +++ b/modules/navigation/nav_region.cpp @@ -33,6 +33,9 @@ #include "nav_map.h" void NavRegion::set_map(NavMap *p_map) { + if (map == p_map) { + return; + } map = p_map; polygons_dirty = true; if (!map) { @@ -41,6 +44,9 @@ void NavRegion::set_map(NavMap *p_map) { } void NavRegion::set_transform(Transform3D p_transform) { + if (transform == p_transform) { + return; + } transform = p_transform; polygons_dirty = true; } diff --git a/platform/android/display_server_android.h b/platform/android/display_server_android.h index b2400d81dc..ad1cbddb08 100644 --- a/platform/android/display_server_android.h +++ b/platform/android/display_server_android.h @@ -39,6 +39,8 @@ class RenderingDeviceVulkan; #endif class DisplayServerAndroid : public DisplayServer { + // No need to register with GDCLASS, it's platform-specific and nothing is added. + String rendering_driver; // https://developer.android.com/reference/android/view/PointerIcon diff --git a/platform/android/doc_classes/EditorExportPlatformAndroid.xml b/platform/android/doc_classes/EditorExportPlatformAndroid.xml index 1a07774287..11129ca149 100644 --- a/platform/android/doc_classes/EditorExportPlatformAndroid.xml +++ b/platform/android/doc_classes/EditorExportPlatformAndroid.xml @@ -58,21 +58,27 @@ </member> <member name="keystore/debug" type="String" setter="" getter=""> Path of the debug keystore file. + Can be overridden with the environment variable [code]GODOT_ANDROID_KEYSTORE_DEBUG_PATH[/code]. </member> <member name="keystore/debug_password" type="String" setter="" getter=""> Password for the debug keystore file. + Can be overridden with the environment variable [code]GODOT_ANDROID_KEYSTORE_DEBUG_PASSWORD[/code]. </member> <member name="keystore/debug_user" type="String" setter="" getter=""> User name for the debug keystore file. + Can be overridden with the environment variable [code]GODOT_ANDROID_KEYSTORE_DEBUG_USER[/code]. </member> <member name="keystore/release" type="String" setter="" getter=""> Path of the release keystore file. + Can be overridden with the environment variable [code]GODOT_ANDROID_KEYSTORE_RELEASE_PATH[/code]. </member> <member name="keystore/release_password" type="String" setter="" getter=""> Password for the release keystore file. + Can be overridden with the environment variable [code]GODOT_ANDROID_KEYSTORE_RELEASE_PASSWORD[/code]. </member> <member name="keystore/release_user" type="String" setter="" getter=""> User name for the release keystore file. + Can be overridden with the environment variable [code]GODOT_ANDROID_KEYSTORE_RELEASE_USER[/code]. </member> <member name="launcher_icons/adaptive_background_432x432" type="String" setter="" getter=""> Background layer of the application adaptive icon file. diff --git a/platform/android/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp index 87f4cf2de2..d8dd453faf 100644 --- a/platform/android/export/export_plugin.cpp +++ b/platform/android/export/export_plugin.cpp @@ -1825,12 +1825,12 @@ void EditorExportPlatformAndroid::get_export_options(List<ExportOption> *r_optio r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, vformat("%s/%s", PNAME("architectures"), abi)), is_default)); } - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "keystore/debug", PROPERTY_HINT_GLOBAL_FILE, "*.keystore,*.jks"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "keystore/debug_user"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "keystore/debug_password"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "keystore/release", PROPERTY_HINT_GLOBAL_FILE, "*.keystore,*.jks"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "keystore/release_user"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "keystore/release_password"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "keystore/debug", PROPERTY_HINT_GLOBAL_FILE, "*.keystore,*.jks", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SECRET), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "keystore/debug_user", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SECRET), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "keystore/debug_password", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SECRET), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "keystore/release", PROPERTY_HINT_GLOBAL_FILE, "*.keystore,*.jks", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SECRET), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "keystore/release_user", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SECRET), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "keystore/release_password", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SECRET), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "version/code", PROPERTY_HINT_RANGE, "1,4096,1,or_greater"), 1)); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "version/name"), "1.0")); @@ -2277,9 +2277,9 @@ bool EditorExportPlatformAndroid::has_valid_export_configuration(const Ref<Edito // Validate the rest of the export configuration. - String dk = p_preset->get("keystore/debug"); - String dk_user = p_preset->get("keystore/debug_user"); - String dk_password = p_preset->get("keystore/debug_password"); + String dk = p_preset->get_or_env("keystore/debug", ENV_ANDROID_KEYSTORE_DEBUG_PATH); + String dk_user = p_preset->get_or_env("keystore/debug_user", ENV_ANDROID_KEYSTORE_DEBUG_USER); + String dk_password = p_preset->get_or_env("keystore/debug_password", ENV_ANDROID_KEYSTORE_DEBUG_PASS); if ((dk.is_empty() || dk_user.is_empty() || dk_password.is_empty()) && (!dk.is_empty() || !dk_user.is_empty() || !dk_password.is_empty())) { valid = false; @@ -2294,9 +2294,9 @@ bool EditorExportPlatformAndroid::has_valid_export_configuration(const Ref<Edito } } - String rk = p_preset->get("keystore/release"); - String rk_user = p_preset->get("keystore/release_user"); - String rk_password = p_preset->get("keystore/release_password"); + String rk = p_preset->get_or_env("keystore/release", ENV_ANDROID_KEYSTORE_RELEASE_PATH); + String rk_user = p_preset->get_or_env("keystore/release_user", ENV_ANDROID_KEYSTORE_RELEASE_USER); + String rk_password = p_preset->get_or_env("keystore/release_password", ENV_ANDROID_KEYSTORE_RELEASE_PASS); if ((rk.is_empty() || rk_user.is_empty() || rk_password.is_empty()) && (!rk.is_empty() || !rk_user.is_empty() || !rk_password.is_empty())) { valid = false; @@ -2507,9 +2507,9 @@ void EditorExportPlatformAndroid::get_command_line_flags(const Ref<EditorExportP Error EditorExportPlatformAndroid::sign_apk(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &export_path, EditorProgress &ep) { int export_format = int(p_preset->get("gradle_build/export_format")); String export_label = export_format == EXPORT_FORMAT_AAB ? "AAB" : "APK"; - String release_keystore = p_preset->get("keystore/release"); - String release_username = p_preset->get("keystore/release_user"); - String release_password = p_preset->get("keystore/release_password"); + String release_keystore = p_preset->get_or_env("keystore/release", ENV_ANDROID_KEYSTORE_RELEASE_PATH); + String release_username = p_preset->get_or_env("keystore/release_user", ENV_ANDROID_KEYSTORE_RELEASE_USER); + String release_password = p_preset->get_or_env("keystore/release_password", ENV_ANDROID_KEYSTORE_RELEASE_PASS); String target_sdk_version = p_preset->get("gradle_build/target_sdk"); if (!target_sdk_version.is_valid_int()) { target_sdk_version = itos(DEFAULT_TARGET_SDK_VERSION); @@ -2529,9 +2529,9 @@ Error EditorExportPlatformAndroid::sign_apk(const Ref<EditorExportPreset> &p_pre String password; String user; if (p_debug) { - keystore = p_preset->get("keystore/debug"); - password = p_preset->get("keystore/debug_password"); - user = p_preset->get("keystore/debug_user"); + keystore = p_preset->get_or_env("keystore/debug", ENV_ANDROID_KEYSTORE_DEBUG_PATH); + password = p_preset->get_or_env("keystore/debug_password", ENV_ANDROID_KEYSTORE_DEBUG_PASS); + user = p_preset->get_or_env("keystore/debug_user", ENV_ANDROID_KEYSTORE_DEBUG_USER); if (keystore.is_empty()) { keystore = EDITOR_GET("export/android/debug_keystore"); @@ -2886,9 +2886,9 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP if (should_sign) { if (p_debug) { - String debug_keystore = p_preset->get("keystore/debug"); - String debug_password = p_preset->get("keystore/debug_password"); - String debug_user = p_preset->get("keystore/debug_user"); + String debug_keystore = p_preset->get_or_env("keystore/debug", ENV_ANDROID_KEYSTORE_DEBUG_PATH); + String debug_password = p_preset->get_or_env("keystore/debug_password", ENV_ANDROID_KEYSTORE_DEBUG_PASS); + String debug_user = p_preset->get_or_env("keystore/debug_user", ENV_ANDROID_KEYSTORE_DEBUG_USER); if (debug_keystore.is_empty()) { debug_keystore = EDITOR_GET("export/android/debug_keystore"); @@ -2908,9 +2908,9 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP cmdline.push_back("-Pdebug_keystore_password=" + debug_password); // argument to specify the debug keystore password. } else { // Pass the release keystore info as well - String release_keystore = p_preset->get("keystore/release"); - String release_username = p_preset->get("keystore/release_user"); - String release_password = p_preset->get("keystore/release_password"); + String release_keystore = p_preset->get_or_env("keystore/release", ENV_ANDROID_KEYSTORE_RELEASE_PATH); + String release_username = p_preset->get_or_env("keystore/release_user", ENV_ANDROID_KEYSTORE_RELEASE_USER); + String release_password = p_preset->get_or_env("keystore/release_password", ENV_ANDROID_KEYSTORE_RELEASE_PASS); if (release_keystore.is_relative_path()) { release_keystore = OS::get_singleton()->get_resource_dir().path_join(release_keystore).simplify_path(); } diff --git a/platform/android/export/export_plugin.h b/platform/android/export/export_plugin.h index f9dad5ce5e..390b8c6465 100644 --- a/platform/android/export/export_plugin.h +++ b/platform/android/export/export_plugin.h @@ -49,6 +49,15 @@ const String SPLASH_CONFIG_XML_CONTENT = R"SPLASH(<?xml version="1.0" encoding=" </layer-list> )SPLASH"; +// Optional environment variables for defining confidential information. If any +// of these is set, they will override the values set in the credentials file. +const String ENV_ANDROID_KEYSTORE_DEBUG_PATH = "GODOT_ANDROID_KEYSTORE_DEBUG_PATH"; +const String ENV_ANDROID_KEYSTORE_DEBUG_USER = "GODOT_ANDROID_KEYSTORE_DEBUG_USER"; +const String ENV_ANDROID_KEYSTORE_DEBUG_PASS = "GODOT_ANDROID_KEYSTORE_DEBUG_PASSWORD"; +const String ENV_ANDROID_KEYSTORE_RELEASE_PATH = "GODOT_ANDROID_KEYSTORE_RELEASE_PATH"; +const String ENV_ANDROID_KEYSTORE_RELEASE_USER = "GODOT_ANDROID_KEYSTORE_RELEASE_USER"; +const String ENV_ANDROID_KEYSTORE_RELEASE_PASS = "GODOT_ANDROID_KEYSTORE_RELEASE_PASSWORD"; + struct LauncherIcon { const char *export_path; int dimensions = 0; diff --git a/platform/ios/display_server_ios.h b/platform/ios/display_server_ios.h index 6eaa7c8edc..57f601a858 100644 --- a/platform/ios/display_server_ios.h +++ b/platform/ios/display_server_ios.h @@ -55,7 +55,7 @@ #import <QuartzCore/CAMetalLayer.h> class DisplayServerIOS : public DisplayServer { - GDCLASS(DisplayServerIOS, DisplayServer) + // No need to register with GDCLASS, it's platform-specific and nothing is added. _THREAD_SAFE_CLASS_ diff --git a/platform/ios/doc_classes/EditorExportPlatformIOS.xml b/platform/ios/doc_classes/EditorExportPlatformIOS.xml index 899ab61206..381884067b 100644 --- a/platform/ios/doc_classes/EditorExportPlatformIOS.xml +++ b/platform/ios/doc_classes/EditorExportPlatformIOS.xml @@ -35,9 +35,11 @@ </member> <member name="application/provisioning_profile_uuid_debug" type="String" setter="" getter=""> UUID of the provisioning profile. If left empty, Xcode will download or create a provisioning profile automatically. See [url=https://developer.apple.com/help/account/manage-profiles/edit-download-or-delete-profiles]Edit, download, or delete provisioning profiles[/url]. + Can be overridden with the environment variable [code]GODOT_IOS_PROVISIONING_PROFILE_UUID_DEBUG[/code]. </member> <member name="application/provisioning_profile_uuid_release" type="String" setter="" getter=""> UUID of the provisioning profile. If left empty, Xcode will download or create a provisioning profile automatically. See [url=https://developer.apple.com/help/account/manage-profiles/edit-download-or-delete-profiles]Edit, download, or delete provisioning profiles[/url]. + Can be overridden with the environment variable [code]GODOT_IOS_PROVISIONING_PROFILE_UUID_RELEASE[/code]. </member> <member name="application/short_version" type="String" setter="" getter=""> Application version visible to the user, can only contain numeric characters ([code]0-9[/code]) and periods ([code].[/code]). diff --git a/platform/ios/export/export_plugin.cpp b/platform/ios/export/export_plugin.cpp index 9c0e4e39e8..06741a12e4 100644 --- a/platform/ios/export/export_plugin.cpp +++ b/platform/ios/export/export_plugin.cpp @@ -160,10 +160,10 @@ void EditorExportPlatformIOS::get_export_options(List<ExportOption> *r_options) r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/app_store_team_id"), "", false, true)); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/provisioning_profile_uuid_debug"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/provisioning_profile_uuid_debug", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SECRET), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/code_sign_identity_debug", PROPERTY_HINT_PLACEHOLDER_TEXT, "iPhone Developer"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "application/export_method_debug", PROPERTY_HINT_ENUM, "App Store,Development,Ad-Hoc,Enterprise"), 1)); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/provisioning_profile_uuid_release"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/provisioning_profile_uuid_release", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SECRET), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/code_sign_identity_release", PROPERTY_HINT_PLACEHOLDER_TEXT, "iPhone Distribution"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "application/export_method_release", PROPERTY_HINT_ENUM, "App Store,Development,Ad-Hoc,Enterprise"), 0)); @@ -253,8 +253,8 @@ void EditorExportPlatformIOS::_fix_config_file(const Ref<EditorExportPreset> &p_ }; String dbg_sign_id = p_preset->get("application/code_sign_identity_debug").operator String().is_empty() ? "iPhone Developer" : p_preset->get("application/code_sign_identity_debug"); String rel_sign_id = p_preset->get("application/code_sign_identity_release").operator String().is_empty() ? "iPhone Distribution" : p_preset->get("application/code_sign_identity_release"); - bool dbg_manual = !p_preset->get("application/provisioning_profile_uuid_debug").operator String().is_empty() || (dbg_sign_id != "iPhone Developer"); - bool rel_manual = !p_preset->get("application/provisioning_profile_uuid_release").operator String().is_empty() || (rel_sign_id != "iPhone Distribution"); + bool dbg_manual = !p_preset->get_or_env("application/provisioning_profile_uuid_debug", ENV_IOS_PROFILE_UUID_DEBUG).operator String().is_empty() || (dbg_sign_id != "iPhone Developer"); + bool rel_manual = !p_preset->get_or_env("application/provisioning_profile_uuid_release", ENV_IOS_PROFILE_UUID_RELEASE).operator String().is_empty() || (rel_sign_id != "iPhone Distribution"); String str; String strnew; str.parse_utf8((const char *)pfile.ptr(), pfile.size()); @@ -288,9 +288,9 @@ void EditorExportPlatformIOS::_fix_config_file(const Ref<EditorExportPreset> &p_ int export_method = p_preset->get(p_debug ? "application/export_method_debug" : "application/export_method_release"); strnew += lines[i].replace("$export_method", export_method_string[export_method]) + "\n"; } else if (lines[i].find("$provisioning_profile_uuid_release") != -1) { - strnew += lines[i].replace("$provisioning_profile_uuid_release", p_preset->get("application/provisioning_profile_uuid_release")) + "\n"; + strnew += lines[i].replace("$provisioning_profile_uuid_release", p_preset->get_or_env("application/provisioning_profile_uuid_release", ENV_IOS_PROFILE_UUID_RELEASE)) + "\n"; } else if (lines[i].find("$provisioning_profile_uuid_debug") != -1) { - strnew += lines[i].replace("$provisioning_profile_uuid_debug", p_preset->get("application/provisioning_profile_uuid_debug")) + "\n"; + strnew += lines[i].replace("$provisioning_profile_uuid_debug", p_preset->get_or_env("application/provisioning_profile_uuid_debug", ENV_IOS_PROFILE_UUID_DEBUG)) + "\n"; } else if (lines[i].find("$code_sign_style_debug") != -1) { if (dbg_manual) { strnew += lines[i].replace("$code_sign_style_debug", "Manual") + "\n"; @@ -304,7 +304,7 @@ void EditorExportPlatformIOS::_fix_config_file(const Ref<EditorExportPreset> &p_ strnew += lines[i].replace("$code_sign_style_release", "Automatic") + "\n"; } } else if (lines[i].find("$provisioning_profile_uuid") != -1) { - String uuid = p_debug ? p_preset->get("application/provisioning_profile_uuid_debug") : p_preset->get("application/provisioning_profile_uuid_release"); + String uuid = p_debug ? p_preset->get_or_env("application/provisioning_profile_uuid_debug", ENV_IOS_PROFILE_UUID_DEBUG) : p_preset->get_or_env("application/provisioning_profile_uuid_release", ENV_IOS_PROFILE_UUID_RELEASE); strnew += lines[i].replace("$provisioning_profile_uuid", uuid) + "\n"; } else if (lines[i].find("$code_sign_identity_debug") != -1) { strnew += lines[i].replace("$code_sign_identity_debug", dbg_sign_id) + "\n"; diff --git a/platform/ios/export/export_plugin.h b/platform/ios/export/export_plugin.h index 0fde3b7c0b..9afefef121 100644 --- a/platform/ios/export/export_plugin.h +++ b/platform/ios/export/export_plugin.h @@ -49,6 +49,11 @@ #include <sys/stat.h> +// Optional environment variables for defining confidential information. If any +// of these is set, they will override the values set in the credentials file. +const String ENV_IOS_PROFILE_UUID_DEBUG = "GODOT_IOS_PROVISIONING_PROFILE_UUID_DEBUG"; +const String ENV_IOS_PROFILE_UUID_RELEASE = "GODOT_IOS_PROVISIONING_PROFILE_UUID_RELEASE"; + class EditorExportPlatformIOS : public EditorExportPlatform { GDCLASS(EditorExportPlatformIOS, EditorExportPlatform); diff --git a/platform/linuxbsd/x11/display_server_x11.cpp b/platform/linuxbsd/x11/display_server_x11.cpp index 86423f798f..043a18349f 100644 --- a/platform/linuxbsd/x11/display_server_x11.cpp +++ b/platform/linuxbsd/x11/display_server_x11.cpp @@ -186,6 +186,7 @@ bool DisplayServerX11::_refresh_device_info() { xi.absolute_devices.clear(); xi.touch_devices.clear(); xi.pen_inverted_devices.clear(); + xi.last_relative_time = 0; int dev_count; XIDeviceInfo *info = XIQueryDevice(x11_display, XIAllDevices, &dev_count); diff --git a/platform/linuxbsd/x11/display_server_x11.h b/platform/linuxbsd/x11/display_server_x11.h index e8e0680c14..67fa21c75f 100644 --- a/platform/linuxbsd/x11/display_server_x11.h +++ b/platform/linuxbsd/x11/display_server_x11.h @@ -119,8 +119,7 @@ typedef struct _xrr_monitor_info { #undef CursorShape class DisplayServerX11 : public DisplayServer { - //No need to register, it's platform-specific and nothing is added - //GDCLASS(DisplayServerX11, DisplayServer) + // No need to register with GDCLASS, it's platform-specific and nothing is added. _THREAD_SAFE_CLASS_ diff --git a/platform/macos/display_server_macos.h b/platform/macos/display_server_macos.h index a71ac3a310..a1cd83280d 100644 --- a/platform/macos/display_server_macos.h +++ b/platform/macos/display_server_macos.h @@ -56,7 +56,7 @@ #undef CursorShape class DisplayServerMacOS : public DisplayServer { - GDCLASS(DisplayServerMacOS, DisplayServer) + // No need to register with GDCLASS, it's platform-specific and nothing is added. _THREAD_SAFE_CLASS_ diff --git a/platform/macos/doc_classes/EditorExportPlatformMacOS.xml b/platform/macos/doc_classes/EditorExportPlatformMacOS.xml index f57af3bbd7..64e1efde26 100644 --- a/platform/macos/doc_classes/EditorExportPlatformMacOS.xml +++ b/platform/macos/doc_classes/EditorExportPlatformMacOS.xml @@ -50,9 +50,11 @@ </member> <member name="codesign/certificate_file" type="String" setter="" getter=""> PKCS #12 certificate file used to sign [code].app[/code] bundle. + Can be overridden with the environment variable [code]GODOT_MACOS_CODESIGN_CERTIFICATE_FILE[/code]. </member> <member name="codesign/certificate_password" type="String" setter="" getter=""> Password for the certificate file used to sign [code].app[/code] bundle. + Can be overridden with the environment variable [code]GODOT_MACOS_CODESIGN_CERTIFICATE_PASSWORD[/code]. </member> <member name="codesign/codesign" type="int" setter="" getter=""> Tool to use for code signing. @@ -138,6 +140,7 @@ </member> <member name="codesign/provisioning_profile" type="String" setter="" getter=""> Provisioning profile file downloaded from Apple developer account dashboard. See [url=https://developer.apple.com/help/account/manage-profiles/edit-download-or-delete-profiles]Edit, download, or delete provisioning profiles[/url]. + Can be overridden with the environment variable [code]GODOT_MACOS_CODESIGN_PROVISIONING_PROFILE[/code]. </member> <member name="custom_template/debug" type="String" setter="" getter=""> Path to the custom export template. If left empty, default template is used. @@ -156,18 +159,23 @@ </member> <member name="notarization/api_key" type="String" setter="" getter=""> Apple App Store Connect API issuer key file. + Can be overridden with the environment variable [code]GODOT_MACOS_NOTARIZATION_API_KEY[/code]. </member> <member name="notarization/api_key_id" type="String" setter="" getter=""> Apple App Store Connect API issuer key ID. + Can be overridden with the environment variable [code]GODOT_MACOS_NOTARIZATION_API_KEY_ID[/code]. </member> <member name="notarization/api_uuid" type="String" setter="" getter=""> Apple App Store Connect API issuer UUID. + Can be overridden with the environment variable [code]GODOT_MACOS_NOTARIZATION_API_UUID[/code]. </member> <member name="notarization/apple_id_name" type="String" setter="" getter=""> Apple ID account name (email address). + Can be overridden with the environment variable [code]GODOT_MACOS_NOTARIZATION_APPLE_ID_NAME[/code]. </member> <member name="notarization/apple_id_password" type="String" setter="" getter=""> Apple ID app-specific password. + Can be overridden with the environment variable [code]GODOT_MACOS_NOTARIZATION_APPLE_ID_PASSWORD[/code]. </member> <member name="notarization/notarization" type="int" setter="" getter=""> Tool to use for notarization. diff --git a/platform/macos/export/export_plugin.cpp b/platform/macos/export/export_plugin.cpp index cd9d17dd4f..be677f4da2 100644 --- a/platform/macos/export/export_plugin.cpp +++ b/platform/macos/export/export_plugin.cpp @@ -73,7 +73,7 @@ String EditorExportPlatformMacOS::get_export_option_warning(const EditorExportPr ad_hoc = true; } break; case 2: { // "rcodesign" - ad_hoc = p_preset->get("codesign/certificate_file").operator String().is_empty() || p_preset->get("codesign/certificate_password").operator String().is_empty(); + ad_hoc = p_preset->get_or_env("codesign/certificate_file", ENV_MAC_CODESIGN_CERT_FILE).operator String().is_empty() || p_preset->get_or_env("codesign/certificate_password", ENV_MAC_CODESIGN_CERT_FILE).operator String().is_empty(); } break; #ifdef MACOS_ENABLED case 3: { // "codesign" @@ -114,7 +114,7 @@ String EditorExportPlatformMacOS::get_export_option_warning(const EditorExportPr } if (p_name == "codesign/provisioning_profile" && dist_type == 2) { - String pprof = p_preset->get("codesign/provisioning_profile"); + String pprof = p_preset->get_or_env("codesign/provisioning_profile", ENV_MAC_CODESIGN_PROFILE); if (pprof.is_empty()) { return TTR("Provisioning profile is required for App Store distribution."); } @@ -154,8 +154,8 @@ String EditorExportPlatformMacOS::get_export_option_warning(const EditorExportPr if (notary_tool == 2 || notary_tool == 3) { if (p_name == "notarization/apple_id_name" || p_name == "notarization/api_uuid") { - String apple_id = p_preset->get("notarization/apple_id_name"); - String api_uuid = p_preset->get("notarization/api_uuid"); + String apple_id = p_preset->get_or_env("notarization/apple_id_name", ENV_MAC_NOTARIZATION_APPLE_ID); + String api_uuid = p_preset->get_or_env("notarization/api_uuid", ENV_MAC_NOTARIZATION_UUID); if (apple_id.is_empty() && api_uuid.is_empty()) { return TTR("Neither Apple ID name nor App Store Connect issuer ID name not specified."); } @@ -164,28 +164,28 @@ String EditorExportPlatformMacOS::get_export_option_warning(const EditorExportPr } } if (p_name == "notarization/apple_id_password") { - String apple_id = p_preset->get("notarization/apple_id_name"); - String apple_pass = p_preset->get("notarization/apple_id_password"); + String apple_id = p_preset->get_or_env("notarization/apple_id_name", ENV_MAC_NOTARIZATION_APPLE_ID); + String apple_pass = p_preset->get_or_env("notarization/apple_id_password", ENV_MAC_NOTARIZATION_APPLE_PASS); if (!apple_id.is_empty() && apple_pass.is_empty()) { return TTR("Apple ID password not specified."); } } if (p_name == "notarization/api_key_id") { - String api_uuid = p_preset->get("notarization/api_uuid"); - String api_key = p_preset->get("notarization/api_key_id"); + String api_uuid = p_preset->get_or_env("notarization/api_uuid", ENV_MAC_NOTARIZATION_UUID); + String api_key = p_preset->get_or_env("notarization/api_key_id", ENV_MAC_NOTARIZATION_KEY_ID); if (!api_uuid.is_empty() && api_key.is_empty()) { return TTR("App Store Connect API key ID not specified."); } } } else if (notary_tool == 1) { if (p_name == "notarization/api_uuid") { - String api_uuid = p_preset->get("notarization/api_uuid"); + String api_uuid = p_preset->get_or_env("notarization/api_uuid", ENV_MAC_NOTARIZATION_UUID); if (api_uuid.is_empty()) { return TTR("App Store Connect issuer ID name not specified."); } } if (p_name == "notarization/api_key_id") { - String api_key = p_preset->get("notarization/api_key_id"); + String api_key = p_preset->get_or_env("notarization/api_key_id", ENV_MAC_NOTARIZATION_KEY_ID); if (api_key.is_empty()) { return TTR("App Store Connect API key ID not specified."); } @@ -398,10 +398,10 @@ void EditorExportPlatformMacOS::get_export_options(List<ExportOption> *r_options // "codesign" only options: r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/identity", PROPERTY_HINT_PLACEHOLDER_TEXT, "Type: Name (ID)"), "")); // "rcodesign" only options: - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/certificate_file", PROPERTY_HINT_GLOBAL_FILE, "*.pfx,*.p12"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/certificate_password", PROPERTY_HINT_PASSWORD), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/certificate_file", PROPERTY_HINT_GLOBAL_FILE, "*.pfx,*.p12", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SECRET), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/certificate_password", PROPERTY_HINT_PASSWORD, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SECRET), "")); // "codesign" and "rcodesign" only options: - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/provisioning_profile", PROPERTY_HINT_GLOBAL_FILE, "*.provisionprofile"), "", false, true)); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/provisioning_profile", PROPERTY_HINT_GLOBAL_FILE, "*.provisionprofile", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SECRET), "", false, true)); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/entitlements/custom_file", PROPERTY_HINT_GLOBAL_FILE, "*.plist"), "", true)); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/allow_jit_code_execution"), false)); @@ -434,12 +434,12 @@ void EditorExportPlatformMacOS::get_export_options(List<ExportOption> *r_options r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "notarization/notarization", PROPERTY_HINT_ENUM, "Disabled,rcodesign"), 0, true)); #endif // "altool" and "notarytool" only options: - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "notarization/apple_id_name", PROPERTY_HINT_PLACEHOLDER_TEXT, "Apple ID email"), "", false, true)); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "notarization/apple_id_password", PROPERTY_HINT_PASSWORD, "Enable two-factor authentication and provide app-specific password"), "", false, true)); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "notarization/apple_id_name", PROPERTY_HINT_PLACEHOLDER_TEXT, "Apple ID email", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SECRET), "", false, true)); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "notarization/apple_id_password", PROPERTY_HINT_PASSWORD, "Enable two-factor authentication and provide app-specific password", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SECRET), "", false, true)); // "altool", "notarytool" and "rcodesign" only options: - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "notarization/api_uuid", PROPERTY_HINT_PLACEHOLDER_TEXT, "App Store Connect issuer ID UUID"), "", false, true)); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "notarization/api_key", PROPERTY_HINT_GLOBAL_FILE, "*.p8"), "", false, true)); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "notarization/api_key_id", PROPERTY_HINT_PLACEHOLDER_TEXT, "App Store Connect API key ID"), "", false, true)); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "notarization/api_uuid", PROPERTY_HINT_PLACEHOLDER_TEXT, "App Store Connect issuer ID UUID", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SECRET), "", false, true)); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "notarization/api_key", PROPERTY_HINT_GLOBAL_FILE, "*.p8", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SECRET), "", false, true)); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "notarization/api_key_id", PROPERTY_HINT_PLACEHOLDER_TEXT, "App Store Connect API key ID", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SECRET), "", false, true)); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/microphone_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use the microphone"), "", false, true)); r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/microphone_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary())); @@ -776,24 +776,24 @@ Error EditorExportPlatformMacOS::_notarize(const Ref<EditorExportPreset> &p_pres args.push_back("notary-submit"); - if (p_preset->get("notarization/api_uuid") == "") { + if (p_preset->get_or_env("notarization/api_uuid", ENV_MAC_NOTARIZATION_UUID) == "") { add_message(EXPORT_MESSAGE_ERROR, TTR("Notarization"), TTR("App Store Connect issuer ID name not specified.")); return Error::FAILED; } - if (p_preset->get("notarization/api_key") == "") { + if (p_preset->get_or_env("notarization/api_key", ENV_MAC_NOTARIZATION_KEY) == "") { add_message(EXPORT_MESSAGE_ERROR, TTR("Notarization"), TTR("App Store Connect API key ID not specified.")); return Error::FAILED; } args.push_back("--api-issuer"); - args.push_back(p_preset->get("notarization/api_uuid")); + args.push_back(p_preset->get_or_env("notarization/api_uuid", ENV_MAC_NOTARIZATION_UUID)); args.push_back("--api-key"); - args.push_back(p_preset->get("notarization/api_key_id")); + args.push_back(p_preset->get_or_env("notarization/api_key_id", ENV_MAC_NOTARIZATION_KEY_ID)); - if (!p_preset->get("notarization/api_key").operator String().is_empty()) { + if (!p_preset->get_or_env("notarization/api_key", ENV_MAC_NOTARIZATION_KEY).operator String().is_empty()) { args.push_back("--api-key-path"); - args.push_back(p_preset->get("notarization/api_key")); + args.push_back(p_preset->get_or_env("notarization/api_key", ENV_MAC_NOTARIZATION_KEY)); } args.push_back(p_path); @@ -840,40 +840,40 @@ Error EditorExportPlatformMacOS::_notarize(const Ref<EditorExportPreset> &p_pres args.push_back(p_path); - if (p_preset->get("notarization/apple_id_name") == "" && p_preset->get("notarization/api_uuid") == "") { + if (p_preset->get_or_env("notarization/apple_id_name", ENV_MAC_NOTARIZATION_APPLE_ID) == "" && p_preset->get_or_env("notarization/api_uuid", ENV_MAC_NOTARIZATION_UUID) == "") { add_message(EXPORT_MESSAGE_ERROR, TTR("Notarization"), TTR("Neither Apple ID name nor App Store Connect issuer ID name not specified.")); return Error::FAILED; } - if (p_preset->get("notarization/apple_id_name") != "" && p_preset->get("notarization/api_uuid") != "") { + if (p_preset->get_or_env("notarization/apple_id_name", ENV_MAC_NOTARIZATION_APPLE_ID) != "" && p_preset->get_or_env("notarization/api_uuid", ENV_MAC_NOTARIZATION_UUID) != "") { add_message(EXPORT_MESSAGE_ERROR, TTR("Notarization"), TTR("Both Apple ID name and App Store Connect issuer ID name are specified, only one should be set at the same time.")); return Error::FAILED; } - if (p_preset->get("notarization/apple_id_name") != "") { - if (p_preset->get("notarization/apple_id_password") == "") { + if (p_preset->get_or_env("notarization/apple_id_name", ENV_MAC_NOTARIZATION_APPLE_ID) != "") { + if (p_preset->get_or_env("notarization/apple_id_password", ENV_MAC_NOTARIZATION_APPLE_PASS) == "") { add_message(EXPORT_MESSAGE_ERROR, TTR("Notarization"), TTR("Apple ID password not specified.")); return Error::FAILED; } args.push_back("--apple-id"); - args.push_back(p_preset->get("notarization/apple_id_name")); + args.push_back(p_preset->get_or_env("notarization/apple_id_name", ENV_MAC_NOTARIZATION_APPLE_ID)); args.push_back("--password"); - args.push_back(p_preset->get("notarization/apple_id_password")); + args.push_back(p_preset->get_or_env("notarization/apple_id_password", ENV_MAC_NOTARIZATION_APPLE_PASS)); } else { - if (p_preset->get("notarization/api_key_id") == "") { + if (p_preset->get_or_env("notarization/api_key_id", ENV_MAC_NOTARIZATION_KEY_ID) == "") { add_message(EXPORT_MESSAGE_ERROR, TTR("Notarization"), TTR("App Store Connect API key ID not specified.")); return Error::FAILED; } args.push_back("--issuer"); - args.push_back(p_preset->get("notarization/api_uuid")); + args.push_back(p_preset->get_or_env("notarization/api_uuid", ENV_MAC_NOTARIZATION_UUID)); - if (!p_preset->get("notarization/api_key").operator String().is_empty()) { + if (!p_preset->get_or_env("notarization/api_key", ENV_MAC_NOTARIZATION_KEY).operator String().is_empty()) { args.push_back("--key"); - args.push_back(p_preset->get("notarization/api_key")); + args.push_back(p_preset->get_or_env("notarization/api_key", ENV_MAC_NOTARIZATION_KEY)); } args.push_back("--key-id"); - args.push_back(p_preset->get("notarization/api_key_id")); + args.push_back(p_preset->get_or_env("notarization/api_key_id", ENV_MAC_NOTARIZATION_KEY_ID)); } args.push_back("--no-progress"); @@ -925,35 +925,35 @@ Error EditorExportPlatformMacOS::_notarize(const Ref<EditorExportPreset> &p_pres args.push_back("--primary-bundle-id"); args.push_back(p_preset->get("application/bundle_identifier")); - if (p_preset->get("notarization/apple_id_name") == "" && p_preset->get("notarization/api_uuid") == "") { + if (p_preset->get_or_env("notarization/apple_id_name", ENV_MAC_NOTARIZATION_APPLE_ID) == "" && p_preset->get_or_env("notarization/api_uuid", ENV_MAC_NOTARIZATION_UUID) == "") { add_message(EXPORT_MESSAGE_ERROR, TTR("Notarization"), TTR("Neither Apple ID name nor App Store Connect issuer ID name not specified.")); return Error::FAILED; } - if (p_preset->get("notarization/apple_id_name") != "" && p_preset->get("notarization/api_uuid") != "") { + if (p_preset->get_or_env("notarization/apple_id_name", ENV_MAC_NOTARIZATION_APPLE_ID) != "" && p_preset->get_or_env("notarization/api_uuid", ENV_MAC_NOTARIZATION_UUID) != "") { add_message(EXPORT_MESSAGE_ERROR, TTR("Notarization"), TTR("Both Apple ID name and App Store Connect issuer ID name are specified, only one should be set at the same time.")); return Error::FAILED; } - if (p_preset->get("notarization/apple_id_name") != "") { - if (p_preset->get("notarization/apple_id_password") == "") { + if (p_preset->get_or_env("notarization/apple_id_name", ENV_MAC_NOTARIZATION_APPLE_ID) != "") { + if (p_preset->get_or_env("notarization/apple_id_password", ENV_MAC_NOTARIZATION_APPLE_PASS) == "") { add_message(EXPORT_MESSAGE_ERROR, TTR("Notarization"), TTR("Apple ID password not specified.")); return Error::FAILED; } args.push_back("--username"); - args.push_back(p_preset->get("notarization/apple_id_name")); + args.push_back(p_preset->get_or_env("notarization/apple_id_name", ENV_MAC_NOTARIZATION_APPLE_ID)); args.push_back("--password"); - args.push_back(p_preset->get("notarization/apple_id_password")); + args.push_back(p_preset->get_or_env("notarization/apple_id_password", ENV_MAC_NOTARIZATION_APPLE_PASS)); } else { - if (p_preset->get("notarization/api_key") == "") { + if (p_preset->get_or_env("notarization/api_key", ENV_MAC_NOTARIZATION_KEY) == "") { add_message(EXPORT_MESSAGE_ERROR, TTR("Notarization"), TTR("App Store Connect API key ID not specified.")); return Error::FAILED; } args.push_back("--apiIssuer"); - args.push_back(p_preset->get("notarization/api_uuid")); + args.push_back(p_preset->get_or_env("notarization/api_uuid", ENV_MAC_NOTARIZATION_UUID)); args.push_back("--apiKey"); - args.push_back(p_preset->get("notarization/api_key_id")); + args.push_back(p_preset->get_or_env("notarization/api_key_id", ENV_MAC_NOTARIZATION_KEY_ID)); } args.push_back("--type"); @@ -1032,8 +1032,8 @@ Error EditorExportPlatformMacOS::_code_sign(const Ref<EditorExportPreset> &p_pre args.push_back(p_ent_path); } - String certificate_file = p_preset->get("codesign/certificate_file"); - String certificate_pass = p_preset->get("codesign/certificate_password"); + String certificate_file = p_preset->get_or_env("codesign/certificate_file", ENV_MAC_CODESIGN_CERT_FILE); + String certificate_pass = p_preset->get_or_env("codesign/certificate_password", ENV_MAC_CODESIGN_CERT_PASS); if (!certificate_file.is_empty() && !certificate_pass.is_empty()) { args.push_back("--p12-file"); args.push_back(certificate_file); @@ -1763,7 +1763,7 @@ Error EditorExportPlatformMacOS::export_project(const Ref<EditorExportPreset> &p ad_hoc = true; } break; case 2: { // "rcodesign" - ad_hoc = p_preset->get("codesign/certificate_file").operator String().is_empty() || p_preset->get("codesign/certificate_password").operator String().is_empty(); + ad_hoc = p_preset->get_or_env("codesign/certificate_file", ENV_MAC_CODESIGN_CERT_FILE).operator String().is_empty() || p_preset->get_or_env("codesign/certificate_password", ENV_MAC_CODESIGN_CERT_PASS).operator String().is_empty(); } break; #ifdef MACOS_ENABLED case 3: { // "codesign" @@ -1857,7 +1857,7 @@ Error EditorExportPlatformMacOS::export_project(const Ref<EditorExportPreset> &p int dist_type = p_preset->get("export/distribution_type"); if (dist_type == 2) { - String pprof = p_preset->get("codesign/provisioning_profile"); + String pprof = p_preset->get_or_env("codesign/provisioning_profile", ENV_MAC_CODESIGN_PROFILE); String teamid = p_preset->get("codesign/apple_team_id"); String bid = p_preset->get("application/bundle_identifier"); if (!pprof.is_empty() && !teamid.is_empty()) { @@ -1990,7 +1990,7 @@ Error EditorExportPlatformMacOS::export_project(const Ref<EditorExportPreset> &p if (err == OK && sign_enabled) { int dist_type = p_preset->get("export/distribution_type"); if (dist_type == 2) { - String pprof = p_preset->get("codesign/provisioning_profile").operator String(); + String pprof = p_preset->get_or_env("codesign/provisioning_profile", ENV_MAC_CODESIGN_PROFILE).operator String(); if (!pprof.is_empty()) { Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); err = da->copy(pprof, tmp_app_path_name + "/Contents/embedded.provisionprofile"); @@ -2147,7 +2147,7 @@ bool EditorExportPlatformMacOS::has_valid_project_configuration(const Ref<Editor ad_hoc = true; } break; case 2: { // "rcodesign" - ad_hoc = p_preset->get("codesign/certificate_file").operator String().is_empty() || p_preset->get("codesign/certificate_password").operator String().is_empty(); + ad_hoc = p_preset->get_or_env("codesign/certificate_file", ENV_MAC_CODESIGN_CERT_FILE).operator String().is_empty() || p_preset->get_or_env("codesign/certificate_password", ENV_MAC_CODESIGN_CERT_PASS).operator String().is_empty(); } break; #ifdef MACOS_ENABLED case 3: { // "codesign" diff --git a/platform/macos/export/export_plugin.h b/platform/macos/export/export_plugin.h index a8caf535c4..0477a8c0cc 100644 --- a/platform/macos/export/export_plugin.h +++ b/platform/macos/export/export_plugin.h @@ -43,6 +43,17 @@ #include <sys/stat.h> +// Optional environment variables for defining confidential information. If any +// of these is set, they will override the values set in the credentials file. +const String ENV_MAC_CODESIGN_CERT_FILE = "GODOT_MACOS_CODESIGN_CERTIFICATE_FILE"; +const String ENV_MAC_CODESIGN_CERT_PASS = "GODOT_MACOS_CODESIGN_CERTIFICATE_PASSWORD"; +const String ENV_MAC_CODESIGN_PROFILE = "GODOT_MACOS_CODESIGN_PROVISIONING_PROFILE"; +const String ENV_MAC_NOTARIZATION_UUID = "GODOT_MACOS_NOTARIZATION_API_UUID"; +const String ENV_MAC_NOTARIZATION_KEY = "GODOT_MACOS_NOTARIZATION_API_KEY"; +const String ENV_MAC_NOTARIZATION_KEY_ID = "GODOT_MACOS_NOTARIZATION_API_KEY_ID"; +const String ENV_MAC_NOTARIZATION_APPLE_ID = "GODOT_MACOS_NOTARIZATION_APPLE_ID_NAME"; +const String ENV_MAC_NOTARIZATION_APPLE_PASS = "GODOT_MACOS_NOTARIZATION_APPLE_ID_PASSWORD"; + class EditorExportPlatformMacOS : public EditorExportPlatform { GDCLASS(EditorExportPlatformMacOS, EditorExportPlatform); diff --git a/platform/uwp/export/export_plugin.cpp b/platform/uwp/export/export_plugin.cpp index 163236e506..aac61184b1 100644 --- a/platform/uwp/export/export_plugin.cpp +++ b/platform/uwp/export/export_plugin.cpp @@ -80,8 +80,8 @@ void EditorExportPlatformUWP::get_export_options(List<ExportOption> *r_options) r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "identity/product_guid", PROPERTY_HINT_PLACEHOLDER_TEXT, "00000000-0000-0000-0000-000000000000"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "identity/publisher_guid", PROPERTY_HINT_PLACEHOLDER_TEXT, "00000000-0000-0000-0000-000000000000"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "signing/certificate", PROPERTY_HINT_GLOBAL_FILE, "*.pfx"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "signing/password"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "signing/certificate", PROPERTY_HINT_GLOBAL_FILE, "*.pfx", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SECRET), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "signing/password", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SECRET), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "signing/algorithm", PROPERTY_HINT_ENUM, "MD5,SHA1,SHA256"), 2)); r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "version/major"), 1)); @@ -465,8 +465,8 @@ Error EditorExportPlatformUWP::export_project(const Ref<EditorExportPreset> &p_p int cert_alg = EDITOR_GET("export/uwp/debug_algorithm"); if (!p_debug) { - cert_path = p_preset->get("signing/certificate"); - cert_pass = p_preset->get("signing/password"); + cert_path = p_preset->get_or_env("signing/certificate", ENV_UWP_SIGNING_CERT); + cert_pass = p_preset->get_or_env("signing/password", ENV_UWP_SIGNING_PASS); cert_alg = p_preset->get("signing/algorithm"); } diff --git a/platform/uwp/export/export_plugin.h b/platform/uwp/export/export_plugin.h index 37a32b1f7f..b42a2ae6d9 100644 --- a/platform/uwp/export/export_plugin.h +++ b/platform/uwp/export/export_plugin.h @@ -85,6 +85,11 @@ static const char *uwp_device_capabilities[] = { nullptr }; +// Optional environment variables for defining confidential information. If any +// of these is set, they will override the values set in the credentials file. +const String ENV_UWP_SIGNING_CERT = "GODOT_UWP_SIGNING_CERTIFICATE"; +const String ENV_UWP_SIGNING_PASS = "GODOT_UWP_SIGNING_PASSWORD"; + class EditorExportPlatformUWP : public EditorExportPlatform { GDCLASS(EditorExportPlatformUWP, EditorExportPlatform); diff --git a/platform/web/display_server_web.h b/platform/web/display_server_web.h index 2e50a6bbc8..a72977e3c3 100644 --- a/platform/web/display_server_web.h +++ b/platform/web/display_server_web.h @@ -37,6 +37,8 @@ #include <emscripten/html5.h> class DisplayServerWeb : public DisplayServer { + // No need to register with GDCLASS, it's platform-specific and nothing is added. + private: struct JSTouchEvent { uint32_t identifier[32] = { 0 }; diff --git a/platform/windows/display_server_windows.h b/platform/windows/display_server_windows.h index 80c75c63b5..a8dcc6bad0 100644 --- a/platform/windows/display_server_windows.h +++ b/platform/windows/display_server_windows.h @@ -280,8 +280,7 @@ typedef struct { } ICONDIR, *LPICONDIR; class DisplayServerWindows : public DisplayServer { - //No need to register, it's platform-specific and nothing is added - //GDCLASS(DisplayServerWindows, DisplayServer) + // No need to register with GDCLASS, it's platform-specific and nothing is added. _THREAD_SAFE_CLASS_ diff --git a/platform/windows/doc_classes/EditorExportPlatformWindows.xml b/platform/windows/doc_classes/EditorExportPlatformWindows.xml index fee8a118bc..ec2b105f58 100644 --- a/platform/windows/doc_classes/EditorExportPlatformWindows.xml +++ b/platform/windows/doc_classes/EditorExportPlatformWindows.xml @@ -64,12 +64,15 @@ </member> <member name="codesign/identity" type="String" setter="" getter=""> PKCS #12 certificate file used to sign executable or certificate SHA-1 hash (if [member codesign/identity_type] is set to "Use certificate store"). See [url=https://learn.microsoft.com/en-us/dotnet/framework/tools/signtool-exe]Sign Tool[/url]. + Can be overridden with the environment variable [code]GODOT_WINDOWS_CODESIGN_IDENTITY[/code]. </member> <member name="codesign/identity_type" type="int" setter="" getter=""> Type of identity to use. See [url=https://learn.microsoft.com/en-us/dotnet/framework/tools/signtool-exe]Sign Tool[/url]. + Can be overridden with the environment variable [code]GODOT_WINDOWS_CODESIGN_IDENTITY_TYPE[/code]. </member> <member name="codesign/password" type="String" setter="" getter=""> Password for the certificate file used to sign executable. See [url=https://learn.microsoft.com/en-us/dotnet/framework/tools/signtool-exe]Sign Tool[/url]. + Can be overridden with the environment variable [code]GODOT_WINDOWS_CODESIGN_PASSWORD[/code]. </member> <member name="codesign/timestamp" type="bool" setter="" getter=""> If [code]true[/code], time-stamp is added to the signature. See [url=https://learn.microsoft.com/en-us/dotnet/framework/tools/signtool-exe]Sign Tool[/url]. diff --git a/platform/windows/export/export_plugin.cpp b/platform/windows/export/export_plugin.cpp index 1863a3083b..ca390236fb 100644 --- a/platform/windows/export/export_plugin.cpp +++ b/platform/windows/export/export_plugin.cpp @@ -328,9 +328,9 @@ void EditorExportPlatformWindows::get_export_options(List<ExportOption> *r_optio r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "binary_format/architecture", PROPERTY_HINT_ENUM, "x86_64,x86_32,arm64"), "x86_64")); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/enable"), false, true)); - r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "codesign/identity_type", PROPERTY_HINT_ENUM, "Select automatically,Use PKCS12 file (specify *.PFX/*.P12 file),Use certificate store (specify SHA-1 hash)"), 0)); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/identity", PROPERTY_HINT_GLOBAL_FILE, "*.pfx,*.p12"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/password", PROPERTY_HINT_PASSWORD), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "codesign/identity_type", PROPERTY_HINT_ENUM, "Select automatically,Use PKCS12 file (specify *.PFX/*.P12 file),Use certificate store (specify SHA-1 hash)", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SECRET), 0)); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/identity", PROPERTY_HINT_GLOBAL_FILE, "*.pfx,*.p12", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SECRET), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/password", PROPERTY_HINT_PASSWORD, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SECRET), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/timestamp"), true)); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/timestamp_server_url"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "codesign/digest_algorithm", PROPERTY_HINT_ENUM, "SHA1,SHA256"), 1)); @@ -518,21 +518,21 @@ Error EditorExportPlatformWindows::_code_sign(const Ref<EditorExportPreset> &p_p //identity #ifdef WINDOWS_ENABLED - int id_type = p_preset->get("codesign/identity_type"); + int id_type = p_preset->get_or_env("codesign/identity_type", ENV_WIN_CODESIGN_ID_TYPE); if (id_type == 0) { //auto select args.push_back("/a"); } else if (id_type == 1) { //pkcs12 - if (p_preset->get("codesign/identity") != "") { + if (p_preset->get_or_env("codesign/identity", ENV_WIN_CODESIGN_ID) != "") { args.push_back("/f"); - args.push_back(p_preset->get("codesign/identity")); + args.push_back(p_preset->get_or_env("codesign/identity", ENV_WIN_CODESIGN_ID)); } else { add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), TTR("No identity found.")); return FAILED; } } else if (id_type == 2) { //Windows certificate store - if (p_preset->get("codesign/identity") != "") { + if (p_preset->get_or_env("codesign/identity", ENV_WIN_CODESIGN_ID) != "") { args.push_back("/sha1"); - args.push_back(p_preset->get("codesign/identity")); + args.push_back(p_preset->get_or_env("codesign/identity", ENV_WIN_CODESIGN_ID)); } else { add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), TTR("No identity found.")); return FAILED; @@ -543,9 +543,9 @@ Error EditorExportPlatformWindows::_code_sign(const Ref<EditorExportPreset> &p_p } #else int id_type = 1; - if (p_preset->get("codesign/identity") != "") { + if (p_preset->get_or_env("codesign/identity", ENV_WIN_CODESIGN_ID) != "") { args.push_back("-pkcs12"); - args.push_back(p_preset->get("codesign/identity")); + args.push_back(p_preset->get_or_env("codesign/identity", ENV_WIN_CODESIGN_ID)); } else { add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), TTR("No identity found.")); return FAILED; @@ -553,13 +553,13 @@ Error EditorExportPlatformWindows::_code_sign(const Ref<EditorExportPreset> &p_p #endif //password - if ((id_type == 1) && (p_preset->get("codesign/password") != "")) { + if ((id_type == 1) && (p_preset->get_or_env("codesign/password", ENV_WIN_CODESIGN_PASS) != "")) { #ifdef WINDOWS_ENABLED args.push_back("/p"); #else args.push_back("-pass"); #endif - args.push_back(p_preset->get("codesign/password")); + args.push_back(p_preset->get_or_env("codesign/password", ENV_WIN_CODESIGN_PASS)); } //timestamp diff --git a/platform/windows/export/export_plugin.h b/platform/windows/export/export_plugin.h index c466971202..184b2f96f8 100644 --- a/platform/windows/export/export_plugin.h +++ b/platform/windows/export/export_plugin.h @@ -36,6 +36,12 @@ #include "editor/editor_settings.h" #include "editor/export/editor_export_platform_pc.h" +// Optional environment variables for defining confidential information. If any +// of these is set, they will override the values set in the credentials file. +const String ENV_WIN_CODESIGN_ID_TYPE = "GODOT_WINDOWS_CODESIGN_IDENTITY_TYPE"; +const String ENV_WIN_CODESIGN_ID = "GODOT_WINDOWS_CODESIGN_IDENTITY"; +const String ENV_WIN_CODESIGN_PASS = "GODOT_WINDOWS_CODESIGN_PASSWORD"; + class EditorExportPlatformWindows : public EditorExportPlatformPC { GDCLASS(EditorExportPlatformWindows, EditorExportPlatformPC); diff --git a/scene/2d/navigation_agent_2d.cpp b/scene/2d/navigation_agent_2d.cpp index d101cac4eb..936244fdb1 100644 --- a/scene/2d/navigation_agent_2d.cpp +++ b/scene/2d/navigation_agent_2d.cpp @@ -56,8 +56,11 @@ void NavigationAgent2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_max_neighbors", "max_neighbors"), &NavigationAgent2D::set_max_neighbors); ClassDB::bind_method(D_METHOD("get_max_neighbors"), &NavigationAgent2D::get_max_neighbors); - ClassDB::bind_method(D_METHOD("set_time_horizon", "time_horizon"), &NavigationAgent2D::set_time_horizon); - ClassDB::bind_method(D_METHOD("get_time_horizon"), &NavigationAgent2D::get_time_horizon); + ClassDB::bind_method(D_METHOD("set_time_horizon_agents", "time_horizon"), &NavigationAgent2D::set_time_horizon_agents); + ClassDB::bind_method(D_METHOD("get_time_horizon_agents"), &NavigationAgent2D::get_time_horizon_agents); + + ClassDB::bind_method(D_METHOD("set_time_horizon_obstacles", "time_horizon"), &NavigationAgent2D::set_time_horizon_obstacles); + ClassDB::bind_method(D_METHOD("get_time_horizon_obstacles"), &NavigationAgent2D::get_time_horizon_obstacles); ClassDB::bind_method(D_METHOD("set_max_speed", "max_speed"), &NavigationAgent2D::set_max_speed); ClassDB::bind_method(D_METHOD("get_max_speed"), &NavigationAgent2D::get_max_speed); @@ -87,9 +90,16 @@ void NavigationAgent2D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_target_position"), &NavigationAgent2D::get_target_position); ClassDB::bind_method(D_METHOD("get_next_path_position"), &NavigationAgent2D::get_next_path_position); - ClassDB::bind_method(D_METHOD("distance_to_target"), &NavigationAgent2D::distance_to_target); + + ClassDB::bind_method(D_METHOD("set_velocity_forced", "velocity"), &NavigationAgent2D::set_velocity_forced); + ClassDB::bind_method(D_METHOD("set_velocity", "velocity"), &NavigationAgent2D::set_velocity); + ClassDB::bind_method(D_METHOD("get_velocity"), &NavigationAgent2D::get_velocity); + + ClassDB::bind_method(D_METHOD("distance_to_target"), &NavigationAgent2D::distance_to_target); + ClassDB::bind_method(D_METHOD("get_current_navigation_result"), &NavigationAgent2D::get_current_navigation_result); + ClassDB::bind_method(D_METHOD("get_current_navigation_path"), &NavigationAgent2D::get_current_navigation_path); ClassDB::bind_method(D_METHOD("get_current_navigation_path_index"), &NavigationAgent2D::get_current_navigation_path_index); ClassDB::bind_method(D_METHOD("is_target_reached"), &NavigationAgent2D::is_target_reached); @@ -99,6 +109,17 @@ void NavigationAgent2D::_bind_methods() { ClassDB::bind_method(D_METHOD("_avoidance_done", "new_velocity"), &NavigationAgent2D::_avoidance_done); + ClassDB::bind_method(D_METHOD("set_avoidance_layers", "layers"), &NavigationAgent2D::set_avoidance_layers); + ClassDB::bind_method(D_METHOD("get_avoidance_layers"), &NavigationAgent2D::get_avoidance_layers); + ClassDB::bind_method(D_METHOD("set_avoidance_mask", "mask"), &NavigationAgent2D::set_avoidance_mask); + ClassDB::bind_method(D_METHOD("get_avoidance_mask"), &NavigationAgent2D::get_avoidance_mask); + ClassDB::bind_method(D_METHOD("set_avoidance_layer_value", "layer_number", "value"), &NavigationAgent2D::set_avoidance_layer_value); + ClassDB::bind_method(D_METHOD("get_avoidance_layer_value", "layer_number"), &NavigationAgent2D::get_avoidance_layer_value); + ClassDB::bind_method(D_METHOD("set_avoidance_mask_value", "mask_number", "value"), &NavigationAgent2D::set_avoidance_mask_value); + ClassDB::bind_method(D_METHOD("get_avoidance_mask_value", "mask_number"), &NavigationAgent2D::get_avoidance_mask_value); + ClassDB::bind_method(D_METHOD("set_avoidance_priority", "priority"), &NavigationAgent2D::set_avoidance_priority); + ClassDB::bind_method(D_METHOD("get_avoidance_priority"), &NavigationAgent2D::get_avoidance_priority); + ADD_GROUP("Pathfinding", ""); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "target_position", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_target_position", "get_target_position"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "path_desired_distance", PROPERTY_HINT_RANGE, "0.1,1000,0.01,or_greater,suffix:px"), "set_path_desired_distance", "get_path_desired_distance"); @@ -111,11 +132,16 @@ void NavigationAgent2D::_bind_methods() { ADD_GROUP("Avoidance", ""); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "avoidance_enabled"), "set_avoidance_enabled", "get_avoidance_enabled"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0.1,500,0.01,or_greater,suffix:px"), "set_radius", "get_radius"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "velocity", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_velocity", "get_velocity"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0.01,500,0.01,or_greater,suffix:px"), "set_radius", "get_radius"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "neighbor_distance", PROPERTY_HINT_RANGE, "0.1,100000,0.01,or_greater,suffix:px"), "set_neighbor_distance", "get_neighbor_distance"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "max_neighbors", PROPERTY_HINT_RANGE, "1,10000,or_greater,1"), "set_max_neighbors", "get_max_neighbors"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "time_horizon", PROPERTY_HINT_RANGE, "0.1,10,0.01,or_greater,suffix:s"), "set_time_horizon", "get_time_horizon"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "max_speed", PROPERTY_HINT_RANGE, "0.1,10000,0.01,or_greater,suffix:px/s"), "set_max_speed", "get_max_speed"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "max_neighbors", PROPERTY_HINT_RANGE, "1,10000,1,or_greater"), "set_max_neighbors", "get_max_neighbors"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "time_horizon_agents", PROPERTY_HINT_RANGE, "0.0,10,0.01,or_greater,suffix:s"), "set_time_horizon_agents", "get_time_horizon_agents"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "time_horizon_obstacles", PROPERTY_HINT_RANGE, "0.0,10,0.01,or_greater,suffix:s"), "set_time_horizon_obstacles", "get_time_horizon_obstacles"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "max_speed", PROPERTY_HINT_RANGE, "0.01,100000,0.01,or_greater,suffix:px/s"), "set_max_speed", "get_max_speed"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "avoidance_layers", PROPERTY_HINT_LAYERS_AVOIDANCE), "set_avoidance_layers", "get_avoidance_layers"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "avoidance_mask", PROPERTY_HINT_LAYERS_AVOIDANCE), "set_avoidance_mask", "get_avoidance_mask"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "avoidance_priority", PROPERTY_HINT_RANGE, "0.0,1.0,0.01"), "set_avoidance_priority", "get_avoidance_priority"); ClassDB::bind_method(D_METHOD("set_debug_enabled", "enabled"), &NavigationAgent2D::set_debug_enabled); ClassDB::bind_method(D_METHOD("get_debug_enabled"), &NavigationAgent2D::get_debug_enabled); @@ -143,6 +169,34 @@ void NavigationAgent2D::_bind_methods() { ADD_SIGNAL(MethodInfo("velocity_computed", PropertyInfo(Variant::VECTOR2, "safe_velocity"))); } +#ifndef DISABLE_DEPRECATED +// Compatibility with Godot 4.0 beta 10 or below. +// Functions in block below all renamed or replaced in 4.0 beta 1X avoidance rework. +bool NavigationAgent2D::_set(const StringName &p_name, const Variant &p_value) { + if (p_name == "time_horizon") { + set_time_horizon_agents(p_value); + return true; + } + if (p_name == "target_location") { + set_target_position(p_value); + return true; + } + return false; +} + +bool NavigationAgent2D::_get(const StringName &p_name, Variant &r_ret) const { + if (p_name == "time_horizon") { + r_ret = get_time_horizon_agents(); + return true; + } + if (p_name == "target_location") { + r_ret = get_target_position(); + return true; + } + return false; +} +#endif // DISABLE_DEPRECATED + void NavigationAgent2D::_notification(int p_what) { switch (p_what) { case NOTIFICATION_POST_ENTER_TREE: { @@ -151,11 +205,16 @@ void NavigationAgent2D::_notification(int p_what) { set_agent_parent(get_parent()); set_physics_process_internal(true); + if (agent_parent && avoidance_enabled) { + NavigationServer2D::get_singleton()->agent_set_position(agent, agent_parent->get_global_position()); + } + #ifdef DEBUG_ENABLED if (NavigationServer2D::get_singleton()->get_debug_enabled()) { debug_path_dirty = true; } #endif // DEBUG_ENABLED + } break; case NOTIFICATION_PARENTED: { @@ -208,10 +267,18 @@ void NavigationAgent2D::_notification(int p_what) { case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { if (agent_parent && target_position_submitted) { - if (avoidance_enabled) { - // agent_position on NavigationServer is avoidance only and has nothing to do with pathfinding - // no point in flooding NavigationServer queue with agent position updates that get send to the void if avoidance is not used - NavigationServer2D::get_singleton()->agent_set_position(agent, agent_parent->get_global_position()); + if (velocity_submitted) { + velocity_submitted = false; + if (avoidance_enabled) { + NavigationServer2D::get_singleton()->agent_set_position(agent, agent_parent->get_global_position()); + NavigationServer2D::get_singleton()->agent_set_velocity(agent, velocity); + } + } + if (velocity_forced_submitted) { + velocity_forced_submitted = false; + if (avoidance_enabled) { + NavigationServer2D::get_singleton()->agent_set_velocity_forced(agent, velocity_forced); + } } _check_distance_to_target(); } @@ -226,9 +293,11 @@ void NavigationAgent2D::_notification(int p_what) { NavigationAgent2D::NavigationAgent2D() { agent = NavigationServer2D::get_singleton()->agent_create(); + NavigationServer2D::get_singleton()->agent_set_neighbor_distance(agent, neighbor_distance); NavigationServer2D::get_singleton()->agent_set_max_neighbors(agent, max_neighbors); - NavigationServer2D::get_singleton()->agent_set_time_horizon(agent, time_horizon); + NavigationServer2D::get_singleton()->agent_set_time_horizon_agents(agent, time_horizon_agents); + NavigationServer2D::get_singleton()->agent_set_time_horizon_obstacles(agent, time_horizon_obstacles); NavigationServer2D::get_singleton()->agent_set_radius(agent, radius); NavigationServer2D::get_singleton()->agent_set_max_speed(agent, max_speed); @@ -239,6 +308,11 @@ NavigationAgent2D::NavigationAgent2D() { navigation_result = Ref<NavigationPathQueryResult2D>(); navigation_result.instantiate(); + set_avoidance_layers(avoidance_layers); + set_avoidance_mask(avoidance_mask); + set_avoidance_priority(avoidance_priority); + set_avoidance_enabled(avoidance_enabled); + #ifdef DEBUG_ENABLED NavigationServer2D::get_singleton()->connect(SNAME("navigation_debug_changed"), callable_mp(this, &NavigationAgent2D::_navigation_debug_changed)); #endif // DEBUG_ENABLED @@ -267,9 +341,11 @@ void NavigationAgent2D::set_avoidance_enabled(bool p_enabled) { avoidance_enabled = p_enabled; if (avoidance_enabled) { - NavigationServer2D::get_singleton()->agent_set_callback(agent, callable_mp(this, &NavigationAgent2D::_avoidance_done)); + NavigationServer2D::get_singleton()->agent_set_avoidance_enabled(agent, true); + NavigationServer2D::get_singleton()->agent_set_avoidance_callback(agent, callable_mp(this, &NavigationAgent2D::_avoidance_done)); } else { - NavigationServer2D::get_singleton()->agent_set_callback(agent, Callable()); + NavigationServer2D::get_singleton()->agent_set_avoidance_enabled(agent, false); + NavigationServer2D::get_singleton()->agent_set_avoidance_callback(agent, Callable()); } } @@ -283,7 +359,7 @@ void NavigationAgent2D::set_agent_parent(Node *p_agent_parent) { } // remove agent from any avoidance map before changing parent or there will be leftovers on the RVO map - NavigationServer2D::get_singleton()->agent_set_callback(agent, Callable()); + NavigationServer2D::get_singleton()->agent_set_avoidance_callback(agent, Callable()); if (Object::cast_to<Node2D>(p_agent_parent) != nullptr) { // place agent on navigation map first or else the RVO agent callback creation fails silently later @@ -296,7 +372,7 @@ void NavigationAgent2D::set_agent_parent(Node *p_agent_parent) { // create new avoidance callback if enabled if (avoidance_enabled) { - NavigationServer2D::get_singleton()->agent_set_callback(agent, callable_mp(this, &NavigationAgent2D::_avoidance_done)); + NavigationServer2D::get_singleton()->agent_set_avoidance_callback(agent, callable_mp(this, &NavigationAgent2D::_avoidance_done)); } } else { agent_parent = nullptr; @@ -401,6 +477,7 @@ void NavigationAgent2D::set_target_desired_distance(real_t p_target_desired_dist } void NavigationAgent2D::set_radius(real_t p_radius) { + ERR_FAIL_COND_MSG(p_radius < 0.0, "Radius must be positive."); if (Math::is_equal_approx(radius, p_radius)) { return; } @@ -430,21 +507,29 @@ void NavigationAgent2D::set_max_neighbors(int p_count) { NavigationServer2D::get_singleton()->agent_set_max_neighbors(agent, max_neighbors); } -void NavigationAgent2D::set_time_horizon(real_t p_time) { - if (Math::is_equal_approx(time_horizon, p_time)) { +void NavigationAgent2D::set_time_horizon_agents(real_t p_time_horizon) { + ERR_FAIL_COND_MSG(p_time_horizon < 0.0, "Time horizion must be positive."); + if (Math::is_equal_approx(time_horizon_agents, p_time_horizon)) { return; } + time_horizon_agents = p_time_horizon; + NavigationServer2D::get_singleton()->agent_set_time_horizon_agents(agent, time_horizon_agents); +} - time_horizon = p_time; - - NavigationServer2D::get_singleton()->agent_set_time_horizon(agent, time_horizon); +void NavigationAgent2D::set_time_horizon_obstacles(real_t p_time_horizon) { + ERR_FAIL_COND_MSG(p_time_horizon < 0.0, "Time horizion must be positive."); + if (Math::is_equal_approx(time_horizon_obstacles, p_time_horizon)) { + return; + } + time_horizon_obstacles = p_time_horizon; + NavigationServer2D::get_singleton()->agent_set_time_horizon_obstacles(agent, time_horizon_obstacles); } void NavigationAgent2D::set_max_speed(real_t p_max_speed) { + ERR_FAIL_COND_MSG(p_max_speed < 0.0, "Max speed must be positive."); if (Math::is_equal_approx(max_speed, p_max_speed)) { return; } - max_speed = p_max_speed; NavigationServer2D::get_singleton()->agent_set_max_speed(agent, max_speed); @@ -516,29 +601,24 @@ Vector2 NavigationAgent2D::get_final_position() { return navigation_path[navigation_path.size() - 1]; } -void NavigationAgent2D::set_velocity(Vector2 p_velocity) { +void NavigationAgent2D::set_velocity_forced(Vector2 p_velocity) { // Intentionally not checking for equality of the parameter. // We need to always submit the velocity to the navigation server, even when it is the same, in order to run avoidance every frame. // Revisit later when the navigation server can update avoidance without users resubmitting the velocity. - target_velocity = p_velocity; - velocity_submitted = true; + velocity_forced = p_velocity; + velocity_forced_submitted = true; +} - NavigationServer2D::get_singleton()->agent_set_target_velocity(agent, target_velocity); - NavigationServer2D::get_singleton()->agent_set_velocity(agent, prev_safe_velocity); +void NavigationAgent2D::set_velocity(const Vector2 p_velocity) { + velocity = p_velocity; + velocity_submitted = true; } void NavigationAgent2D::_avoidance_done(Vector3 p_new_velocity) { - const Vector2 velocity = Vector2(p_new_velocity.x, p_new_velocity.z); - prev_safe_velocity = velocity; - - if (!velocity_submitted) { - target_velocity = Vector2(); - return; - } - velocity_submitted = false; - - emit_signal(SNAME("velocity_computed"), velocity); + const Vector2 new_safe_velocity = Vector2(p_new_velocity.x, p_new_velocity.z); + safe_velocity = new_safe_velocity; + emit_signal(SNAME("velocity_computed"), safe_velocity); } PackedStringArray NavigationAgent2D::get_configuration_warnings() const { @@ -561,9 +641,6 @@ void NavigationAgent2D::update_navigation() { if (!target_position_submitted) { return; } - if (update_frame_id == Engine::get_singleton()->get_physics_frames()) { - return; - } update_frame_id = Engine::get_singleton()->get_physics_frames(); @@ -709,6 +786,71 @@ void NavigationAgent2D::_check_distance_to_target() { } } +void NavigationAgent2D::set_avoidance_layers(uint32_t p_layers) { + avoidance_layers = p_layers; + NavigationServer2D::get_singleton()->agent_set_avoidance_layers(get_rid(), avoidance_layers); +} + +uint32_t NavigationAgent2D::get_avoidance_layers() const { + return avoidance_layers; +} + +void NavigationAgent2D::set_avoidance_mask(uint32_t p_mask) { + avoidance_mask = p_mask; + NavigationServer2D::get_singleton()->agent_set_avoidance_mask(get_rid(), p_mask); +} + +uint32_t NavigationAgent2D::get_avoidance_mask() const { + return avoidance_mask; +} + +void NavigationAgent2D::set_avoidance_layer_value(int p_layer_number, bool p_value) { + ERR_FAIL_COND_MSG(p_layer_number < 1, "Avoidance layer number must be between 1 and 32 inclusive."); + ERR_FAIL_COND_MSG(p_layer_number > 32, "Avoidance layer number must be between 1 and 32 inclusive."); + uint32_t avoidance_layers_new = get_avoidance_layers(); + if (p_value) { + avoidance_layers_new |= 1 << (p_layer_number - 1); + } else { + avoidance_layers_new &= ~(1 << (p_layer_number - 1)); + } + set_avoidance_layers(avoidance_layers_new); +} + +bool NavigationAgent2D::get_avoidance_layer_value(int p_layer_number) const { + ERR_FAIL_COND_V_MSG(p_layer_number < 1, false, "Avoidance layer number must be between 1 and 32 inclusive."); + ERR_FAIL_COND_V_MSG(p_layer_number > 32, false, "Avoidance layer number must be between 1 and 32 inclusive."); + return get_avoidance_layers() & (1 << (p_layer_number - 1)); +} + +void NavigationAgent2D::set_avoidance_mask_value(int p_mask_number, bool p_value) { + ERR_FAIL_COND_MSG(p_mask_number < 1, "Avoidance mask number must be between 1 and 32 inclusive."); + ERR_FAIL_COND_MSG(p_mask_number > 32, "Avoidance mask number must be between 1 and 32 inclusive."); + uint32_t mask = get_avoidance_mask(); + if (p_value) { + mask |= 1 << (p_mask_number - 1); + } else { + mask &= ~(1 << (p_mask_number - 1)); + } + set_avoidance_mask(mask); +} + +bool NavigationAgent2D::get_avoidance_mask_value(int p_mask_number) const { + ERR_FAIL_COND_V_MSG(p_mask_number < 1, false, "Avoidance mask number must be between 1 and 32 inclusive."); + ERR_FAIL_COND_V_MSG(p_mask_number > 32, false, "Avoidance mask number must be between 1 and 32 inclusive."); + return get_avoidance_mask() & (1 << (p_mask_number - 1)); +} + +void NavigationAgent2D::set_avoidance_priority(real_t p_priority) { + ERR_FAIL_COND_MSG(p_priority < 0.0, "Avoidance priority must be between 0.0 and 1.0 inclusive."); + ERR_FAIL_COND_MSG(p_priority > 1.0, "Avoidance priority must be between 0.0 and 1.0 inclusive."); + avoidance_priority = p_priority; + NavigationServer2D::get_singleton()->agent_set_avoidance_priority(get_rid(), p_priority); +} + +real_t NavigationAgent2D::get_avoidance_priority() const { + return avoidance_priority; +} + ////////DEBUG//////////////////////////////////////////////////////////// void NavigationAgent2D::set_debug_enabled(bool p_enabled) { diff --git a/scene/2d/navigation_agent_2d.h b/scene/2d/navigation_agent_2d.h index b7febba89d..ca43abf833 100644 --- a/scene/2d/navigation_agent_2d.h +++ b/scene/2d/navigation_agent_2d.h @@ -47,6 +47,9 @@ class NavigationAgent2D : public Node { RID map_override; bool avoidance_enabled = false; + uint32_t avoidance_layers = 1; + uint32_t avoidance_mask = 1; + real_t avoidance_priority = 1.0; uint32_t navigation_layers = 1; NavigationPathQueryParameters2D::PathfindingAlgorithm pathfinding_algorithm = NavigationPathQueryParameters2D::PathfindingAlgorithm::PATHFINDING_ALGORITHM_ASTAR; NavigationPathQueryParameters2D::PathPostProcessing path_postprocessing = NavigationPathQueryParameters2D::PathPostProcessing::PATH_POSTPROCESSING_CORRIDORFUNNEL; @@ -57,19 +60,33 @@ class NavigationAgent2D : public Node { real_t radius = 10.0; real_t neighbor_distance = 500.0; int max_neighbors = 10; - real_t time_horizon = 1.0; + real_t time_horizon_agents = 1.0; + real_t time_horizon_obstacles = 0.0; real_t max_speed = 100.0; real_t path_max_distance = 100.0; Vector2 target_position; - bool target_position_submitted = false; + Ref<NavigationPathQueryParameters2D> navigation_query; Ref<NavigationPathQueryResult2D> navigation_result; int navigation_path_index = 0; + + // the velocity result of the avoidance simulation step + Vector2 safe_velocity; + + /// The submitted target velocity, sets the "wanted" rvo agent velocity on the next update + // this velocity is not guaranteed, the simulation will try to fulfil it if possible + // if other agents or obstacles interfere it will be changed accordingly + Vector2 velocity; bool velocity_submitted = false; - Vector2 prev_safe_velocity; - /// The submitted target velocity - Vector2 target_velocity; + + /// The submitted forced velocity, overrides the rvo agent velocity on the next update + // should only be used very intentionally and not every frame as it interferes with the simulation stability + Vector2 velocity_forced; + bool velocity_forced_submitted = false; + + bool target_position_submitted = false; + bool target_reached = false; bool navigation_finished = true; // No initialized on purpose @@ -81,27 +98,27 @@ class NavigationAgent2D : public Node { float debug_path_custom_line_width = -1.0; bool debug_use_custom = false; Color debug_path_custom_color = Color(1.0, 1.0, 1.0, 1.0); + #ifdef DEBUG_ENABLED // Debug properties internal only bool debug_path_dirty = true; RID debug_path_instance; - -private: - void _navigation_debug_changed(); - void _update_debug_path(); #endif // DEBUG_ENABLED protected: static void _bind_methods(); void _notification(int p_what); +#ifndef DISABLE_DEPRECATED + bool _set(const StringName &p_name, const Variant &p_value); + bool _get(const StringName &p_name, Variant &r_ret) const; +#endif // DISABLE_DEPRECATED + public: NavigationAgent2D(); virtual ~NavigationAgent2D(); - RID get_rid() const { - return agent; - } + RID get_rid() const { return agent; } void set_avoidance_enabled(bool p_enabled); bool get_avoidance_enabled() const; @@ -133,39 +150,28 @@ public: RID get_navigation_map() const; void set_path_desired_distance(real_t p_dd); - real_t get_path_desired_distance() const { - return path_desired_distance; - } + real_t get_path_desired_distance() const { return path_desired_distance; } void set_target_desired_distance(real_t p_dd); - real_t get_target_desired_distance() const { - return target_desired_distance; - } + real_t get_target_desired_distance() const { return target_desired_distance; } void set_radius(real_t p_radius); - real_t get_radius() const { - return radius; - } + real_t get_radius() const { return radius; } void set_neighbor_distance(real_t p_distance); - real_t get_neighbor_distance() const { - return neighbor_distance; - } + real_t get_neighbor_distance() const { return neighbor_distance; } void set_max_neighbors(int p_count); - int get_max_neighbors() const { - return max_neighbors; - } + int get_max_neighbors() const { return max_neighbors; } - void set_time_horizon(real_t p_time); - real_t get_time_horizon() const { - return time_horizon; - } + void set_time_horizon_agents(real_t p_time_horizon); + real_t get_time_horizon_agents() const { return time_horizon_agents; } + + void set_time_horizon_obstacles(real_t p_time_horizon); + real_t get_time_horizon_obstacles() const { return time_horizon_obstacles; } void set_max_speed(real_t p_max_speed); - real_t get_max_speed() const { - return max_speed; - } + real_t get_max_speed() const { return max_speed; } void set_path_max_distance(real_t p_pmd); real_t get_path_max_distance(); @@ -175,15 +181,11 @@ public: Vector2 get_next_path_position(); - Ref<NavigationPathQueryResult2D> get_current_navigation_result() const { - return navigation_result; - } - const Vector<Vector2> &get_current_navigation_path() const { - return navigation_result->get_path(); - } - int get_current_navigation_path_index() const { - return navigation_path_index; - } + Ref<NavigationPathQueryResult2D> get_current_navigation_result() const { return navigation_result; } + + const Vector<Vector2> &get_current_navigation_path() const { return navigation_result->get_path(); } + + int get_current_navigation_path_index() const { return navigation_path_index; } real_t distance_to_target() const; bool is_target_reached() const; @@ -191,11 +193,30 @@ public: bool is_navigation_finished(); Vector2 get_final_position(); - void set_velocity(Vector2 p_velocity); + void set_velocity(const Vector2 p_velocity); + Vector2 get_velocity() { return velocity; } + + void set_velocity_forced(const Vector2 p_velocity); + void _avoidance_done(Vector3 p_new_velocity); PackedStringArray get_configuration_warnings() const override; + void set_avoidance_layers(uint32_t p_layers); + uint32_t get_avoidance_layers() const; + + void set_avoidance_mask(uint32_t p_mask); + uint32_t get_avoidance_mask() const; + + void set_avoidance_layer_value(int p_layer_number, bool p_value); + bool get_avoidance_layer_value(int p_layer_number) const; + + void set_avoidance_mask_value(int p_mask_number, bool p_value); + bool get_avoidance_mask_value(int p_mask_number) const; + + void set_avoidance_priority(real_t p_priority); + real_t get_avoidance_priority() const; + void set_debug_enabled(bool p_enabled); bool get_debug_enabled() const; @@ -215,6 +236,11 @@ private: void update_navigation(); void _request_repath(); void _check_distance_to_target(); + +#ifdef DEBUG_ENABLED + void _navigation_debug_changed(); + void _update_debug_path(); +#endif // DEBUG_ENABLED }; #endif // NAVIGATION_AGENT_2D_H diff --git a/scene/2d/navigation_link_2d.cpp b/scene/2d/navigation_link_2d.cpp index 8adb7c6305..3664040e7b 100644 --- a/scene/2d/navigation_link_2d.cpp +++ b/scene/2d/navigation_link_2d.cpp @@ -106,19 +106,29 @@ void NavigationLink2D::_notification(int p_what) { case NOTIFICATION_ENTER_TREE: { if (enabled) { NavigationServer2D::get_singleton()->link_set_map(link, get_world_2d()->get_navigation_map()); - - // Update global positions for the link. - Transform2D gt = get_global_transform(); - NavigationServer2D::get_singleton()->link_set_start_position(link, gt.xform(start_position)); - NavigationServer2D::get_singleton()->link_set_end_position(link, gt.xform(end_position)); } + current_global_transform = get_global_transform(); + NavigationServer2D::get_singleton()->link_set_start_position(link, current_global_transform.xform(start_position)); + NavigationServer2D::get_singleton()->link_set_end_position(link, current_global_transform.xform(end_position)); } break; + case NOTIFICATION_TRANSFORM_CHANGED: { - // Update global positions for the link. - Transform2D gt = get_global_transform(); - NavigationServer2D::get_singleton()->link_set_start_position(link, gt.xform(start_position)); - NavigationServer2D::get_singleton()->link_set_end_position(link, gt.xform(end_position)); + set_physics_process_internal(true); } break; + + case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { + set_physics_process_internal(false); + if (is_inside_tree()) { + Transform2D new_global_transform = get_global_transform(); + if (current_global_transform != new_global_transform) { + current_global_transform = new_global_transform; + NavigationServer2D::get_singleton()->link_set_start_position(link, current_global_transform.xform(start_position)); + NavigationServer2D::get_singleton()->link_set_end_position(link, current_global_transform.xform(end_position)); + queue_redraw(); + } + } + } break; + case NOTIFICATION_EXIT_TREE: { NavigationServer2D::get_singleton()->link_set_map(link, RID()); } break; @@ -242,8 +252,7 @@ void NavigationLink2D::set_start_position(Vector2 p_position) { return; } - Transform2D gt = get_global_transform(); - NavigationServer2D::get_singleton()->link_set_start_position(link, gt.xform(start_position)); + NavigationServer2D::get_singleton()->link_set_start_position(link, current_global_transform.xform(start_position)); update_configuration_warnings(); @@ -265,8 +274,7 @@ void NavigationLink2D::set_end_position(Vector2 p_position) { return; } - Transform2D gt = get_global_transform(); - NavigationServer2D::get_singleton()->link_set_end_position(link, gt.xform(end_position)); + NavigationServer2D::get_singleton()->link_set_end_position(link, current_global_transform.xform(end_position)); update_configuration_warnings(); diff --git a/scene/2d/navigation_link_2d.h b/scene/2d/navigation_link_2d.h index 8a24d611c9..4259740c90 100644 --- a/scene/2d/navigation_link_2d.h +++ b/scene/2d/navigation_link_2d.h @@ -45,6 +45,8 @@ class NavigationLink2D : public Node2D { real_t enter_cost = 0.0; real_t travel_cost = 1.0; + Transform2D current_global_transform; + protected: static void _bind_methods(); void _notification(int p_what); diff --git a/scene/2d/navigation_obstacle_2d.cpp b/scene/2d/navigation_obstacle_2d.cpp index 2366cb696e..d368a4d436 100644 --- a/scene/2d/navigation_obstacle_2d.cpp +++ b/scene/2d/navigation_obstacle_2d.cpp @@ -30,223 +30,280 @@ #include "navigation_obstacle_2d.h" -#include "scene/2d/collision_shape_2d.h" -#include "scene/2d/physics_body_2d.h" +#include "core/math/geometry_2d.h" #include "scene/resources/world_2d.h" #include "servers/navigation_server_2d.h" +#include "servers/navigation_server_3d.h" void NavigationObstacle2D::_bind_methods() { - ClassDB::bind_method(D_METHOD("get_rid"), &NavigationObstacle2D::get_rid); + ClassDB::bind_method(D_METHOD("get_obstacle_rid"), &NavigationObstacle2D::get_obstacle_rid); + ClassDB::bind_method(D_METHOD("get_agent_rid"), &NavigationObstacle2D::get_agent_rid); ClassDB::bind_method(D_METHOD("set_navigation_map", "navigation_map"), &NavigationObstacle2D::set_navigation_map); ClassDB::bind_method(D_METHOD("get_navigation_map"), &NavigationObstacle2D::get_navigation_map); - ClassDB::bind_method(D_METHOD("set_estimate_radius", "estimate_radius"), &NavigationObstacle2D::set_estimate_radius); - ClassDB::bind_method(D_METHOD("is_radius_estimated"), &NavigationObstacle2D::is_radius_estimated); ClassDB::bind_method(D_METHOD("set_radius", "radius"), &NavigationObstacle2D::set_radius); ClassDB::bind_method(D_METHOD("get_radius"), &NavigationObstacle2D::get_radius); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "estimate_radius"), "set_estimate_radius", "is_radius_estimated"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0.01,500,0.01"), "set_radius", "get_radius"); -} + ClassDB::bind_method(D_METHOD("set_velocity", "velocity"), &NavigationObstacle2D::set_velocity); + ClassDB::bind_method(D_METHOD("get_velocity"), &NavigationObstacle2D::get_velocity); -void NavigationObstacle2D::_validate_property(PropertyInfo &p_property) const { - if (p_property.name == "radius") { - if (estimate_radius) { - p_property.usage = PROPERTY_USAGE_NO_EDITOR; - } - } + ClassDB::bind_method(D_METHOD("set_vertices", "vertices"), &NavigationObstacle2D::set_vertices); + ClassDB::bind_method(D_METHOD("get_vertices"), &NavigationObstacle2D::get_vertices); + + ClassDB::bind_method(D_METHOD("set_avoidance_layers", "layers"), &NavigationObstacle2D::set_avoidance_layers); + ClassDB::bind_method(D_METHOD("get_avoidance_layers"), &NavigationObstacle2D::get_avoidance_layers); + ClassDB::bind_method(D_METHOD("set_avoidance_layer_value", "layer_number", "value"), &NavigationObstacle2D::set_avoidance_layer_value); + ClassDB::bind_method(D_METHOD("get_avoidance_layer_value", "layer_number"), &NavigationObstacle2D::get_avoidance_layer_value); + + ADD_GROUP("Avoidance", "avoidance_"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "velocity", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_velocity", "get_velocity"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0.0,500,0.01,suffix:px"), "set_radius", "get_radius"); + ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR2_ARRAY, "vertices"), "set_vertices", "get_vertices"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "avoidance_layers", PROPERTY_HINT_LAYERS_AVOIDANCE), "set_avoidance_layers", "get_avoidance_layers"); } void NavigationObstacle2D::_notification(int p_what) { switch (p_what) { case NOTIFICATION_POST_ENTER_TREE: { - set_agent_parent(get_parent()); + if (map_override.is_valid()) { + _update_map(map_override); + } else if (is_inside_tree()) { + _update_map(get_world_2d()->get_navigation_map()); + } else { + _update_map(RID()); + } + previous_transform = get_global_transform(); + // need to trigger map controlled agent assignment somehow for the fake_agent since obstacles use no callback like regular agents + NavigationServer2D::get_singleton()->agent_set_avoidance_enabled(fake_agent, radius > 0); + _update_position(get_global_transform().get_origin()); set_physics_process_internal(true); } break; case NOTIFICATION_EXIT_TREE: { - set_agent_parent(nullptr); - set_physics_process_internal(false); - } break; - - case NOTIFICATION_PARENTED: { - if (is_inside_tree() && (get_parent() != parent_node2d)) { - set_agent_parent(get_parent()); - set_physics_process_internal(true); - } - } break; - - case NOTIFICATION_UNPARENTED: { - set_agent_parent(nullptr); set_physics_process_internal(false); + _update_map(RID()); } break; case NOTIFICATION_PAUSED: { - if (parent_node2d && !parent_node2d->can_process()) { - map_before_pause = NavigationServer2D::get_singleton()->agent_get_map(get_rid()); - NavigationServer2D::get_singleton()->agent_set_map(get_rid(), RID()); - } else if (parent_node2d && parent_node2d->can_process() && !(map_before_pause == RID())) { - NavigationServer2D::get_singleton()->agent_set_map(get_rid(), map_before_pause); + if (!can_process()) { + map_before_pause = map_current; + _update_map(RID()); + } else if (can_process() && !(map_before_pause == RID())) { + _update_map(map_before_pause); map_before_pause = RID(); } } break; case NOTIFICATION_UNPAUSED: { - if (parent_node2d && !parent_node2d->can_process()) { - map_before_pause = NavigationServer2D::get_singleton()->agent_get_map(get_rid()); - NavigationServer2D::get_singleton()->agent_set_map(get_rid(), RID()); - } else if (parent_node2d && parent_node2d->can_process() && !(map_before_pause == RID())) { - NavigationServer2D::get_singleton()->agent_set_map(get_rid(), map_before_pause); + if (!can_process()) { + map_before_pause = map_current; + _update_map(RID()); + } else if (can_process() && !(map_before_pause == RID())) { + _update_map(map_before_pause); map_before_pause = RID(); } } break; case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { - if (parent_node2d && parent_node2d->is_inside_tree()) { - NavigationServer2D::get_singleton()->agent_set_position(agent, parent_node2d->get_global_position()); + if (is_inside_tree()) { + _update_position(get_global_transform().get_origin()); + + if (velocity_submitted) { + velocity_submitted = false; + // only update if there is a noticeable change, else the rvo agent preferred velocity stays the same + if (!previous_velocity.is_equal_approx(velocity)) { + NavigationServer2D::get_singleton()->agent_set_velocity(fake_agent, velocity); + } + previous_velocity = velocity; + } + } + } break; + + case NOTIFICATION_DRAW: { +#ifdef DEBUG_ENABLED + if (is_inside_tree()) { + bool is_debug_enabled = false; + if (Engine::get_singleton()->is_editor_hint()) { + is_debug_enabled = true; + } else if (NavigationServer2D::get_singleton()->get_debug_enabled() && NavigationServer2D::get_singleton()->get_debug_avoidance_enabled()) { + is_debug_enabled = true; + } + + if (is_debug_enabled) { + _update_fake_agent_radius_debug(); + _update_static_obstacle_debug(); + } } +#endif // DEBUG_ENABLED } break; } } NavigationObstacle2D::NavigationObstacle2D() { - agent = NavigationServer2D::get_singleton()->agent_create(); - initialize_agent(); + obstacle = NavigationServer2D::get_singleton()->obstacle_create(); + fake_agent = NavigationServer2D::get_singleton()->agent_create(); + + // change properties of the fake agent so it can act as fake obstacle with a radius + NavigationServer2D::get_singleton()->agent_set_neighbor_distance(fake_agent, 0.0); + NavigationServer2D::get_singleton()->agent_set_max_neighbors(fake_agent, 0); + NavigationServer2D::get_singleton()->agent_set_time_horizon_agents(fake_agent, 0.0); + NavigationServer2D::get_singleton()->agent_set_time_horizon_obstacles(fake_agent, 0.0); + NavigationServer2D::get_singleton()->agent_set_max_speed(fake_agent, 0.0); + NavigationServer2D::get_singleton()->agent_set_avoidance_mask(fake_agent, 0); + NavigationServer2D::get_singleton()->agent_set_avoidance_priority(fake_agent, 1.0); + NavigationServer2D::get_singleton()->agent_set_avoidance_enabled(fake_agent, radius > 0); + + set_radius(radius); + set_vertices(vertices); + set_avoidance_layers(avoidance_layers); } NavigationObstacle2D::~NavigationObstacle2D() { ERR_FAIL_NULL(NavigationServer2D::get_singleton()); - NavigationServer2D::get_singleton()->free(agent); - agent = RID(); // Pointless -} -PackedStringArray NavigationObstacle2D::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + NavigationServer2D::get_singleton()->free(obstacle); + obstacle = RID(); - if (!Object::cast_to<Node2D>(get_parent())) { - warnings.push_back(RTR("The NavigationObstacle2D only serves to provide collision avoidance to a Node2D object.")); - } - - if (Object::cast_to<StaticBody2D>(get_parent())) { - warnings.push_back(RTR("The NavigationObstacle2D is intended for constantly moving bodies like CharacterBody2D or RigidBody2D as it creates only an RVO avoidance radius and does not follow scene geometry exactly." - "\nNot constantly moving or complete static objects should be captured with a refreshed NavigationPolygon so agents can not only avoid them but also move along those objects outline at high detail")); - } - - return warnings; + NavigationServer2D::get_singleton()->free(fake_agent); + fake_agent = RID(); } -void NavigationObstacle2D::initialize_agent() { - NavigationServer2D::get_singleton()->agent_set_neighbor_distance(agent, 0.0); - NavigationServer2D::get_singleton()->agent_set_max_neighbors(agent, 0); - NavigationServer2D::get_singleton()->agent_set_time_horizon(agent, 0.0); - NavigationServer2D::get_singleton()->agent_set_max_speed(agent, 0.0); +void NavigationObstacle2D::set_vertices(const Vector<Vector2> &p_vertices) { + vertices = p_vertices; + NavigationServer2D::get_singleton()->obstacle_set_vertices(obstacle, vertices); + if (is_inside_tree() && (Engine::get_singleton()->is_editor_hint() || get_tree()->is_debugging_navigation_hint())) { + queue_redraw(); + } } -void NavigationObstacle2D::reevaluate_agent_radius() { - if (!estimate_radius) { - NavigationServer2D::get_singleton()->agent_set_radius(agent, radius); - } else if (parent_node2d && parent_node2d->is_inside_tree()) { - NavigationServer2D::get_singleton()->agent_set_radius(agent, estimate_agent_radius()); +void NavigationObstacle2D::set_navigation_map(RID p_navigation_map) { + if (map_override == p_navigation_map) { + return; } + map_override = p_navigation_map; + _update_map(map_override); } -real_t NavigationObstacle2D::estimate_agent_radius() const { - if (parent_node2d && parent_node2d->is_inside_tree()) { - // Estimate the radius of this physics body - real_t max_radius = 0.0; - for (int i(0); i < parent_node2d->get_child_count(); i++) { - // For each collision shape - CollisionShape2D *cs = Object::cast_to<CollisionShape2D>(parent_node2d->get_child(i)); - if (cs && cs->is_inside_tree()) { - // Take the distance between the Body center to the shape center - real_t r = cs->get_transform().get_origin().length(); - if (cs->get_shape().is_valid()) { - // and add the enclosing shape radius - r += cs->get_shape()->get_enclosing_radius(); - } - Size2 s = cs->get_global_scale(); - r *= MAX(s.x, s.y); - // Takes the biggest radius - max_radius = MAX(max_radius, r); - } else if (cs && !cs->is_inside_tree()) { - WARN_PRINT("A CollisionShape2D of the NavigationObstacle2D parent node was not inside the SceneTree when estimating the obstacle radius." - "\nMove the NavigationObstacle2D to a child position below any CollisionShape2D node of the parent node so the CollisionShape2D is already inside the SceneTree."); - } - } - Vector2 s = parent_node2d->get_global_scale(); - max_radius *= MAX(s.x, s.y); - - if (max_radius > 0.0) { - return max_radius; - } +RID NavigationObstacle2D::get_navigation_map() const { + if (map_override.is_valid()) { + return map_override; + } else if (is_inside_tree()) { + return get_world_2d()->get_navigation_map(); } - return 1.0; // Never a 0 radius + return RID(); } -void NavigationObstacle2D::set_agent_parent(Node *p_agent_parent) { - if (parent_node2d == p_agent_parent) { +void NavigationObstacle2D::set_radius(real_t p_radius) { + ERR_FAIL_COND_MSG(p_radius < 0.0, "Radius must be positive."); + if (Math::is_equal_approx(radius, p_radius)) { return; } - if (Object::cast_to<Node2D>(p_agent_parent) != nullptr) { - parent_node2d = Object::cast_to<Node2D>(p_agent_parent); - if (map_override.is_valid()) { - NavigationServer2D::get_singleton()->agent_set_map(get_rid(), map_override); - } else { - NavigationServer2D::get_singleton()->agent_set_map(get_rid(), parent_node2d->get_world_2d()->get_navigation_map()); - } - // Need to register Callback as obstacle requires a valid Callback to be added to avoidance simulation. - NavigationServer2D::get_singleton()->agent_set_callback(get_rid(), callable_mp(this, &NavigationObstacle2D::_avoidance_done)); - reevaluate_agent_radius(); - } else { - parent_node2d = nullptr; - NavigationServer2D::get_singleton()->agent_set_map(get_rid(), RID()); - NavigationServer2D::get_singleton()->agent_set_callback(agent, Callable()); + radius = p_radius; + + NavigationServer2D::get_singleton()->agent_set_avoidance_enabled(fake_agent, radius > 0.0); + NavigationServer2D::get_singleton()->agent_set_radius(fake_agent, radius); + if (is_inside_tree() && (Engine::get_singleton()->is_editor_hint() || get_tree()->is_debugging_navigation_hint())) { + queue_redraw(); } } -void NavigationObstacle2D::_avoidance_done(Vector3 p_new_velocity) { - // Dummy function as obstacle requires a valid Callback to be added to avoidance simulation. +void NavigationObstacle2D::set_avoidance_layers(uint32_t p_layers) { + avoidance_layers = p_layers; + NavigationServer2D::get_singleton()->obstacle_set_avoidance_layers(obstacle, avoidance_layers); + NavigationServer2D::get_singleton()->agent_set_avoidance_layers(fake_agent, avoidance_layers); } -void NavigationObstacle2D::set_navigation_map(RID p_navigation_map) { - if (map_override == p_navigation_map) { - return; +uint32_t NavigationObstacle2D::get_avoidance_layers() const { + return avoidance_layers; +} + +void NavigationObstacle2D::set_avoidance_layer_value(int p_layer_number, bool p_value) { + ERR_FAIL_COND_MSG(p_layer_number < 1, "Avoidance layer number must be between 1 and 32 inclusive."); + ERR_FAIL_COND_MSG(p_layer_number > 32, "Avoidance layer number must be between 1 and 32 inclusive."); + uint32_t avoidance_layers_new = get_avoidance_layers(); + if (p_value) { + avoidance_layers_new |= 1 << (p_layer_number - 1); + } else { + avoidance_layers_new &= ~(1 << (p_layer_number - 1)); } + set_avoidance_layers(avoidance_layers_new); +} - map_override = p_navigation_map; +bool NavigationObstacle2D::get_avoidance_layer_value(int p_layer_number) const { + ERR_FAIL_COND_V_MSG(p_layer_number < 1, false, "Avoidance layer number must be between 1 and 32 inclusive."); + ERR_FAIL_COND_V_MSG(p_layer_number > 32, false, "Avoidance layer number must be between 1 and 32 inclusive."); + return get_avoidance_layers() & (1 << (p_layer_number - 1)); +} - NavigationServer2D::get_singleton()->agent_set_map(agent, map_override); +void NavigationObstacle2D::set_velocity(const Vector2 p_velocity) { + velocity = p_velocity; + velocity_submitted = true; } -RID NavigationObstacle2D::get_navigation_map() const { - if (map_override.is_valid()) { - return map_override; - } else if (parent_node2d != nullptr) { - return parent_node2d->get_world_2d()->get_navigation_map(); +void NavigationObstacle2D::_update_map(RID p_map) { + NavigationServer2D::get_singleton()->obstacle_set_map(obstacle, p_map); + NavigationServer2D::get_singleton()->agent_set_map(fake_agent, p_map); + map_current = p_map; +} + +void NavigationObstacle2D::_update_position(const Vector2 p_position) { + if (vertices.size() > 0) { + NavigationServer2D::get_singleton()->obstacle_set_position(obstacle, p_position); + } + if (radius > 0.0) { + NavigationServer2D::get_singleton()->agent_set_position(fake_agent, p_position); } - return RID(); } -void NavigationObstacle2D::set_estimate_radius(bool p_estimate_radius) { - if (estimate_radius == p_estimate_radius) { - return; +#ifdef DEBUG_ENABLED +void NavigationObstacle2D::_update_fake_agent_radius_debug() { + if (radius > 0.0 && NavigationServer2D::get_singleton()->get_debug_navigation_avoidance_enable_obstacles_radius()) { + Color debug_radius_color = NavigationServer2D::get_singleton()->get_debug_navigation_avoidance_obstacles_radius_color(); + draw_circle(get_global_transform().get_origin(), radius, debug_radius_color); } +} +#endif // DEBUG_ENABLED - estimate_radius = p_estimate_radius; +#ifdef DEBUG_ENABLED +void NavigationObstacle2D::_update_static_obstacle_debug() { + if (get_vertices().size() > 2 && NavigationServer2D::get_singleton()->get_debug_navigation_avoidance_enable_obstacles_static()) { + bool obstacle_pushes_inward = Geometry2D::is_polygon_clockwise(get_vertices()); - notify_property_list_changed(); - reevaluate_agent_radius(); -} + Color debug_static_obstacle_face_color; -void NavigationObstacle2D::set_radius(real_t p_radius) { - ERR_FAIL_COND_MSG(p_radius <= 0.0, "Radius must be greater than 0."); - if (Math::is_equal_approx(radius, p_radius)) { - return; - } + if (obstacle_pushes_inward) { + debug_static_obstacle_face_color = NavigationServer2D::get_singleton()->get_debug_navigation_avoidance_static_obstacle_pushin_face_color(); + } else { + debug_static_obstacle_face_color = NavigationServer2D::get_singleton()->get_debug_navigation_avoidance_static_obstacle_pushout_face_color(); + } - radius = p_radius; + Vector<Vector2> debug_obstacle_polygon_vertices = get_vertices(); + + Vector<Color> debug_obstacle_polygon_colors; + debug_obstacle_polygon_colors.resize(debug_obstacle_polygon_vertices.size()); + debug_obstacle_polygon_colors.fill(debug_static_obstacle_face_color); + + RS::get_singleton()->canvas_item_add_polygon(get_canvas_item(), debug_obstacle_polygon_vertices, debug_obstacle_polygon_colors); + + Color debug_static_obstacle_edge_color; + + if (obstacle_pushes_inward) { + debug_static_obstacle_edge_color = NavigationServer2D::get_singleton()->get_debug_navigation_avoidance_static_obstacle_pushin_edge_color(); + } else { + debug_static_obstacle_edge_color = NavigationServer2D::get_singleton()->get_debug_navigation_avoidance_static_obstacle_pushout_edge_color(); + } + + Vector<Vector2> debug_obstacle_line_vertices = get_vertices(); + debug_obstacle_line_vertices.push_back(debug_obstacle_line_vertices[0]); + debug_obstacle_line_vertices.resize(debug_obstacle_line_vertices.size()); - reevaluate_agent_radius(); + Vector<Color> debug_obstacle_line_colors; + debug_obstacle_line_colors.resize(debug_obstacle_line_vertices.size()); + debug_obstacle_line_colors.fill(debug_static_obstacle_edge_color); + + RS::get_singleton()->canvas_item_add_polyline(get_canvas_item(), debug_obstacle_line_vertices, debug_obstacle_line_colors, 4.0); + } } +#endif // DEBUG_ENABLED diff --git a/scene/2d/navigation_obstacle_2d.h b/scene/2d/navigation_obstacle_2d.h index f856c481b0..d3b0926247 100644 --- a/scene/2d/navigation_obstacle_2d.h +++ b/scene/2d/navigation_obstacle_2d.h @@ -32,55 +32,71 @@ #define NAVIGATION_OBSTACLE_2D_H #include "scene/2d/node_2d.h" -#include "scene/main/node.h" -class NavigationObstacle2D : public Node { - GDCLASS(NavigationObstacle2D, Node); +class NavigationObstacle2D : public Node2D { + GDCLASS(NavigationObstacle2D, Node2D); - Node2D *parent_node2d = nullptr; - - RID agent; + RID obstacle; RID map_before_pause; RID map_override; + RID map_current; + + real_t radius = 0.0; + + Vector<Vector2> vertices; + + RID fake_agent; + uint32_t avoidance_layers = 1; + + Transform2D previous_transform; + + Vector2 velocity; + Vector2 previous_velocity; + bool velocity_submitted = false; - bool estimate_radius = true; - real_t radius = 1.0; +#ifdef DEBUG_ENABLED +private: + void _update_fake_agent_radius_debug(); + void _update_static_obstacle_debug(); +#endif // DEBUG_ENABLED protected: static void _bind_methods(); - void _validate_property(PropertyInfo &p_property) const; void _notification(int p_what); public: NavigationObstacle2D(); virtual ~NavigationObstacle2D(); - RID get_rid() const { - return agent; - } - - void set_agent_parent(Node *p_agent_parent); + RID get_obstacle_rid() const { return obstacle; } + RID get_agent_rid() const { return fake_agent; } void set_navigation_map(RID p_navigation_map); RID get_navigation_map() const; - void set_estimate_radius(bool p_estimate_radius); - bool is_radius_estimated() const { - return estimate_radius; - } void set_radius(real_t p_radius); - real_t get_radius() const { - return radius; - } + real_t get_radius() const { return radius; } + + void set_vertices(const Vector<Vector2> &p_vertices); + const Vector<Vector2> &get_vertices() const { return vertices; }; + + void set_avoidance_layers(uint32_t p_layers); + uint32_t get_avoidance_layers() const; + + void set_avoidance_mask(uint32_t p_mask); + uint32_t get_avoidance_mask() const; + + void set_avoidance_layer_value(int p_layer_number, bool p_value); + bool get_avoidance_layer_value(int p_layer_number) const; - PackedStringArray get_configuration_warnings() const override; + void set_velocity(const Vector2 p_velocity); + Vector2 get_velocity() const { return velocity; }; void _avoidance_done(Vector3 p_new_velocity); // Dummy private: - void initialize_agent(); - void reevaluate_agent_radius(); - real_t estimate_agent_radius() const; + void _update_map(RID p_map); + void _update_position(const Vector2 p_position); }; #endif // NAVIGATION_OBSTACLE_2D_H diff --git a/scene/2d/navigation_region_2d.cpp b/scene/2d/navigation_region_2d.cpp index 5dbba313bc..cbcdb9f88e 100644 --- a/scene/2d/navigation_region_2d.cpp +++ b/scene/2d/navigation_region_2d.cpp @@ -31,6 +31,8 @@ #include "navigation_region_2d.h" #include "core/core_string_names.h" +#include "core/math/geometry_2d.h" +#include "scene/2d/navigation_obstacle_2d.h" #include "scene/resources/world_2d.h" #include "servers/navigation_server_2d.h" #include "servers/navigation_server_3d.h" @@ -55,7 +57,7 @@ void NavigationRegion2D::set_enabled(bool p_enabled) { } #ifdef DEBUG_ENABLED - if (Engine::get_singleton()->is_editor_hint() || NavigationServer3D::get_singleton()->get_debug_enabled()) { + if (Engine::get_singleton()->is_editor_hint() || NavigationServer3D::get_singleton()->get_debug_navigation_enabled()) { queue_redraw(); } #endif // DEBUG_ENABLED @@ -151,11 +153,19 @@ void NavigationRegion2D::_notification(int p_what) { if (enabled) { NavigationServer2D::get_singleton()->region_set_map(region, get_world_2d()->get_navigation_map()); NavigationServer2D::get_singleton()->connect("map_changed", callable_mp(this, &NavigationRegion2D::_map_changed)); + for (uint32_t i = 0; i < constrain_avoidance_obstacles.size(); i++) { + if (constrain_avoidance_obstacles[i].is_valid()) { + NavigationServer2D::get_singleton()->obstacle_set_map(constrain_avoidance_obstacles[i], get_world_2d()->get_navigation_map()); + NavigationServer2D::get_singleton()->obstacle_set_position(constrain_avoidance_obstacles[i], get_global_position()); + } + } } + current_global_transform = get_global_transform(); + NavigationServer2D::get_singleton()->region_set_transform(region, current_global_transform); } break; case NOTIFICATION_TRANSFORM_CHANGED: { - NavigationServer2D::get_singleton()->region_set_transform(region, get_global_transform()); + set_physics_process_internal(true); } break; case NOTIFICATION_EXIT_TREE: { @@ -163,6 +173,29 @@ void NavigationRegion2D::_notification(int p_what) { if (enabled) { NavigationServer2D::get_singleton()->disconnect("map_changed", callable_mp(this, &NavigationRegion2D::_map_changed)); } + for (uint32_t i = 0; i < constrain_avoidance_obstacles.size(); i++) { + if (constrain_avoidance_obstacles[i].is_valid()) { + NavigationServer2D::get_singleton()->obstacle_set_map(constrain_avoidance_obstacles[i], RID()); + } + } + } break; + + case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { + set_physics_process_internal(false); + if (is_inside_tree()) { + Transform2D new_global_transform = get_global_transform(); + if (current_global_transform != new_global_transform) { + current_global_transform = new_global_transform; + NavigationServer2D::get_singleton()->region_set_transform(region, current_global_transform); + queue_redraw(); + + for (uint32_t i = 0; i < constrain_avoidance_obstacles.size(); i++) { + if (constrain_avoidance_obstacles[i].is_valid()) { + NavigationServer2D::get_singleton()->obstacle_set_position(constrain_avoidance_obstacles[i], get_global_position()); + } + } + } + } } break; case NOTIFICATION_DRAW: { @@ -277,6 +310,7 @@ void NavigationRegion2D::_navigation_polygon_changed() { if (navigation_polygon.is_valid()) { NavigationServer2D::get_singleton()->region_set_navigation_polygon(region, navigation_polygon); } + _update_avoidance_constrain(); } void NavigationRegion2D::_map_changed(RID p_map) { @@ -312,6 +346,13 @@ void NavigationRegion2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_navigation_layer_value", "layer_number", "value"), &NavigationRegion2D::set_navigation_layer_value); ClassDB::bind_method(D_METHOD("get_navigation_layer_value", "layer_number"), &NavigationRegion2D::get_navigation_layer_value); + ClassDB::bind_method(D_METHOD("set_constrain_avoidance", "enabled"), &NavigationRegion2D::set_constrain_avoidance); + ClassDB::bind_method(D_METHOD("get_constrain_avoidance"), &NavigationRegion2D::get_constrain_avoidance); + ClassDB::bind_method(D_METHOD("set_avoidance_layers", "layers"), &NavigationRegion2D::set_avoidance_layers); + ClassDB::bind_method(D_METHOD("get_avoidance_layers"), &NavigationRegion2D::get_avoidance_layers); + ClassDB::bind_method(D_METHOD("set_avoidance_layer_value", "layer_number", "value"), &NavigationRegion2D::set_avoidance_layer_value); + ClassDB::bind_method(D_METHOD("get_avoidance_layer_value", "layer_number"), &NavigationRegion2D::get_avoidance_layer_value); + ClassDB::bind_method(D_METHOD("get_region_rid"), &NavigationRegion2D::get_region_rid); ClassDB::bind_method(D_METHOD("set_enter_cost", "enter_cost"), &NavigationRegion2D::set_enter_cost); @@ -327,6 +368,8 @@ void NavigationRegion2D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "navigation_layers", PROPERTY_HINT_LAYERS_2D_NAVIGATION), "set_navigation_layers", "get_navigation_layers"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "enter_cost"), "set_enter_cost", "get_enter_cost"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "travel_cost"), "set_travel_cost", "get_travel_cost"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "constrain_avoidance"), "set_constrain_avoidance", "get_constrain_avoidance"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "avoidance_layers", PROPERTY_HINT_LAYERS_AVOIDANCE), "set_avoidance_layers", "get_avoidance_layers"); } #ifndef DISABLE_DEPRECATED @@ -367,8 +410,123 @@ NavigationRegion2D::~NavigationRegion2D() { ERR_FAIL_NULL(NavigationServer2D::get_singleton()); NavigationServer2D::get_singleton()->free(region); + for (uint32_t i = 0; i < constrain_avoidance_obstacles.size(); i++) { + if (constrain_avoidance_obstacles[i].is_valid()) { + NavigationServer2D::get_singleton()->free(constrain_avoidance_obstacles[i]); + } + } + constrain_avoidance_obstacles.clear(); + #ifdef DEBUG_ENABLED NavigationServer3D::get_singleton()->disconnect("map_changed", callable_mp(this, &NavigationRegion2D::_map_changed)); NavigationServer3D::get_singleton()->disconnect("navigation_debug_changed", callable_mp(this, &NavigationRegion2D::_map_changed)); #endif // DEBUG_ENABLED } + +void NavigationRegion2D::_update_avoidance_constrain() { + for (uint32_t i = 0; i < constrain_avoidance_obstacles.size(); i++) { + if (constrain_avoidance_obstacles[i].is_valid()) { + NavigationServer2D::get_singleton()->free(constrain_avoidance_obstacles[i]); + constrain_avoidance_obstacles[i] = RID(); + } + } + constrain_avoidance_obstacles.clear(); + + if (!constrain_avoidance) { + return; + } + + if (get_navigation_polygon() == nullptr) { + return; + } + + Ref<NavigationPolygon> _navpoly = get_navigation_polygon(); + int _outline_count = _navpoly->get_outline_count(); + if (_outline_count == 0) { + return; + } + + for (int outline_index(0); outline_index < _outline_count; outline_index++) { + const Vector<Vector2> &_outline = _navpoly->get_outline(outline_index); + + const int outline_size = _outline.size(); + if (outline_size < 3) { + ERR_FAIL_COND_MSG(_outline.size() < 3, "NavigationPolygon outline needs to have at least 3 vertex to create avoidance obstacles to constrain avoidance agent's"); + continue; + } + + RID obstacle_rid = NavigationServer2D::get_singleton()->obstacle_create(); + constrain_avoidance_obstacles.push_back(obstacle_rid); + + Vector<Vector2> new_obstacle_outline; + + if (outline_index == 0) { + for (int i(0); i < outline_size; i++) { + new_obstacle_outline.push_back(_outline[outline_size - i - 1]); + } + ERR_FAIL_COND_MSG(Geometry2D::is_polygon_clockwise(_outline), "Outer most outline needs to be clockwise to push avoidance agent inside"); + } else { + for (int i(0); i < outline_size; i++) { + new_obstacle_outline.push_back(_outline[i]); + } + } + new_obstacle_outline.resize(outline_size); + + NavigationServer2D::get_singleton()->obstacle_set_vertices(obstacle_rid, new_obstacle_outline); + NavigationServer2D::get_singleton()->obstacle_set_avoidance_layers(obstacle_rid, avoidance_layers); + if (is_inside_tree()) { + NavigationServer2D::get_singleton()->obstacle_set_map(obstacle_rid, get_world_2d()->get_navigation_map()); + NavigationServer2D::get_singleton()->obstacle_set_position(obstacle_rid, get_global_position()); + } + } + constrain_avoidance_obstacles.resize(_outline_count); +} + +void NavigationRegion2D::set_constrain_avoidance(bool p_enabled) { + constrain_avoidance = p_enabled; + _update_avoidance_constrain(); + notify_property_list_changed(); +} + +bool NavigationRegion2D::get_constrain_avoidance() const { + return constrain_avoidance; +} + +void NavigationRegion2D::_validate_property(PropertyInfo &p_property) const { + if (p_property.name == "avoidance_layers") { + if (!constrain_avoidance) { + p_property.usage = PROPERTY_USAGE_NO_EDITOR; + } + } +} + +void NavigationRegion2D::set_avoidance_layers(uint32_t p_layers) { + avoidance_layers = p_layers; + if (constrain_avoidance_obstacles.size() > 0) { + for (uint32_t i = 0; i < constrain_avoidance_obstacles.size(); i++) { + NavigationServer2D::get_singleton()->obstacle_set_avoidance_layers(constrain_avoidance_obstacles[i], avoidance_layers); + } + } +} + +uint32_t NavigationRegion2D::get_avoidance_layers() const { + return avoidance_layers; +} + +void NavigationRegion2D::set_avoidance_layer_value(int p_layer_number, bool p_value) { + ERR_FAIL_COND_MSG(p_layer_number < 1, "Avoidance layer number must be between 1 and 32 inclusive."); + ERR_FAIL_COND_MSG(p_layer_number > 32, "Avoidance layer number must be between 1 and 32 inclusive."); + uint32_t avoidance_layers_new = get_avoidance_layers(); + if (p_value) { + avoidance_layers_new |= 1 << (p_layer_number - 1); + } else { + avoidance_layers_new &= ~(1 << (p_layer_number - 1)); + } + set_avoidance_layers(avoidance_layers_new); +} + +bool NavigationRegion2D::get_avoidance_layer_value(int p_layer_number) const { + ERR_FAIL_COND_V_MSG(p_layer_number < 1, false, "Avoidance layer number must be between 1 and 32 inclusive."); + ERR_FAIL_COND_V_MSG(p_layer_number > 32, false, "Avoidance layer number must be between 1 and 32 inclusive."); + return get_avoidance_layers() & (1 << (p_layer_number - 1)); +} diff --git a/scene/2d/navigation_region_2d.h b/scene/2d/navigation_region_2d.h index 49ff143dd8..bd7bdc28eb 100644 --- a/scene/2d/navigation_region_2d.h +++ b/scene/2d/navigation_region_2d.h @@ -43,11 +43,18 @@ class NavigationRegion2D : public Node2D { real_t travel_cost = 1.0; Ref<NavigationPolygon> navigation_polygon; + bool constrain_avoidance = false; + LocalVector<RID> constrain_avoidance_obstacles; + uint32_t avoidance_layers = 1; + + Transform2D current_global_transform; + void _navigation_polygon_changed(); void _map_changed(RID p_RID); protected: void _notification(int p_what); + void _validate_property(PropertyInfo &p_property) const; static void _bind_methods(); #ifndef DISABLE_DEPRECATED @@ -81,10 +88,22 @@ public: void set_navigation_polygon(const Ref<NavigationPolygon> &p_navigation_polygon); Ref<NavigationPolygon> get_navigation_polygon() const; + void set_constrain_avoidance(bool p_enabled); + bool get_constrain_avoidance() const; + + void set_avoidance_layers(uint32_t p_layers); + uint32_t get_avoidance_layers() const; + + void set_avoidance_layer_value(int p_layer_number, bool p_value); + bool get_avoidance_layer_value(int p_layer_number) const; + PackedStringArray get_configuration_warnings() const override; NavigationRegion2D(); ~NavigationRegion2D(); + +private: + void _update_avoidance_constrain(); }; #endif // NAVIGATION_REGION_2D_H diff --git a/scene/3d/navigation_agent_3d.cpp b/scene/3d/navigation_agent_3d.cpp index 7d404ffa07..80b360a2fd 100644 --- a/scene/3d/navigation_agent_3d.cpp +++ b/scene/3d/navigation_agent_3d.cpp @@ -48,11 +48,14 @@ void NavigationAgent3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_radius", "radius"), &NavigationAgent3D::set_radius); ClassDB::bind_method(D_METHOD("get_radius"), &NavigationAgent3D::get_radius); - ClassDB::bind_method(D_METHOD("set_agent_height_offset", "agent_height_offset"), &NavigationAgent3D::set_agent_height_offset); - ClassDB::bind_method(D_METHOD("get_agent_height_offset"), &NavigationAgent3D::get_agent_height_offset); + ClassDB::bind_method(D_METHOD("set_height", "height"), &NavigationAgent3D::set_height); + ClassDB::bind_method(D_METHOD("get_height"), &NavigationAgent3D::get_height); - ClassDB::bind_method(D_METHOD("set_ignore_y", "ignore"), &NavigationAgent3D::set_ignore_y); - ClassDB::bind_method(D_METHOD("get_ignore_y"), &NavigationAgent3D::get_ignore_y); + ClassDB::bind_method(D_METHOD("set_path_height_offset", "path_height_offset"), &NavigationAgent3D::set_path_height_offset); + ClassDB::bind_method(D_METHOD("get_path_height_offset"), &NavigationAgent3D::get_path_height_offset); + + ClassDB::bind_method(D_METHOD("set_use_3d_avoidance", "enabled"), &NavigationAgent3D::set_use_3d_avoidance); + ClassDB::bind_method(D_METHOD("get_use_3d_avoidance"), &NavigationAgent3D::get_use_3d_avoidance); ClassDB::bind_method(D_METHOD("set_neighbor_distance", "neighbor_distance"), &NavigationAgent3D::set_neighbor_distance); ClassDB::bind_method(D_METHOD("get_neighbor_distance"), &NavigationAgent3D::get_neighbor_distance); @@ -60,8 +63,11 @@ void NavigationAgent3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_max_neighbors", "max_neighbors"), &NavigationAgent3D::set_max_neighbors); ClassDB::bind_method(D_METHOD("get_max_neighbors"), &NavigationAgent3D::get_max_neighbors); - ClassDB::bind_method(D_METHOD("set_time_horizon", "time_horizon"), &NavigationAgent3D::set_time_horizon); - ClassDB::bind_method(D_METHOD("get_time_horizon"), &NavigationAgent3D::get_time_horizon); + ClassDB::bind_method(D_METHOD("set_time_horizon_agents", "time_horizon"), &NavigationAgent3D::set_time_horizon_agents); + ClassDB::bind_method(D_METHOD("get_time_horizon_agents"), &NavigationAgent3D::get_time_horizon_agents); + + ClassDB::bind_method(D_METHOD("set_time_horizon_obstacles", "time_horizon"), &NavigationAgent3D::set_time_horizon_obstacles); + ClassDB::bind_method(D_METHOD("get_time_horizon_obstacles"), &NavigationAgent3D::get_time_horizon_obstacles); ClassDB::bind_method(D_METHOD("set_max_speed", "max_speed"), &NavigationAgent3D::set_max_speed); ClassDB::bind_method(D_METHOD("get_max_speed"), &NavigationAgent3D::get_max_speed); @@ -91,9 +97,16 @@ void NavigationAgent3D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_target_position"), &NavigationAgent3D::get_target_position); ClassDB::bind_method(D_METHOD("get_next_path_position"), &NavigationAgent3D::get_next_path_position); - ClassDB::bind_method(D_METHOD("distance_to_target"), &NavigationAgent3D::distance_to_target); + + ClassDB::bind_method(D_METHOD("set_velocity_forced", "velocity"), &NavigationAgent3D::set_velocity_forced); + ClassDB::bind_method(D_METHOD("set_velocity", "velocity"), &NavigationAgent3D::set_velocity); + ClassDB::bind_method(D_METHOD("get_velocity"), &NavigationAgent3D::get_velocity); + + ClassDB::bind_method(D_METHOD("distance_to_target"), &NavigationAgent3D::distance_to_target); + ClassDB::bind_method(D_METHOD("get_current_navigation_result"), &NavigationAgent3D::get_current_navigation_result); + ClassDB::bind_method(D_METHOD("get_current_navigation_path"), &NavigationAgent3D::get_current_navigation_path); ClassDB::bind_method(D_METHOD("get_current_navigation_path_index"), &NavigationAgent3D::get_current_navigation_path_index); ClassDB::bind_method(D_METHOD("is_target_reached"), &NavigationAgent3D::is_target_reached); @@ -103,11 +116,22 @@ void NavigationAgent3D::_bind_methods() { ClassDB::bind_method(D_METHOD("_avoidance_done", "new_velocity"), &NavigationAgent3D::_avoidance_done); + ClassDB::bind_method(D_METHOD("set_avoidance_layers", "layers"), &NavigationAgent3D::set_avoidance_layers); + ClassDB::bind_method(D_METHOD("get_avoidance_layers"), &NavigationAgent3D::get_avoidance_layers); + ClassDB::bind_method(D_METHOD("set_avoidance_mask", "mask"), &NavigationAgent3D::set_avoidance_mask); + ClassDB::bind_method(D_METHOD("get_avoidance_mask"), &NavigationAgent3D::get_avoidance_mask); + ClassDB::bind_method(D_METHOD("set_avoidance_layer_value", "layer_number", "value"), &NavigationAgent3D::set_avoidance_layer_value); + ClassDB::bind_method(D_METHOD("get_avoidance_layer_value", "layer_number"), &NavigationAgent3D::get_avoidance_layer_value); + ClassDB::bind_method(D_METHOD("set_avoidance_mask_value", "mask_number", "value"), &NavigationAgent3D::set_avoidance_mask_value); + ClassDB::bind_method(D_METHOD("get_avoidance_mask_value", "mask_number"), &NavigationAgent3D::get_avoidance_mask_value); + ClassDB::bind_method(D_METHOD("set_avoidance_priority", "priority"), &NavigationAgent3D::set_avoidance_priority); + ClassDB::bind_method(D_METHOD("get_avoidance_priority"), &NavigationAgent3D::get_avoidance_priority); + ADD_GROUP("Pathfinding", ""); ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "target_position", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_target_position", "get_target_position"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "path_desired_distance", PROPERTY_HINT_RANGE, "0.1,100,0.01,or_greater,suffix:m"), "set_path_desired_distance", "get_path_desired_distance"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "target_desired_distance", PROPERTY_HINT_RANGE, "0.1,100,0.01,or_greater,suffix:m"), "set_target_desired_distance", "get_target_desired_distance"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "agent_height_offset", PROPERTY_HINT_RANGE, "-100.0,100,0.01,or_greater,suffix:m"), "set_agent_height_offset", "get_agent_height_offset"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "path_height_offset", PROPERTY_HINT_RANGE, "-100.0,100,0.01,or_greater,suffix:m"), "set_path_height_offset", "get_path_height_offset"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "path_max_distance", PROPERTY_HINT_RANGE, "0.01,100,0.1,or_greater,suffix:m"), "set_path_max_distance", "get_path_max_distance"); ADD_PROPERTY(PropertyInfo(Variant::INT, "navigation_layers", PROPERTY_HINT_LAYERS_3D_NAVIGATION), "set_navigation_layers", "get_navigation_layers"); ADD_PROPERTY(PropertyInfo(Variant::INT, "pathfinding_algorithm", PROPERTY_HINT_ENUM, "AStar"), "set_pathfinding_algorithm", "get_pathfinding_algorithm"); @@ -116,12 +140,18 @@ void NavigationAgent3D::_bind_methods() { ADD_GROUP("Avoidance", ""); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "avoidance_enabled"), "set_avoidance_enabled", "get_avoidance_enabled"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0.1,100,0.01,or_greater,suffix:m"), "set_radius", "get_radius"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "velocity", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_velocity", "get_velocity"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "height", PROPERTY_HINT_RANGE, "0.01,100,0.01,or_greater,suffix:m"), "set_height", "get_height"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0.01,100,0.01,or_greater,suffix:m"), "set_radius", "get_radius"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "neighbor_distance", PROPERTY_HINT_RANGE, "0.1,10000,0.01,or_greater,suffix:m"), "set_neighbor_distance", "get_neighbor_distance"); ADD_PROPERTY(PropertyInfo(Variant::INT, "max_neighbors", PROPERTY_HINT_RANGE, "1,10000,1,or_greater"), "set_max_neighbors", "get_max_neighbors"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "time_horizon", PROPERTY_HINT_RANGE, "0.01,10,0.01,or_greater,suffix:s"), "set_time_horizon", "get_time_horizon"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "max_speed", PROPERTY_HINT_RANGE, "0.1,1000,0.01,or_greater,suffix:m/s"), "set_max_speed", "get_max_speed"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "ignore_y"), "set_ignore_y", "get_ignore_y"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "time_horizon_agents", PROPERTY_HINT_RANGE, "0.0,10,0.01,or_greater,suffix:s"), "set_time_horizon_agents", "get_time_horizon_agents"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "time_horizon_obstacles", PROPERTY_HINT_RANGE, "0.0,10,0.01,or_greater,suffix:s"), "set_time_horizon_obstacles", "get_time_horizon_obstacles"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "max_speed", PROPERTY_HINT_RANGE, "0.01,10000,0.01,or_greater,suffix:m/s"), "set_max_speed", "get_max_speed"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_3d_avoidance"), "set_use_3d_avoidance", "get_use_3d_avoidance"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "avoidance_layers", PROPERTY_HINT_LAYERS_AVOIDANCE), "set_avoidance_layers", "get_avoidance_layers"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "avoidance_mask", PROPERTY_HINT_LAYERS_AVOIDANCE), "set_avoidance_mask", "get_avoidance_mask"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "avoidance_priority", PROPERTY_HINT_RANGE, "0.0,1.0,0.01"), "set_avoidance_priority", "get_avoidance_priority"); ADD_SIGNAL(MethodInfo("path_changed")); ADD_SIGNAL(MethodInfo("target_reached")); @@ -146,6 +176,42 @@ void NavigationAgent3D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "debug_path_custom_point_size", PROPERTY_HINT_RANGE, "0,50,0.01,or_greater,suffix:px"), "set_debug_path_custom_point_size", "get_debug_path_custom_point_size"); } +#ifndef DISABLE_DEPRECATED +// Compatibility with Godot 4.0 beta 10 or below. +// Functions in block below all renamed or replaced in 4.0 beta 1X avoidance rework. +bool NavigationAgent3D::_set(const StringName &p_name, const Variant &p_value) { + if (p_name == "time_horizon") { + set_time_horizon_agents(p_value); + return true; + } + if (p_name == "target_location") { + set_target_position(p_value); + return true; + } + if (p_name == "agent_height_offset") { + set_path_height_offset(p_value); + return true; + } + return false; +} + +bool NavigationAgent3D::_get(const StringName &p_name, Variant &r_ret) const { + if (p_name == "time_horizon") { + r_ret = get_time_horizon_agents(); + return true; + } + if (p_name == "target_location") { + r_ret = get_target_position(); + return true; + } + if (p_name == "agent_height_offset") { + r_ret = get_path_height_offset(); + return true; + } + return false; +} +#endif // DISABLE_DEPRECATED + void NavigationAgent3D::_notification(int p_what) { switch (p_what) { case NOTIFICATION_POST_ENTER_TREE: { @@ -154,11 +220,16 @@ void NavigationAgent3D::_notification(int p_what) { set_agent_parent(get_parent()); set_physics_process_internal(true); + if (avoidance_enabled) { + NavigationServer3D::get_singleton()->agent_set_position(agent, agent_parent->get_global_transform().origin); + } + #ifdef DEBUG_ENABLED if (NavigationServer3D::get_singleton()->get_debug_enabled()) { debug_path_dirty = true; } #endif // DEBUG_ENABLED + } break; case NOTIFICATION_PARENTED: { @@ -211,10 +282,22 @@ void NavigationAgent3D::_notification(int p_what) { case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { if (agent_parent && target_position_submitted) { - if (avoidance_enabled) { - // agent_position on NavigationServer is avoidance only and has nothing to do with pathfinding - // no point in flooding NavigationServer queue with agent position updates that get send to the void if avoidance is not used - NavigationServer3D::get_singleton()->agent_set_position(agent, agent_parent->get_global_position()); + if (velocity_submitted) { + velocity_submitted = false; + if (avoidance_enabled) { + NavigationServer3D::get_singleton()->agent_set_position(agent, agent_parent->get_global_transform().origin); + if (!use_3d_avoidance) { + stored_y_velocity = velocity.y; + velocity.y = 0.0; + } + NavigationServer3D::get_singleton()->agent_set_velocity(agent, velocity); + } + } + if (velocity_forced_submitted) { + velocity_forced_submitted = false; + if (avoidance_enabled) { + NavigationServer3D::get_singleton()->agent_set_velocity_forced(agent, velocity_forced); + } } _check_distance_to_target(); } @@ -229,12 +312,14 @@ void NavigationAgent3D::_notification(int p_what) { NavigationAgent3D::NavigationAgent3D() { agent = NavigationServer3D::get_singleton()->agent_create(); + NavigationServer3D::get_singleton()->agent_set_neighbor_distance(agent, neighbor_distance); NavigationServer3D::get_singleton()->agent_set_max_neighbors(agent, max_neighbors); - NavigationServer3D::get_singleton()->agent_set_time_horizon(agent, time_horizon); + NavigationServer3D::get_singleton()->agent_set_time_horizon_agents(agent, time_horizon_agents); + NavigationServer3D::get_singleton()->agent_set_time_horizon_obstacles(agent, time_horizon_obstacles); NavigationServer3D::get_singleton()->agent_set_radius(agent, radius); + NavigationServer3D::get_singleton()->agent_set_height(agent, height); NavigationServer3D::get_singleton()->agent_set_max_speed(agent, max_speed); - NavigationServer3D::get_singleton()->agent_set_ignore_y(agent, ignore_y); // Preallocate query and result objects to improve performance. navigation_query = Ref<NavigationPathQueryParameters3D>(); @@ -243,6 +328,12 @@ NavigationAgent3D::NavigationAgent3D() { navigation_result = Ref<NavigationPathQueryResult3D>(); navigation_result.instantiate(); + set_avoidance_layers(avoidance_layers); + set_avoidance_mask(avoidance_mask); + set_avoidance_priority(avoidance_priority); + set_use_3d_avoidance(use_3d_avoidance); + set_avoidance_enabled(avoidance_enabled); + #ifdef DEBUG_ENABLED NavigationServer3D::get_singleton()->connect(SNAME("navigation_debug_changed"), callable_mp(this, &NavigationAgent3D::_navigation_debug_changed)); #endif // DEBUG_ENABLED @@ -274,9 +365,11 @@ void NavigationAgent3D::set_avoidance_enabled(bool p_enabled) { avoidance_enabled = p_enabled; if (avoidance_enabled) { - NavigationServer3D::get_singleton()->agent_set_callback(agent, callable_mp(this, &NavigationAgent3D::_avoidance_done)); + NavigationServer3D::get_singleton()->agent_set_avoidance_enabled(agent, true); + NavigationServer3D::get_singleton()->agent_set_avoidance_callback(agent, callable_mp(this, &NavigationAgent3D::_avoidance_done)); } else { - NavigationServer3D::get_singleton()->agent_set_callback(agent, Callable()); + NavigationServer3D::get_singleton()->agent_set_avoidance_enabled(agent, false); + NavigationServer3D::get_singleton()->agent_set_avoidance_callback(agent, Callable()); } } @@ -290,7 +383,7 @@ void NavigationAgent3D::set_agent_parent(Node *p_agent_parent) { } // remove agent from any avoidance map before changing parent or there will be leftovers on the RVO map - NavigationServer3D::get_singleton()->agent_set_callback(agent, Callable()); + NavigationServer3D::get_singleton()->agent_set_avoidance_callback(agent, Callable()); if (Object::cast_to<Node3D>(p_agent_parent) != nullptr) { // place agent on navigation map first or else the RVO agent callback creation fails silently later @@ -303,7 +396,7 @@ void NavigationAgent3D::set_agent_parent(Node *p_agent_parent) { // create new avoidance callback if enabled if (avoidance_enabled) { - NavigationServer3D::get_singleton()->agent_set_callback(agent, callable_mp(this, &NavigationAgent3D::_avoidance_done)); + NavigationServer3D::get_singleton()->agent_set_avoidance_callback(agent, callable_mp(this, &NavigationAgent3D::_avoidance_done)); } } else { agent_parent = nullptr; @@ -408,31 +501,32 @@ void NavigationAgent3D::set_target_desired_distance(real_t p_target_desired_dist } void NavigationAgent3D::set_radius(real_t p_radius) { + ERR_FAIL_COND_MSG(p_radius < 0.0, "Radius must be positive."); if (Math::is_equal_approx(radius, p_radius)) { return; } - radius = p_radius; NavigationServer3D::get_singleton()->agent_set_radius(agent, radius); } -void NavigationAgent3D::set_agent_height_offset(real_t p_agent_height_offset) { - if (Math::is_equal_approx(navigation_height_offset, p_agent_height_offset)) { +void NavigationAgent3D::set_height(real_t p_height) { + ERR_FAIL_COND_MSG(p_height < 0.0, "Height must be positive."); + if (Math::is_equal_approx(height, p_height)) { return; } - - navigation_height_offset = p_agent_height_offset; + height = p_height; + NavigationServer3D::get_singleton()->agent_set_height(agent, height); } -void NavigationAgent3D::set_ignore_y(bool p_ignore_y) { - if (ignore_y == p_ignore_y) { - return; - } - - ignore_y = p_ignore_y; +void NavigationAgent3D::set_path_height_offset(real_t p_path_height_offset) { + path_height_offset = p_path_height_offset; +} - NavigationServer3D::get_singleton()->agent_set_ignore_y(agent, ignore_y); +void NavigationAgent3D::set_use_3d_avoidance(bool p_use_3d_avoidance) { + use_3d_avoidance = p_use_3d_avoidance; + NavigationServer3D::get_singleton()->agent_set_use_3d_avoidance(agent, use_3d_avoidance); + notify_property_list_changed(); } void NavigationAgent3D::set_neighbor_distance(real_t p_distance) { @@ -455,21 +549,29 @@ void NavigationAgent3D::set_max_neighbors(int p_count) { NavigationServer3D::get_singleton()->agent_set_max_neighbors(agent, max_neighbors); } -void NavigationAgent3D::set_time_horizon(real_t p_time) { - if (Math::is_equal_approx(time_horizon, p_time)) { +void NavigationAgent3D::set_time_horizon_agents(real_t p_time_horizon) { + ERR_FAIL_COND_MSG(p_time_horizon < 0.0, "Time horizion must be positive."); + if (Math::is_equal_approx(time_horizon_agents, p_time_horizon)) { return; } + time_horizon_agents = p_time_horizon; + NavigationServer3D::get_singleton()->agent_set_time_horizon_agents(agent, time_horizon_agents); +} - time_horizon = p_time; - - NavigationServer3D::get_singleton()->agent_set_time_horizon(agent, time_horizon); +void NavigationAgent3D::set_time_horizon_obstacles(real_t p_time_horizon) { + ERR_FAIL_COND_MSG(p_time_horizon < 0.0, "Time horizion must be positive."); + if (Math::is_equal_approx(time_horizon_obstacles, p_time_horizon)) { + return; + } + time_horizon_obstacles = p_time_horizon; + NavigationServer3D::get_singleton()->agent_set_time_horizon_obstacles(agent, time_horizon_obstacles); } void NavigationAgent3D::set_max_speed(real_t p_max_speed) { + ERR_FAIL_COND_MSG(p_max_speed < 0.0, "Max speed must be positive."); if (Math::is_equal_approx(max_speed, p_max_speed)) { return; } - max_speed = p_max_speed; NavigationServer3D::get_singleton()->agent_set_max_speed(agent, max_speed); @@ -509,7 +611,7 @@ Vector3 NavigationAgent3D::get_next_path_position() { ERR_FAIL_COND_V_MSG(agent_parent == nullptr, Vector3(), "The agent has no parent."); return agent_parent->get_global_position(); } else { - return navigation_path[navigation_path_index] - Vector3(0, navigation_height_offset, 0); + return navigation_path[navigation_path_index] - Vector3(0, path_height_offset, 0); } } @@ -541,28 +643,26 @@ Vector3 NavigationAgent3D::get_final_position() { return navigation_path[navigation_path.size() - 1]; } -void NavigationAgent3D::set_velocity(Vector3 p_velocity) { +void NavigationAgent3D::set_velocity_forced(Vector3 p_velocity) { // Intentionally not checking for equality of the parameter. // We need to always submit the velocity to the navigation server, even when it is the same, in order to run avoidance every frame. // Revisit later when the navigation server can update avoidance without users resubmitting the velocity. - target_velocity = p_velocity; - velocity_submitted = true; + velocity_forced = p_velocity; + velocity_forced_submitted = true; +} - NavigationServer3D::get_singleton()->agent_set_target_velocity(agent, target_velocity); - NavigationServer3D::get_singleton()->agent_set_velocity(agent, prev_safe_velocity); +void NavigationAgent3D::set_velocity(const Vector3 p_velocity) { + velocity = p_velocity; + velocity_submitted = true; } void NavigationAgent3D::_avoidance_done(Vector3 p_new_velocity) { - prev_safe_velocity = p_new_velocity; - - if (!velocity_submitted) { - target_velocity = Vector3(); - return; + safe_velocity = p_new_velocity; + if (!use_3d_avoidance) { + safe_velocity.y = stored_y_velocity; } - velocity_submitted = false; - - emit_signal(SNAME("velocity_computed"), p_new_velocity); + emit_signal(SNAME("velocity_computed"), safe_velocity); } PackedStringArray NavigationAgent3D::get_configuration_warnings() const { @@ -585,9 +685,6 @@ void NavigationAgent3D::update_navigation() { if (!target_position_submitted) { return; } - if (update_frame_id == Engine::get_singleton()->get_physics_frames()) { - return; - } update_frame_id = Engine::get_singleton()->get_physics_frames(); @@ -607,8 +704,8 @@ void NavigationAgent3D::update_navigation() { Vector3 segment[2]; segment[0] = navigation_path[navigation_path_index - 1]; segment[1] = navigation_path[navigation_path_index]; - segment[0].y -= navigation_height_offset; - segment[1].y -= navigation_height_offset; + segment[0].y -= path_height_offset; + segment[1].y -= path_height_offset; Vector3 p = Geometry3D::get_closest_point_to_segment(origin, segment); if (origin.distance_to(p) >= path_max_distance) { // To faraway, reload path @@ -650,7 +747,7 @@ void NavigationAgent3D::update_navigation() { const TypedArray<RID> &navigation_path_rids = navigation_result->get_path_rids(); const Vector<int64_t> &navigation_path_owners = navigation_result->get_path_owner_ids(); - while (origin.distance_to(navigation_path[navigation_path_index] - Vector3(0, navigation_height_offset, 0)) < path_desired_distance) { + while (origin.distance_to(navigation_path[navigation_path_index] - Vector3(0, path_height_offset, 0)) < path_desired_distance) { Dictionary details; const Vector3 waypoint = navigation_path[navigation_path_index]; @@ -735,6 +832,71 @@ void NavigationAgent3D::_check_distance_to_target() { } } +void NavigationAgent3D::set_avoidance_layers(uint32_t p_layers) { + avoidance_layers = p_layers; + NavigationServer3D::get_singleton()->agent_set_avoidance_layers(get_rid(), avoidance_layers); +} + +uint32_t NavigationAgent3D::get_avoidance_layers() const { + return avoidance_layers; +} + +void NavigationAgent3D::set_avoidance_mask(uint32_t p_mask) { + avoidance_mask = p_mask; + NavigationServer3D::get_singleton()->agent_set_avoidance_mask(get_rid(), avoidance_mask); +} + +uint32_t NavigationAgent3D::get_avoidance_mask() const { + return avoidance_mask; +} + +void NavigationAgent3D::set_avoidance_layer_value(int p_layer_number, bool p_value) { + ERR_FAIL_COND_MSG(p_layer_number < 1, "Avoidance layer number must be between 1 and 32 inclusive."); + ERR_FAIL_COND_MSG(p_layer_number > 32, "Avoidance layer number must be between 1 and 32 inclusive."); + uint32_t avoidance_layers_new = get_avoidance_layers(); + if (p_value) { + avoidance_layers_new |= 1 << (p_layer_number - 1); + } else { + avoidance_layers_new &= ~(1 << (p_layer_number - 1)); + } + set_avoidance_layers(avoidance_layers_new); +} + +bool NavigationAgent3D::get_avoidance_layer_value(int p_layer_number) const { + ERR_FAIL_COND_V_MSG(p_layer_number < 1, false, "Avoidance layer number must be between 1 and 32 inclusive."); + ERR_FAIL_COND_V_MSG(p_layer_number > 32, false, "Avoidance layer number must be between 1 and 32 inclusive."); + return get_avoidance_layers() & (1 << (p_layer_number - 1)); +} + +void NavigationAgent3D::set_avoidance_mask_value(int p_mask_number, bool p_value) { + ERR_FAIL_COND_MSG(p_mask_number < 1, "Avoidance mask number must be between 1 and 32 inclusive."); + ERR_FAIL_COND_MSG(p_mask_number > 32, "Avoidance mask number must be between 1 and 32 inclusive."); + uint32_t mask = get_avoidance_mask(); + if (p_value) { + mask |= 1 << (p_mask_number - 1); + } else { + mask &= ~(1 << (p_mask_number - 1)); + } + set_avoidance_mask(mask); +} + +bool NavigationAgent3D::get_avoidance_mask_value(int p_mask_number) const { + ERR_FAIL_COND_V_MSG(p_mask_number < 1, false, "Avoidance mask number must be between 1 and 32 inclusive."); + ERR_FAIL_COND_V_MSG(p_mask_number > 32, false, "Avoidance mask number must be between 1 and 32 inclusive."); + return get_avoidance_mask() & (1 << (p_mask_number - 1)); +} + +void NavigationAgent3D::set_avoidance_priority(real_t p_priority) { + ERR_FAIL_COND_MSG(p_priority < 0.0, "Avoidance priority must be between 0.0 and 1.0 inclusive."); + ERR_FAIL_COND_MSG(p_priority > 1.0, "Avoidance priority must be between 0.0 and 1.0 inclusive."); + avoidance_priority = p_priority; + NavigationServer3D::get_singleton()->agent_set_avoidance_priority(get_rid(), p_priority); +} + +real_t NavigationAgent3D::get_avoidance_priority() const { + return avoidance_priority; +} + ////////DEBUG//////////////////////////////////////////////////////////// void NavigationAgent3D::set_debug_enabled(bool p_enabled) { diff --git a/scene/3d/navigation_agent_3d.h b/scene/3d/navigation_agent_3d.h index 16d702f9a5..b74e816d43 100644 --- a/scene/3d/navigation_agent_3d.h +++ b/scene/3d/navigation_agent_3d.h @@ -47,6 +47,10 @@ class NavigationAgent3D : public Node { RID map_override; bool avoidance_enabled = false; + bool use_3d_avoidance = false; + uint32_t avoidance_layers = 1; + uint32_t avoidance_mask = 1; + real_t avoidance_priority = 1.0; uint32_t navigation_layers = 1; NavigationPathQueryParameters3D::PathfindingAlgorithm pathfinding_algorithm = NavigationPathQueryParameters3D::PathfindingAlgorithm::PATHFINDING_ALGORITHM_ASTAR; NavigationPathQueryParameters3D::PathPostProcessing path_postprocessing = NavigationPathQueryParameters3D::PathPostProcessing::PATH_POSTPROCESSING_CORRIDORFUNNEL; @@ -54,24 +58,41 @@ class NavigationAgent3D : public Node { real_t path_desired_distance = 1.0; real_t target_desired_distance = 1.0; + real_t height = 1.0; real_t radius = 0.5; - real_t navigation_height_offset = 0.0; - bool ignore_y = true; + real_t path_height_offset = 0.0; real_t neighbor_distance = 50.0; int max_neighbors = 10; - real_t time_horizon = 1.0; + real_t time_horizon_agents = 1.0; + real_t time_horizon_obstacles = 0.0; real_t max_speed = 10.0; real_t path_max_distance = 5.0; Vector3 target_position; - bool target_position_submitted = false; + Ref<NavigationPathQueryParameters3D> navigation_query; Ref<NavigationPathQueryResult3D> navigation_result; int navigation_path_index = 0; + + // the velocity result of the avoidance simulation step + Vector3 safe_velocity; + + /// The submitted target velocity, sets the "wanted" rvo agent velocity on the next update + // this velocity is not guaranteed, the simulation will try to fulfil it if possible + // if other agents or obstacles interfere it will be changed accordingly + Vector3 velocity; bool velocity_submitted = false; - Vector3 prev_safe_velocity; - /// The submitted target velocity - Vector3 target_velocity; + + /// The submitted forced velocity, overrides the rvo agent velocity on the next update + // should only be used very intentionally and not every frame as it interferes with the simulation stability + Vector3 velocity_forced; + bool velocity_forced_submitted = false; + + // 2D avoidance has no y-axis. This stores and reapplies the y-axis velocity to the agent before and after the avoidance step. + // While not perfect it at least looks way better than agent's that clip through everything that is not a flat surface + float stored_y_velocity = 0.0; + + bool target_position_submitted = false; bool target_reached = false; bool navigation_finished = true; // No initialized on purpose @@ -89,23 +110,22 @@ class NavigationAgent3D : public Node { Ref<ArrayMesh> debug_path_mesh; Ref<StandardMaterial3D> debug_agent_path_line_custom_material; Ref<StandardMaterial3D> debug_agent_path_point_custom_material; - -private: - void _navigation_debug_changed(); - void _update_debug_path(); #endif // DEBUG_ENABLED protected: static void _bind_methods(); void _notification(int p_what); +#ifndef DISABLE_DEPRECATED + bool _set(const StringName &p_name, const Variant &p_value); + bool _get(const StringName &p_name, Variant &r_ret) const; +#endif // DISABLE_DEPRECATED + public: NavigationAgent3D(); virtual ~NavigationAgent3D(); - RID get_rid() const { - return agent; - } + RID get_rid() const { return agent; } void set_avoidance_enabled(bool p_enabled); bool get_avoidance_enabled() const; @@ -137,49 +157,37 @@ public: RID get_navigation_map() const; void set_path_desired_distance(real_t p_dd); - real_t get_path_desired_distance() const { - return path_desired_distance; - } + real_t get_path_desired_distance() const { return path_desired_distance; } void set_target_desired_distance(real_t p_dd); - real_t get_target_desired_distance() const { - return target_desired_distance; - } + real_t get_target_desired_distance() const { return target_desired_distance; } void set_radius(real_t p_radius); - real_t get_radius() const { - return radius; - } + real_t get_radius() const { return radius; } - void set_agent_height_offset(real_t p_hh); - real_t get_agent_height_offset() const { - return navigation_height_offset; - } + void set_height(real_t p_height); + real_t get_height() const { return height; } - void set_ignore_y(bool p_ignore_y); - bool get_ignore_y() const { - return ignore_y; - } + void set_path_height_offset(real_t p_path_height_offset); + real_t get_path_height_offset() const { return path_height_offset; } + + void set_use_3d_avoidance(bool p_use_3d_avoidance); + bool get_use_3d_avoidance() const { return use_3d_avoidance; } void set_neighbor_distance(real_t p_distance); - real_t get_neighbor_distance() const { - return neighbor_distance; - } + real_t get_neighbor_distance() const { return neighbor_distance; } void set_max_neighbors(int p_count); - int get_max_neighbors() const { - return max_neighbors; - } + int get_max_neighbors() const { return max_neighbors; } - void set_time_horizon(real_t p_time); - real_t get_time_horizon() const { - return time_horizon; - } + void set_time_horizon_agents(real_t p_time_horizon); + real_t get_time_horizon_agents() const { return time_horizon_agents; } + + void set_time_horizon_obstacles(real_t p_time_horizon); + real_t get_time_horizon_obstacles() const { return time_horizon_obstacles; } void set_max_speed(real_t p_max_speed); - real_t get_max_speed() const { - return max_speed; - } + real_t get_max_speed() const { return max_speed; } void set_path_max_distance(real_t p_pmd); real_t get_path_max_distance(); @@ -189,15 +197,11 @@ public: Vector3 get_next_path_position(); - Ref<NavigationPathQueryResult3D> get_current_navigation_result() const { - return navigation_result; - } - const Vector<Vector3> &get_current_navigation_path() const { - return navigation_result->get_path(); - } - int get_current_navigation_path_index() const { - return navigation_path_index; - } + Ref<NavigationPathQueryResult3D> get_current_navigation_result() const { return navigation_result; } + + const Vector<Vector3> &get_current_navigation_path() const { return navigation_result->get_path(); } + + int get_current_navigation_path_index() const { return navigation_path_index; } real_t distance_to_target() const; bool is_target_reached() const; @@ -205,11 +209,30 @@ public: bool is_navigation_finished(); Vector3 get_final_position(); - void set_velocity(Vector3 p_velocity); + void set_velocity(const Vector3 p_velocity); + Vector3 get_velocity() { return velocity; } + + void set_velocity_forced(const Vector3 p_velocity); + void _avoidance_done(Vector3 p_new_velocity); PackedStringArray get_configuration_warnings() const override; + void set_avoidance_layers(uint32_t p_layers); + uint32_t get_avoidance_layers() const; + + void set_avoidance_mask(uint32_t p_mask); + uint32_t get_avoidance_mask() const; + + void set_avoidance_layer_value(int p_layer_number, bool p_value); + bool get_avoidance_layer_value(int p_layer_number) const; + + void set_avoidance_mask_value(int p_mask_number, bool p_value); + bool get_avoidance_mask_value(int p_mask_number) const; + + void set_avoidance_priority(real_t p_priority); + real_t get_avoidance_priority() const; + void set_debug_enabled(bool p_enabled); bool get_debug_enabled() const; @@ -226,6 +249,11 @@ private: void update_navigation(); void _request_repath(); void _check_distance_to_target(); + +#ifdef DEBUG_ENABLED + void _navigation_debug_changed(); + void _update_debug_path(); +#endif // DEBUG_ENABLED }; #endif // NAVIGATION_AGENT_3D_H diff --git a/scene/3d/navigation_link_3d.cpp b/scene/3d/navigation_link_3d.cpp index 9c4b8e7905..2263d38d6c 100644 --- a/scene/3d/navigation_link_3d.cpp +++ b/scene/3d/navigation_link_3d.cpp @@ -45,7 +45,7 @@ void NavigationLink3D::_update_debug_mesh() { return; } - if (!NavigationServer3D::get_singleton()->get_debug_enabled()) { + if (!NavigationServer3D::get_singleton()->get_debug_navigation_enabled()) { if (debug_instance.is_valid()) { RS::get_singleton()->instance_set_visible(debug_instance, false); } @@ -141,6 +141,8 @@ void NavigationLink3D::_update_debug_mesh() { } else { RS::get_singleton()->instance_set_surface_override_material(debug_instance, 0, disabled_link_material->get_rid()); } + + RS::get_singleton()->instance_set_transform(debug_instance, current_global_transform); } #endif // DEBUG_ENABLED @@ -215,29 +217,37 @@ void NavigationLink3D::_notification(int p_what) { case NOTIFICATION_ENTER_TREE: { if (enabled) { NavigationServer3D::get_singleton()->link_set_map(link, get_world_3d()->get_navigation_map()); - - // Update global positions for the link. - Transform3D gt = get_global_transform(); - NavigationServer3D::get_singleton()->link_set_start_position(link, gt.xform(start_position)); - NavigationServer3D::get_singleton()->link_set_end_position(link, gt.xform(end_position)); } + current_global_transform = get_global_transform(); + NavigationServer3D::get_singleton()->link_set_start_position(link, current_global_transform.xform(start_position)); + NavigationServer3D::get_singleton()->link_set_end_position(link, current_global_transform.xform(end_position)); #ifdef DEBUG_ENABLED _update_debug_mesh(); #endif // DEBUG_ENABLED } break; + case NOTIFICATION_TRANSFORM_CHANGED: { - // Update global positions for the link. - Transform3D gt = get_global_transform(); - NavigationServer3D::get_singleton()->link_set_start_position(link, gt.xform(start_position)); - NavigationServer3D::get_singleton()->link_set_end_position(link, gt.xform(end_position)); + set_physics_process_internal(true); + } break; + case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { + set_physics_process_internal(false); + if (is_inside_tree()) { + Transform3D new_global_transform = get_global_transform(); + if (current_global_transform != new_global_transform) { + current_global_transform = new_global_transform; + NavigationServer3D::get_singleton()->link_set_start_position(link, current_global_transform.xform(start_position)); + NavigationServer3D::get_singleton()->link_set_end_position(link, current_global_transform.xform(end_position)); #ifdef DEBUG_ENABLED - if (is_inside_tree() && debug_instance.is_valid()) { - RS::get_singleton()->instance_set_transform(debug_instance, get_global_transform()); - } + if (debug_instance.is_valid()) { + RS::get_singleton()->instance_set_transform(debug_instance, current_global_transform); + } #endif // DEBUG_ENABLED + } + } } break; + case NOTIFICATION_EXIT_TREE: { NavigationServer3D::get_singleton()->link_set_map(link, RID()); @@ -359,8 +369,7 @@ void NavigationLink3D::set_start_position(Vector3 p_position) { return; } - Transform3D gt = get_global_transform(); - NavigationServer3D::get_singleton()->link_set_start_position(link, gt.xform(start_position)); + NavigationServer3D::get_singleton()->link_set_start_position(link, current_global_transform.xform(start_position)); #ifdef DEBUG_ENABLED _update_debug_mesh(); @@ -381,8 +390,7 @@ void NavigationLink3D::set_end_position(Vector3 p_position) { return; } - Transform3D gt = get_global_transform(); - NavigationServer3D::get_singleton()->link_set_end_position(link, gt.xform(end_position)); + NavigationServer3D::get_singleton()->link_set_end_position(link, current_global_transform.xform(end_position)); #ifdef DEBUG_ENABLED _update_debug_mesh(); diff --git a/scene/3d/navigation_link_3d.h b/scene/3d/navigation_link_3d.h index 991f45c85d..ec92fb9dd9 100644 --- a/scene/3d/navigation_link_3d.h +++ b/scene/3d/navigation_link_3d.h @@ -45,6 +45,8 @@ class NavigationLink3D : public Node3D { real_t enter_cost = 0.0; real_t travel_cost = 1.0; + Transform3D current_global_transform; + #ifdef DEBUG_ENABLED RID debug_instance; Ref<ArrayMesh> debug_mesh; diff --git a/scene/3d/navigation_obstacle_3d.cpp b/scene/3d/navigation_obstacle_3d.cpp index 14d93fb0e0..b44ebf837a 100644 --- a/scene/3d/navigation_obstacle_3d.cpp +++ b/scene/3d/navigation_obstacle_3d.cpp @@ -30,230 +30,524 @@ #include "navigation_obstacle_3d.h" +#include "core/math/geometry_2d.h" #include "scene/3d/collision_shape_3d.h" #include "scene/3d/physics_body_3d.h" #include "servers/navigation_server_3d.h" void NavigationObstacle3D::_bind_methods() { - ClassDB::bind_method(D_METHOD("get_rid"), &NavigationObstacle3D::get_rid); + ClassDB::bind_method(D_METHOD("get_obstacle_rid"), &NavigationObstacle3D::get_obstacle_rid); + ClassDB::bind_method(D_METHOD("get_agent_rid"), &NavigationObstacle3D::get_agent_rid); ClassDB::bind_method(D_METHOD("set_navigation_map", "navigation_map"), &NavigationObstacle3D::set_navigation_map); ClassDB::bind_method(D_METHOD("get_navigation_map"), &NavigationObstacle3D::get_navigation_map); - ClassDB::bind_method(D_METHOD("set_estimate_radius", "estimate_radius"), &NavigationObstacle3D::set_estimate_radius); - ClassDB::bind_method(D_METHOD("is_radius_estimated"), &NavigationObstacle3D::is_radius_estimated); ClassDB::bind_method(D_METHOD("set_radius", "radius"), &NavigationObstacle3D::set_radius); ClassDB::bind_method(D_METHOD("get_radius"), &NavigationObstacle3D::get_radius); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "estimate_radius"), "set_estimate_radius", "is_radius_estimated"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0.01,100,0.01"), "set_radius", "get_radius"); -} + ClassDB::bind_method(D_METHOD("set_height", "height"), &NavigationObstacle3D::set_height); + ClassDB::bind_method(D_METHOD("get_height"), &NavigationObstacle3D::get_height); -void NavigationObstacle3D::_validate_property(PropertyInfo &p_property) const { - if (p_property.name == "radius") { - if (estimate_radius) { - p_property.usage = PROPERTY_USAGE_NO_EDITOR; - } - } + ClassDB::bind_method(D_METHOD("set_velocity", "velocity"), &NavigationObstacle3D::set_velocity); + ClassDB::bind_method(D_METHOD("get_velocity"), &NavigationObstacle3D::get_velocity); + + ClassDB::bind_method(D_METHOD("set_vertices", "vertices"), &NavigationObstacle3D::set_vertices); + ClassDB::bind_method(D_METHOD("get_vertices"), &NavigationObstacle3D::get_vertices); + + ClassDB::bind_method(D_METHOD("set_avoidance_layers", "layers"), &NavigationObstacle3D::set_avoidance_layers); + ClassDB::bind_method(D_METHOD("get_avoidance_layers"), &NavigationObstacle3D::get_avoidance_layers); + + ClassDB::bind_method(D_METHOD("set_avoidance_layer_value", "layer_number", "value"), &NavigationObstacle3D::set_avoidance_layer_value); + ClassDB::bind_method(D_METHOD("get_avoidance_layer_value", "layer_number"), &NavigationObstacle3D::get_avoidance_layer_value); + + ClassDB::bind_method(D_METHOD("set_use_3d_avoidance", "enabled"), &NavigationObstacle3D::set_use_3d_avoidance); + ClassDB::bind_method(D_METHOD("get_use_3d_avoidance"), &NavigationObstacle3D::get_use_3d_avoidance); + + ADD_GROUP("Avoidance", "avoidance_"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "velocity", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_velocity", "get_velocity"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0.0,100,0.01,suffix:m"), "set_radius", "get_radius"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "height", PROPERTY_HINT_RANGE, "0.0,100,0.01,suffix:m"), "set_height", "get_height"); + ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR3_ARRAY, "vertices"), "set_vertices", "get_vertices"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "avoidance_layers", PROPERTY_HINT_LAYERS_AVOIDANCE), "set_avoidance_layers", "get_avoidance_layers"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_3d_avoidance"), "set_use_3d_avoidance", "get_use_3d_avoidance"); } void NavigationObstacle3D::_notification(int p_what) { switch (p_what) { case NOTIFICATION_POST_ENTER_TREE: { - set_agent_parent(get_parent()); + if (map_override.is_valid()) { + _update_map(map_override); + } else if (is_inside_tree()) { + _update_map(get_world_3d()->get_navigation_map()); + } else { + _update_map(RID()); + } + previous_transform = get_global_transform(); + // need to trigger map controlled agent assignment somehow for the fake_agent since obstacles use no callback like regular agents + NavigationServer3D::get_singleton()->agent_set_avoidance_enabled(fake_agent, radius > 0); + _update_position(get_global_transform().origin); set_physics_process_internal(true); +#ifdef DEBUG_ENABLED + if ((NavigationServer3D::get_singleton()->get_debug_avoidance_enabled()) && + (NavigationServer3D::get_singleton()->get_debug_navigation_avoidance_enable_obstacles_radius())) { + _update_fake_agent_radius_debug(); + _update_static_obstacle_debug(); + } +#endif // DEBUG_ENABLED } break; case NOTIFICATION_EXIT_TREE: { - set_agent_parent(nullptr); set_physics_process_internal(false); - } break; - - case NOTIFICATION_PARENTED: { - if (is_inside_tree() && (get_parent() != parent_node3d)) { - set_agent_parent(get_parent()); - set_physics_process_internal(true); + _update_map(RID()); +#ifdef DEBUG_ENABLED + if (fake_agent_radius_debug_instance.is_valid()) { + RS::get_singleton()->instance_set_visible(fake_agent_radius_debug_instance, false); } - } break; - - case NOTIFICATION_UNPARENTED: { - set_agent_parent(nullptr); - set_physics_process_internal(false); + if (static_obstacle_debug_instance.is_valid()) { + RS::get_singleton()->instance_set_visible(static_obstacle_debug_instance, false); + } +#endif // DEBUG_ENABLED } break; case NOTIFICATION_PAUSED: { - if (parent_node3d && !parent_node3d->can_process()) { - map_before_pause = NavigationServer3D::get_singleton()->agent_get_map(get_rid()); - NavigationServer3D::get_singleton()->agent_set_map(get_rid(), RID()); - } else if (parent_node3d && parent_node3d->can_process() && !(map_before_pause == RID())) { - NavigationServer3D::get_singleton()->agent_set_map(get_rid(), map_before_pause); + if (!can_process()) { + map_before_pause = map_current; + _update_map(RID()); + } else if (can_process() && !(map_before_pause == RID())) { + _update_map(map_before_pause); map_before_pause = RID(); } } break; case NOTIFICATION_UNPAUSED: { - if (parent_node3d && !parent_node3d->can_process()) { - map_before_pause = NavigationServer3D::get_singleton()->agent_get_map(get_rid()); - NavigationServer3D::get_singleton()->agent_set_map(get_rid(), RID()); - } else if (parent_node3d && parent_node3d->can_process() && !(map_before_pause == RID())) { - NavigationServer3D::get_singleton()->agent_set_map(get_rid(), map_before_pause); + if (!can_process()) { + map_before_pause = map_current; + _update_map(RID()); + } else if (can_process() && !(map_before_pause == RID())) { + _update_map(map_before_pause); map_before_pause = RID(); } } break; case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { - if (parent_node3d && parent_node3d->is_inside_tree()) { - NavigationServer3D::get_singleton()->agent_set_position(agent, parent_node3d->get_global_transform().origin); - - PhysicsBody3D *rigid = Object::cast_to<PhysicsBody3D>(get_parent()); - if (rigid) { - Vector3 v = rigid->get_linear_velocity(); - NavigationServer3D::get_singleton()->agent_set_velocity(agent, v); - NavigationServer3D::get_singleton()->agent_set_target_velocity(agent, v); + if (is_inside_tree()) { + _update_position(get_global_transform().origin); + + if (velocity_submitted) { + velocity_submitted = false; + // only update if there is a noticeable change, else the rvo agent preferred velocity stays the same + if (!previous_velocity.is_equal_approx(velocity)) { + NavigationServer3D::get_singleton()->agent_set_velocity(fake_agent, velocity); + } + previous_velocity = velocity; + } +#ifdef DEBUG_ENABLED + if (fake_agent_radius_debug_instance.is_valid() && radius > 0.0) { + RS::get_singleton()->instance_set_transform(fake_agent_radius_debug_instance, get_global_transform()); } + if (static_obstacle_debug_instance.is_valid() && get_vertices().size() > 0) { + RS::get_singleton()->instance_set_transform(static_obstacle_debug_instance, get_global_transform()); + } +#endif // DEBUG_ENABLED } } break; } } NavigationObstacle3D::NavigationObstacle3D() { - agent = NavigationServer3D::get_singleton()->agent_create(); - initialize_agent(); + obstacle = NavigationServer3D::get_singleton()->obstacle_create(); + fake_agent = NavigationServer3D::get_singleton()->agent_create(); + + // change properties of the fake agent so it can act as fake obstacle with a radius + NavigationServer3D::get_singleton()->agent_set_neighbor_distance(fake_agent, 0.0); + NavigationServer3D::get_singleton()->agent_set_max_neighbors(fake_agent, 0); + NavigationServer3D::get_singleton()->agent_set_time_horizon_agents(fake_agent, 0.0); + NavigationServer3D::get_singleton()->agent_set_time_horizon_obstacles(fake_agent, 0.0); + NavigationServer3D::get_singleton()->agent_set_max_speed(fake_agent, 0.0); + NavigationServer3D::get_singleton()->agent_set_avoidance_mask(fake_agent, 1); + NavigationServer3D::get_singleton()->agent_set_avoidance_priority(fake_agent, 1.0); + NavigationServer3D::get_singleton()->agent_set_avoidance_enabled(fake_agent, radius > 0); + + set_radius(radius); + set_height(height); + set_vertices(vertices); + set_avoidance_layers(avoidance_layers); + set_use_3d_avoidance(use_3d_avoidance); + +#ifdef DEBUG_ENABLED + NavigationServer3D::get_singleton()->connect("avoidance_debug_changed", callable_mp(this, &NavigationObstacle3D::_update_fake_agent_radius_debug)); + NavigationServer3D::get_singleton()->connect("avoidance_debug_changed", callable_mp(this, &NavigationObstacle3D::_update_static_obstacle_debug)); + _update_fake_agent_radius_debug(); + _update_static_obstacle_debug(); +#endif // DEBUG_ENABLED } NavigationObstacle3D::~NavigationObstacle3D() { ERR_FAIL_NULL(NavigationServer3D::get_singleton()); - NavigationServer3D::get_singleton()->free(agent); - agent = RID(); // Pointless -} -PackedStringArray NavigationObstacle3D::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + NavigationServer3D::get_singleton()->free(obstacle); + obstacle = RID(); + + NavigationServer3D::get_singleton()->free(fake_agent); + fake_agent = RID(); - if (!Object::cast_to<Node3D>(get_parent())) { - warnings.push_back(RTR("The NavigationObstacle3D only serves to provide collision avoidance to a Node3D inheriting parent object.")); +#ifdef DEBUG_ENABLED + NavigationServer3D::get_singleton()->disconnect("avoidance_debug_changed", callable_mp(this, &NavigationObstacle3D::_update_fake_agent_radius_debug)); + NavigationServer3D::get_singleton()->disconnect("avoidance_debug_changed", callable_mp(this, &NavigationObstacle3D::_update_static_obstacle_debug)); + if (fake_agent_radius_debug_instance.is_valid()) { + RenderingServer::get_singleton()->free(fake_agent_radius_debug_instance); + } + if (fake_agent_radius_debug_mesh.is_valid()) { + RenderingServer::get_singleton()->free(fake_agent_radius_debug_mesh->get_rid()); } - if (Object::cast_to<StaticBody3D>(get_parent())) { - warnings.push_back(RTR("The NavigationObstacle3D is intended for constantly moving bodies like CharacterBody3D or RigidBody3D as it creates only an RVO avoidance radius and does not follow scene geometry exactly." - "\nNot constantly moving or complete static objects should be (re)baked to a NavigationMesh so agents can not only avoid them but also move along those objects outline at high detail")); + if (static_obstacle_debug_instance.is_valid()) { + RenderingServer::get_singleton()->free(static_obstacle_debug_instance); + } + if (static_obstacle_debug_mesh.is_valid()) { + RenderingServer::get_singleton()->free(static_obstacle_debug_mesh->get_rid()); } +#endif // DEBUG_ENABLED +} - return warnings; +void NavigationObstacle3D::set_vertices(const Vector<Vector3> &p_vertices) { + vertices = p_vertices; + NavigationServer3D::get_singleton()->obstacle_set_vertices(obstacle, vertices); +#ifdef DEBUG_ENABLED + _update_static_obstacle_debug(); +#endif // DEBUG_ENABLED } -void NavigationObstacle3D::initialize_agent() { - NavigationServer3D::get_singleton()->agent_set_neighbor_distance(agent, 0.0); - NavigationServer3D::get_singleton()->agent_set_max_neighbors(agent, 0); - NavigationServer3D::get_singleton()->agent_set_time_horizon(agent, 0.0); - NavigationServer3D::get_singleton()->agent_set_max_speed(agent, 0.0); +void NavigationObstacle3D::set_navigation_map(RID p_navigation_map) { + if (map_override == p_navigation_map) { + return; + } + map_override = p_navigation_map; + _update_map(map_override); } -void NavigationObstacle3D::reevaluate_agent_radius() { - if (!estimate_radius) { - NavigationServer3D::get_singleton()->agent_set_radius(agent, radius); - } else if (parent_node3d && parent_node3d->is_inside_tree()) { - NavigationServer3D::get_singleton()->agent_set_radius(agent, estimate_agent_radius()); +RID NavigationObstacle3D::get_navigation_map() const { + if (map_override.is_valid()) { + return map_override; + } else if (is_inside_tree()) { + return get_world_3d()->get_navigation_map(); } + return RID(); } -real_t NavigationObstacle3D::estimate_agent_radius() const { - if (parent_node3d && parent_node3d->is_inside_tree()) { - // Estimate the radius of this physics body - real_t max_radius = 0.0; - for (int i(0); i < parent_node3d->get_child_count(); i++) { - // For each collision shape - CollisionShape3D *cs = Object::cast_to<CollisionShape3D>(parent_node3d->get_child(i)); - if (cs && cs->is_inside_tree()) { - // Take the distance between the Body center to the shape center - real_t r = cs->get_transform().origin.length(); - if (cs->get_shape().is_valid()) { - // and add the enclosing shape radius - r += cs->get_shape()->get_enclosing_radius(); - } - Vector3 s = cs->get_global_transform().basis.get_scale(); - r *= MAX(s.x, MAX(s.y, s.z)); - // Takes the biggest radius - max_radius = MAX(max_radius, r); - } else if (cs && !cs->is_inside_tree()) { - WARN_PRINT("A CollisionShape3D of the NavigationObstacle3D parent node was not inside the SceneTree when estimating the obstacle radius." - "\nMove the NavigationObstacle3D to a child position below any CollisionShape3D node of the parent node so the CollisionShape3D is already inside the SceneTree."); - } - } +void NavigationObstacle3D::set_radius(real_t p_radius) { + ERR_FAIL_COND_MSG(p_radius < 0.0, "Radius must be positive."); + if (Math::is_equal_approx(radius, p_radius)) { + return; + } - Vector3 s = parent_node3d->get_global_transform().basis.get_scale(); - max_radius *= MAX(s.x, MAX(s.y, s.z)); + radius = p_radius; + NavigationServer3D::get_singleton()->agent_set_avoidance_enabled(fake_agent, radius > 0.0); + NavigationServer3D::get_singleton()->agent_set_radius(fake_agent, radius); - if (max_radius > 0.0) { - return max_radius; - } - } - return 1.0; // Never a 0 radius +#ifdef DEBUG_ENABLED + _update_fake_agent_radius_debug(); +#endif // DEBUG_ENABLED } -void NavigationObstacle3D::set_agent_parent(Node *p_agent_parent) { - if (parent_node3d == p_agent_parent) { +void NavigationObstacle3D::set_height(real_t p_height) { + ERR_FAIL_COND_MSG(p_height < 0.0, "Height must be positive."); + if (Math::is_equal_approx(height, p_height)) { return; } - if (Object::cast_to<Node3D>(p_agent_parent) != nullptr) { - parent_node3d = Object::cast_to<Node3D>(p_agent_parent); - if (map_override.is_valid()) { - NavigationServer3D::get_singleton()->agent_set_map(get_rid(), map_override); - } else { - NavigationServer3D::get_singleton()->agent_set_map(get_rid(), parent_node3d->get_world_3d()->get_navigation_map()); - } - // Need to register Callback as obstacle requires a valid Callback to be added to avoidance simulation. - NavigationServer3D::get_singleton()->agent_set_callback(get_rid(), callable_mp(this, &NavigationObstacle3D::_avoidance_done)); - reevaluate_agent_radius(); + height = p_height; + NavigationServer3D::get_singleton()->obstacle_set_height(obstacle, height); + NavigationServer3D::get_singleton()->agent_set_height(fake_agent, height); +#ifdef DEBUG_ENABLED + _update_static_obstacle_debug(); +#endif // DEBUG_ENABLED +} + +void NavigationObstacle3D::set_avoidance_layers(uint32_t p_layers) { + avoidance_layers = p_layers; + NavigationServer3D::get_singleton()->obstacle_set_avoidance_layers(obstacle, avoidance_layers); + NavigationServer3D::get_singleton()->agent_set_avoidance_layers(fake_agent, avoidance_layers); +} + +uint32_t NavigationObstacle3D::get_avoidance_layers() const { + return avoidance_layers; +} + +void NavigationObstacle3D::set_avoidance_layer_value(int p_layer_number, bool p_value) { + ERR_FAIL_COND_MSG(p_layer_number < 1, "Avoidance layer number must be between 1 and 32 inclusive."); + ERR_FAIL_COND_MSG(p_layer_number > 32, "Avoidance layer number must be between 1 and 32 inclusive."); + uint32_t avoidance_layers_new = get_avoidance_layers(); + if (p_value) { + avoidance_layers_new |= 1 << (p_layer_number - 1); } else { - parent_node3d = nullptr; - NavigationServer3D::get_singleton()->agent_set_map(get_rid(), RID()); - NavigationServer3D::get_singleton()->agent_set_callback(agent, Callable()); + avoidance_layers_new &= ~(1 << (p_layer_number - 1)); } + set_avoidance_layers(avoidance_layers_new); } -void NavigationObstacle3D::_avoidance_done(Vector3 p_new_velocity) { - // Dummy function as obstacle requires a valid Callback to be added to avoidance simulation. +bool NavigationObstacle3D::get_avoidance_layer_value(int p_layer_number) const { + ERR_FAIL_COND_V_MSG(p_layer_number < 1, false, "Avoidance layer number must be between 1 and 32 inclusive."); + ERR_FAIL_COND_V_MSG(p_layer_number > 32, false, "Avoidance layer number must be between 1 and 32 inclusive."); + return get_avoidance_layers() & (1 << (p_layer_number - 1)); } -void NavigationObstacle3D::set_navigation_map(RID p_navigation_map) { - if (map_override == p_navigation_map) { - return; - } +void NavigationObstacle3D::set_use_3d_avoidance(bool p_use_3d_avoidance) { + use_3d_avoidance = p_use_3d_avoidance; + _update_use_3d_avoidance(use_3d_avoidance); + notify_property_list_changed(); +} - map_override = p_navigation_map; +void NavigationObstacle3D::set_velocity(const Vector3 p_velocity) { + velocity = p_velocity; + velocity_submitted = true; +} - NavigationServer3D::get_singleton()->agent_set_map(agent, map_override); +void NavigationObstacle3D::_update_map(RID p_map) { + // avoidance obstacles are 2D only, no point keeping them on the map while 3D is used + if (use_3d_avoidance) { + NavigationServer3D::get_singleton()->obstacle_set_map(obstacle, RID()); + } else { + NavigationServer3D::get_singleton()->obstacle_set_map(obstacle, p_map); + } + NavigationServer3D::get_singleton()->agent_set_map(fake_agent, p_map); + map_current = p_map; } -RID NavigationObstacle3D::get_navigation_map() const { - if (map_override.is_valid()) { - return map_override; - } else if (parent_node3d != nullptr) { - return parent_node3d->get_world_3d()->get_navigation_map(); +void NavigationObstacle3D::_update_position(const Vector3 p_position) { + if (vertices.size() > 0) { + NavigationServer3D::get_singleton()->obstacle_set_position(obstacle, p_position); } - return RID(); + if (radius > 0.0) { + NavigationServer3D::get_singleton()->agent_set_position(fake_agent, p_position); + } +} + +void NavigationObstacle3D::_update_use_3d_avoidance(bool p_use_3d_avoidance) { + NavigationServer3D::get_singleton()->agent_set_use_3d_avoidance(fake_agent, use_3d_avoidance); + _update_map(map_current); } -void NavigationObstacle3D::set_estimate_radius(bool p_estimate_radius) { - if (estimate_radius == p_estimate_radius) { +#ifdef DEBUG_ENABLED +void NavigationObstacle3D::_update_fake_agent_radius_debug() { + bool is_debug_enabled = false; + if (Engine::get_singleton()->is_editor_hint()) { + is_debug_enabled = true; + } else if (NavigationServer3D::get_singleton()->get_debug_enabled() && + NavigationServer3D::get_singleton()->get_debug_avoidance_enabled() && + NavigationServer3D::get_singleton()->get_debug_navigation_avoidance_enable_obstacles_radius()) { + is_debug_enabled = true; + } + + if (is_debug_enabled == false) { + if (fake_agent_radius_debug_instance.is_valid()) { + RS::get_singleton()->instance_set_visible(fake_agent_radius_debug_instance, false); + } return; } - estimate_radius = p_estimate_radius; + if (!fake_agent_radius_debug_instance.is_valid()) { + fake_agent_radius_debug_instance = RenderingServer::get_singleton()->instance_create(); + } + if (!fake_agent_radius_debug_mesh.is_valid()) { + fake_agent_radius_debug_mesh = Ref<ArrayMesh>(memnew(ArrayMesh)); + } + fake_agent_radius_debug_mesh->clear_surfaces(); - notify_property_list_changed(); - reevaluate_agent_radius(); + Vector<Vector3> face_vertex_array; + Vector<int> face_indices_array; + + int i, j, prevrow, thisrow, point; + float x, y, z; + + int rings = 16; + int radial_segments = 32; + + point = 0; + + thisrow = 0; + prevrow = 0; + for (j = 0; j <= (rings + 1); j++) { + float v = j; + float w; + + v /= (rings + 1); + w = sin(Math_PI * v); + y = (radius)*cos(Math_PI * v); + + for (i = 0; i <= radial_segments; i++) { + float u = i; + u /= radial_segments; + + x = sin(u * Math_TAU); + z = cos(u * Math_TAU); + + Vector3 p = Vector3(x * radius * w, y, z * radius * w); + face_vertex_array.push_back(p); + + point++; + + if (i > 0 && j > 0) { + face_indices_array.push_back(prevrow + i - 1); + face_indices_array.push_back(prevrow + i); + face_indices_array.push_back(thisrow + i - 1); + + face_indices_array.push_back(prevrow + i); + face_indices_array.push_back(thisrow + i); + face_indices_array.push_back(thisrow + i - 1); + }; + }; + + prevrow = thisrow; + thisrow = point; + }; + + Array face_mesh_array; + face_mesh_array.resize(Mesh::ARRAY_MAX); + face_mesh_array[Mesh::ARRAY_VERTEX] = face_vertex_array; + face_mesh_array[Mesh::ARRAY_INDEX] = face_indices_array; + + fake_agent_radius_debug_mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, face_mesh_array); + Ref<StandardMaterial3D> face_material = NavigationServer3D::get_singleton()->get_debug_navigation_avoidance_obstacles_radius_material(); + fake_agent_radius_debug_mesh->surface_set_material(0, face_material); + + RS::get_singleton()->instance_set_base(fake_agent_radius_debug_instance, fake_agent_radius_debug_mesh->get_rid()); + if (is_inside_tree()) { + RS::get_singleton()->instance_set_scenario(fake_agent_radius_debug_instance, get_world_3d()->get_scenario()); + RS::get_singleton()->instance_set_visible(fake_agent_radius_debug_instance, is_visible_in_tree()); + } } +#endif // DEBUG_ENABLED + +#ifdef DEBUG_ENABLED +void NavigationObstacle3D::_update_static_obstacle_debug() { + bool is_debug_enabled = false; + if (Engine::get_singleton()->is_editor_hint()) { + is_debug_enabled = true; + } else if (NavigationServer3D::get_singleton()->get_debug_enabled() && + NavigationServer3D::get_singleton()->get_debug_avoidance_enabled() && + NavigationServer3D::get_singleton()->get_debug_navigation_avoidance_enable_obstacles_static()) { + is_debug_enabled = true; + } -void NavigationObstacle3D::set_radius(real_t p_radius) { - ERR_FAIL_COND_MSG(p_radius <= 0.0, "Radius must be greater than 0."); - if (Math::is_equal_approx(radius, p_radius)) { + if (is_debug_enabled == false) { + if (static_obstacle_debug_instance.is_valid()) { + RS::get_singleton()->instance_set_visible(static_obstacle_debug_instance, false); + } return; } - radius = p_radius; + if (vertices.size() < 3) { + if (static_obstacle_debug_instance.is_valid()) { + RS::get_singleton()->instance_set_visible(static_obstacle_debug_instance, false); + } + return; + } + + if (!static_obstacle_debug_instance.is_valid()) { + static_obstacle_debug_instance = RenderingServer::get_singleton()->instance_create(); + } + if (!static_obstacle_debug_mesh.is_valid()) { + static_obstacle_debug_mesh = Ref<ArrayMesh>(memnew(ArrayMesh)); + } + static_obstacle_debug_mesh->clear_surfaces(); + + Vector<Vector2> polygon_2d_vertices; + polygon_2d_vertices.resize(vertices.size()); + Vector2 *polygon_2d_vertices_ptr = polygon_2d_vertices.ptrw(); - reevaluate_agent_radius(); + for (int i = 0; i < vertices.size(); ++i) { + Vector3 obstacle_vertex = vertices[i]; + Vector2 obstacle_vertex_2d = Vector2(obstacle_vertex.x, obstacle_vertex.z); + polygon_2d_vertices_ptr[i] = obstacle_vertex_2d; + } + + Vector<int> triangulated_polygon_2d_indices = Geometry2D::triangulate_polygon(polygon_2d_vertices); + + if (triangulated_polygon_2d_indices.is_empty()) { + // failed triangulation + return; + } + + bool obstacle_pushes_inward = Geometry2D::is_polygon_clockwise(polygon_2d_vertices); + + Vector<Vector3> face_vertex_array; + Vector<int> face_indices_array; + + face_vertex_array.resize(polygon_2d_vertices.size()); + face_indices_array.resize(triangulated_polygon_2d_indices.size()); + + Vector3 *face_vertex_array_ptr = face_vertex_array.ptrw(); + int *face_indices_array_ptr = face_indices_array.ptrw(); + + for (int i = 0; i < triangulated_polygon_2d_indices.size(); ++i) { + int vertex_index = triangulated_polygon_2d_indices[i]; + const Vector2 &vertex_2d = polygon_2d_vertices[vertex_index]; + Vector3 vertex_3d = Vector3(vertex_2d.x, 0.0, vertex_2d.y); + face_vertex_array_ptr[vertex_index] = vertex_3d; + face_indices_array_ptr[i] = vertex_index; + } + + Array face_mesh_array; + face_mesh_array.resize(Mesh::ARRAY_MAX); + face_mesh_array[Mesh::ARRAY_VERTEX] = face_vertex_array; + face_mesh_array[Mesh::ARRAY_INDEX] = face_indices_array; + + static_obstacle_debug_mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, face_mesh_array); + + Vector<Vector3> edge_vertex_array; + + for (int i = 0; i < polygon_2d_vertices.size(); ++i) { + int from_index = i - 1; + int to_index = i; + + if (i == 0) { + from_index = polygon_2d_vertices.size() - 1; + } + + const Vector2 &vertex_2d_from = polygon_2d_vertices[from_index]; + const Vector2 &vertex_2d_to = polygon_2d_vertices[to_index]; + + Vector3 vertex_3d_ground_from = Vector3(vertex_2d_from.x, 0.0, vertex_2d_from.y); + Vector3 vertex_3d_ground_to = Vector3(vertex_2d_to.x, 0.0, vertex_2d_to.y); + + edge_vertex_array.push_back(vertex_3d_ground_from); + edge_vertex_array.push_back(vertex_3d_ground_to); + + Vector3 vertex_3d_height_from = Vector3(vertex_2d_from.x, height, vertex_2d_from.y); + Vector3 vertex_3d_height_to = Vector3(vertex_2d_to.x, height, vertex_2d_to.y); + + edge_vertex_array.push_back(vertex_3d_height_from); + edge_vertex_array.push_back(vertex_3d_height_to); + + edge_vertex_array.push_back(vertex_3d_ground_from); + edge_vertex_array.push_back(vertex_3d_height_from); + } + + Array edge_mesh_array; + edge_mesh_array.resize(Mesh::ARRAY_MAX); + edge_mesh_array[Mesh::ARRAY_VERTEX] = edge_vertex_array; + + static_obstacle_debug_mesh->add_surface_from_arrays(Mesh::PRIMITIVE_LINES, edge_mesh_array); + + Ref<StandardMaterial3D> face_material; + Ref<StandardMaterial3D> edge_material; + + if (obstacle_pushes_inward) { + face_material = NavigationServer3D::get_singleton()->get_debug_navigation_avoidance_static_obstacle_pushin_face_material(); + edge_material = NavigationServer3D::get_singleton()->get_debug_navigation_avoidance_static_obstacle_pushin_edge_material(); + } else { + face_material = NavigationServer3D::get_singleton()->get_debug_navigation_avoidance_static_obstacle_pushout_face_material(); + edge_material = NavigationServer3D::get_singleton()->get_debug_navigation_avoidance_static_obstacle_pushout_edge_material(); + } + + static_obstacle_debug_mesh->surface_set_material(0, face_material); + static_obstacle_debug_mesh->surface_set_material(1, edge_material); + + RS::get_singleton()->instance_set_base(static_obstacle_debug_instance, static_obstacle_debug_mesh->get_rid()); + if (is_inside_tree()) { + RS::get_singleton()->instance_set_scenario(static_obstacle_debug_instance, get_world_3d()->get_scenario()); + RS::get_singleton()->instance_set_visible(static_obstacle_debug_instance, is_visible_in_tree()); + } } +#endif // DEBUG_ENABLED diff --git a/scene/3d/navigation_obstacle_3d.h b/scene/3d/navigation_obstacle_3d.h index c5a9d737f6..603d22cf5e 100644 --- a/scene/3d/navigation_obstacle_3d.h +++ b/scene/3d/navigation_obstacle_3d.h @@ -33,53 +33,83 @@ #include "scene/3d/node_3d.h" -class NavigationObstacle3D : public Node { - GDCLASS(NavigationObstacle3D, Node); +class NavigationObstacle3D : public Node3D { + GDCLASS(NavigationObstacle3D, Node3D); - Node3D *parent_node3d = nullptr; - - RID agent; + RID obstacle; RID map_before_pause; RID map_override; + RID map_current; + + real_t height = 1.0; + real_t radius = 0.0; + + Vector<Vector3> vertices; + + RID fake_agent; + uint32_t avoidance_layers = 1; + + bool use_3d_avoidance = false; + + Transform3D previous_transform; + + Vector3 velocity; + Vector3 previous_velocity; + bool velocity_submitted = false; + +#ifdef DEBUG_ENABLED + RID fake_agent_radius_debug_instance; + Ref<ArrayMesh> fake_agent_radius_debug_mesh; - bool estimate_radius = true; - real_t radius = 1.0; + RID static_obstacle_debug_instance; + Ref<ArrayMesh> static_obstacle_debug_mesh; + +private: + void _update_fake_agent_radius_debug(); + void _update_static_obstacle_debug(); +#endif // DEBUG_ENABLED protected: static void _bind_methods(); - void _validate_property(PropertyInfo &p_property) const; void _notification(int p_what); public: NavigationObstacle3D(); virtual ~NavigationObstacle3D(); - RID get_rid() const { - return agent; - } - - void set_agent_parent(Node *p_agent_parent); + RID get_obstacle_rid() const { return obstacle; } + RID get_agent_rid() const { return fake_agent; } void set_navigation_map(RID p_navigation_map); RID get_navigation_map() const; - void set_estimate_radius(bool p_estimate_radius); - bool is_radius_estimated() const { - return estimate_radius; - } void set_radius(real_t p_radius); - real_t get_radius() const { - return radius; - } + real_t get_radius() const { return radius; } + + void set_height(real_t p_height); + real_t get_height() const { return height; } + + void set_vertices(const Vector<Vector3> &p_vertices); + const Vector<Vector3> &get_vertices() const { return vertices; }; + + void set_avoidance_layers(uint32_t p_layers); + uint32_t get_avoidance_layers() const; + + void set_avoidance_layer_value(int p_layer_number, bool p_value); + bool get_avoidance_layer_value(int p_layer_number) const; + + void set_use_3d_avoidance(bool p_use_3d_avoidance); + bool get_use_3d_avoidance() const { return use_3d_avoidance; } - PackedStringArray get_configuration_warnings() const override; + void set_velocity(const Vector3 p_velocity); + Vector3 get_velocity() const { return velocity; }; void _avoidance_done(Vector3 p_new_velocity); // Dummy private: - void initialize_agent(); - void reevaluate_agent_radius(); - real_t estimate_agent_radius() const; + void _update_map(RID p_map); + void _update_position(const Vector3 p_position); + void _update_use_3d_avoidance(bool p_use_3d_avoidance); }; #endif // NAVIGATION_OBSTACLE_3D_H diff --git a/scene/3d/navigation_region_3d.cpp b/scene/3d/navigation_region_3d.cpp index 85633c1dc7..b775ef94cb 100644 --- a/scene/3d/navigation_region_3d.cpp +++ b/scene/3d/navigation_region_3d.cpp @@ -157,9 +157,11 @@ void NavigationRegion3D::_notification(int p_what) { if (enabled) { NavigationServer3D::get_singleton()->region_set_map(region, get_world_3d()->get_navigation_map()); } + current_global_transform = get_global_transform(); + NavigationServer3D::get_singleton()->region_set_transform(region, current_global_transform); #ifdef DEBUG_ENABLED - if (NavigationServer3D::get_singleton()->get_debug_enabled()) { + if (NavigationServer3D::get_singleton()->get_debug_navigation_enabled()) { _update_debug_mesh(); } #endif // DEBUG_ENABLED @@ -167,14 +169,24 @@ void NavigationRegion3D::_notification(int p_what) { } break; case NOTIFICATION_TRANSFORM_CHANGED: { - NavigationServer3D::get_singleton()->region_set_transform(region, get_global_transform()); + set_physics_process_internal(true); + } break; + + case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { + set_physics_process_internal(false); + if (is_inside_tree()) { + Transform3D new_global_transform = get_global_transform(); + if (current_global_transform != new_global_transform) { + current_global_transform = new_global_transform; + NavigationServer3D::get_singleton()->region_set_transform(region, current_global_transform); #ifdef DEBUG_ENABLED - if (is_inside_tree() && debug_instance.is_valid()) { - RS::get_singleton()->instance_set_transform(debug_instance, get_global_transform()); - } + if (debug_instance.is_valid()) { + RS::get_singleton()->instance_set_transform(debug_instance, current_global_transform); + } #endif // DEBUG_ENABLED - + } + } } break; case NOTIFICATION_EXIT_TREE: { @@ -210,7 +222,7 @@ void NavigationRegion3D::set_navigation_mesh(const Ref<NavigationMesh> &p_naviga NavigationServer3D::get_singleton()->region_set_navigation_mesh(region, p_navigation_mesh); #ifdef DEBUG_ENABLED - if (is_inside_tree() && NavigationServer3D::get_singleton()->get_debug_enabled()) { + if (is_inside_tree() && NavigationServer3D::get_singleton()->get_debug_navigation_enabled()) { if (navigation_mesh.is_valid()) { _update_debug_mesh(); _update_debug_edge_connections_mesh(); @@ -413,7 +425,7 @@ void NavigationRegion3D::_update_debug_mesh() { return; } - if (!NavigationServer3D::get_singleton()->get_debug_enabled()) { + if (!NavigationServer3D::get_singleton()->get_debug_navigation_enabled()) { if (debug_instance.is_valid()) { RS::get_singleton()->instance_set_visible(debug_instance, false); } @@ -552,6 +564,7 @@ void NavigationRegion3D::_update_debug_mesh() { RS::get_singleton()->instance_set_base(debug_instance, debug_mesh->get_rid()); if (is_inside_tree()) { RS::get_singleton()->instance_set_scenario(debug_instance, get_world_3d()->get_scenario()); + RS::get_singleton()->instance_set_transform(debug_instance, current_global_transform); RS::get_singleton()->instance_set_visible(debug_instance, is_visible_in_tree()); } if (!is_enabled()) { @@ -578,7 +591,7 @@ void NavigationRegion3D::_update_debug_mesh() { #ifdef DEBUG_ENABLED void NavigationRegion3D::_update_debug_edge_connections_mesh() { - if (!NavigationServer3D::get_singleton()->get_debug_enabled()) { + if (!NavigationServer3D::get_singleton()->get_debug_navigation_enabled()) { if (debug_edge_connections_instance.is_valid()) { RS::get_singleton()->instance_set_visible(debug_edge_connections_instance, false); } diff --git a/scene/3d/navigation_region_3d.h b/scene/3d/navigation_region_3d.h index d4045ae1ac..4732c340ba 100644 --- a/scene/3d/navigation_region_3d.h +++ b/scene/3d/navigation_region_3d.h @@ -44,6 +44,8 @@ class NavigationRegion3D : public Node3D { real_t travel_cost = 1.0; Ref<NavigationMesh> navigation_mesh; + Transform3D current_global_transform; + Thread bake_thread; void _navigation_changed(); diff --git a/scene/3d/skeleton_3d.cpp b/scene/3d/skeleton_3d.cpp index 1b46879079..61e61ead5e 100644 --- a/scene/3d/skeleton_3d.cpp +++ b/scene/3d/skeleton_3d.cpp @@ -226,6 +226,11 @@ void Skeleton3D::_update_process_order() { void Skeleton3D::_notification(int p_what) { switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + if (dirty) { + notification(NOTIFICATION_UPDATE_SKELETON); + } + } break; case NOTIFICATION_UPDATE_SKELETON: { RenderingServer *rs = RenderingServer::get_singleton(); Bone *bonesptr = bones.ptrw(); @@ -629,7 +634,9 @@ void Skeleton3D::_make_dirty() { return; } - MessageQueue::get_singleton()->push_notification(this, NOTIFICATION_UPDATE_SKELETON); + if (is_inside_tree()) { + notify_deferred_thread_group(NOTIFICATION_UPDATE_SKELETON); + } dirty = true; } diff --git a/scene/gui/item_list.cpp b/scene/gui/item_list.cpp index 30f161d27a..cafc7c5e51 100644 --- a/scene/gui/item_list.cpp +++ b/scene/gui/item_list.cpp @@ -597,6 +597,7 @@ void ItemList::set_fixed_icon_size(const Size2i &p_size) { fixed_icon_size = p_size; queue_redraw(); + shape_changed = true; } Size2i ItemList::get_fixed_icon_size() const { @@ -656,13 +657,6 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) { if (mb.is_valid() && mb->is_pressed()) { search_string = ""; //any mousepress cancels - Vector2 pos = mb->get_position(); - pos -= theme_cache.panel_style->get_offset(); - pos.y += scroll_bar->get_value(); - - if (is_layout_rtl()) { - pos.x = get_size().width - pos.x; - } int closest = get_item_at_position(mb->get_position(), true); diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp index 18d00d519c..b8fc8004c9 100644 --- a/scene/gui/tree.cpp +++ b/scene/gui/tree.cpp @@ -748,7 +748,7 @@ TreeItem *TreeItem::get_first_child() const { return first_child; } -TreeItem *TreeItem::_get_prev_visible(bool p_wrap) { +TreeItem *TreeItem::_get_prev_in_tree(bool p_wrap, bool p_include_invisible) { TreeItem *current = this; TreeItem *prev_item = current->get_prev(); @@ -771,7 +771,7 @@ TreeItem *TreeItem::_get_prev_visible(bool p_wrap) { } } else { current = prev_item; - while (!current->collapsed && current->first_child) { + while ((!current->collapsed || p_include_invisible) && current->first_child) { //go to the very end current = current->first_child; @@ -786,9 +786,9 @@ TreeItem *TreeItem::_get_prev_visible(bool p_wrap) { TreeItem *TreeItem::get_prev_visible(bool p_wrap) { TreeItem *loop = this; - TreeItem *prev_item = this->_get_prev_visible(p_wrap); + TreeItem *prev_item = this->_get_prev_in_tree(p_wrap); while (prev_item && !prev_item->is_visible()) { - prev_item = prev_item->_get_prev_visible(p_wrap); + prev_item = prev_item->_get_prev_in_tree(p_wrap); if (prev_item == loop) { // Check that we haven't looped all the way around to the start. prev_item = nullptr; @@ -798,10 +798,10 @@ TreeItem *TreeItem::get_prev_visible(bool p_wrap) { return prev_item; } -TreeItem *TreeItem::_get_next_visible(bool p_wrap) { +TreeItem *TreeItem::_get_next_in_tree(bool p_wrap, bool p_include_invisible) { TreeItem *current = this; - if (!current->collapsed && current->first_child) { + if ((!current->collapsed || p_include_invisible) && current->first_child) { current = current->first_child; } else if (current->next) { @@ -827,9 +827,9 @@ TreeItem *TreeItem::_get_next_visible(bool p_wrap) { TreeItem *TreeItem::get_next_visible(bool p_wrap) { TreeItem *loop = this; - TreeItem *next_item = this->_get_next_visible(p_wrap); + TreeItem *next_item = this->_get_next_in_tree(p_wrap); while (next_item && !next_item->is_visible()) { - next_item = next_item->_get_next_visible(p_wrap); + next_item = next_item->_get_next_in_tree(p_wrap); if (next_item == loop) { // Check that we haven't looped all the way around to the start. next_item = nullptr; @@ -839,6 +839,16 @@ TreeItem *TreeItem::get_next_visible(bool p_wrap) { return next_item; } +TreeItem *TreeItem::get_prev_in_tree(bool p_wrap) { + TreeItem *prev_item = this->_get_prev_in_tree(p_wrap, true); + return prev_item; +} + +TreeItem *TreeItem::get_next_in_tree(bool p_wrap) { + TreeItem *next_item = this->_get_next_in_tree(p_wrap, true); + return next_item; +} + TreeItem *TreeItem::get_child(int p_index) { _create_children_cache(); @@ -1539,6 +1549,9 @@ void TreeItem::_bind_methods() { ClassDB::bind_method(D_METHOD("get_parent"), &TreeItem::get_parent); ClassDB::bind_method(D_METHOD("get_first_child"), &TreeItem::get_first_child); + ClassDB::bind_method(D_METHOD("get_next_in_tree", "wrap"), &TreeItem::get_next_in_tree, DEFVAL(false)); + ClassDB::bind_method(D_METHOD("get_prev_in_tree", "wrap"), &TreeItem::get_prev_in_tree, DEFVAL(false)); + ClassDB::bind_method(D_METHOD("get_next_visible", "wrap"), &TreeItem::get_next_visible, DEFVAL(false)); ClassDB::bind_method(D_METHOD("get_prev_visible", "wrap"), &TreeItem::get_prev_visible, DEFVAL(false)); @@ -1773,7 +1786,7 @@ int Tree::get_item_height(TreeItem *p_item) const { return height; } -void Tree::draw_item_rect(TreeItem::Cell &p_cell, const Rect2i &p_rect, const Point2 &p_draw_ofs, const Color &p_color, const Color &p_icon_color, int p_ol_size, const Color &p_ol_color) { +void Tree::draw_item_rect(TreeItem::Cell &p_cell, const Rect2i &p_rect, const Color &p_color, const Color &p_icon_color, int p_ol_size, const Color &p_ol_color) { ERR_FAIL_COND(theme_cache.font.is_null()); Rect2i rect = p_rect; @@ -1811,8 +1824,7 @@ void Tree::draw_item_rect(TreeItem::Cell &p_cell, const Rect2i &p_rect, const Po if (rtl && rect.size.width > 0) { Point2 draw_pos = rect.position; - draw_pos.y += Math::floor(p_draw_ofs.y) - _get_title_button_height(); - p_cell.text_buf->set_width(rect.size.width); + draw_pos.y += Math::floor((rect.size.y - p_cell.text_buf->get_size().y) * 0.5); if (p_ol_size > 0 && p_ol_color.a > 0) { p_cell.text_buf->draw_outline(ci, draw_pos, p_ol_size, p_ol_color); } @@ -1831,8 +1843,7 @@ void Tree::draw_item_rect(TreeItem::Cell &p_cell, const Rect2i &p_rect, const Po if (!rtl && rect.size.width > 0) { Point2 draw_pos = rect.position; - draw_pos.y += Math::floor(p_draw_ofs.y) - _get_title_button_height(); - p_cell.text_buf->set_width(rect.size.width); + draw_pos.y += Math::floor((rect.size.y - p_cell.text_buf->get_size().y) * 0.5); if (p_ol_size > 0 && p_ol_color.a > 0) { p_cell.text_buf->draw_outline(ci, draw_pos, p_ol_size, p_ol_color); } @@ -1907,6 +1918,7 @@ void Tree::update_item_cell(TreeItem *p_item, int p_col) { font_size = theme_cache.font_size; } p_item->cells.write[p_col].text_buf->add_string(valtext, font, font_size, p_item->cells[p_col].language); + p_item->cells.write[p_col].text_buf->set_break_flags(TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND | TextServer::BREAK_ADAPTIVE); TS->shaped_text_set_bidi_override(p_item->cells[p_col].text_buf->get_rid(), structured_text_parser(p_item->cells[p_col].st_parser, p_item->cells[p_col].st_args, valtext)); p_item->cells.write[p_col].dirty = false; } @@ -1923,7 +1935,7 @@ void Tree::update_item_cache(TreeItem *p_item) { } } -int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 &p_draw_size, TreeItem *p_item) { +int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 &p_draw_size, TreeItem *p_item, int *r_self_height) { if (p_pos.y - theme_cache.offset.y > (p_draw_size.height)) { return -1; //draw no more! } @@ -1936,17 +1948,14 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 int htotal = 0; - int label_h = compute_item_height(p_item); + int label_h = 0; bool rtl = cache.rtl; - /* Calculate height of the label part */ - label_h += theme_cache.v_separation; - /* Draw label, if height fits */ bool skip = (p_item == root && hide_root); - if (!skip && (p_pos.y + label_h - theme_cache.offset.y) > 0) { + if (!skip) { // Draw separation. ERR_FAIL_COND_V(theme_cache.font.is_null(), -1); @@ -2006,6 +2015,14 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 buttons_width += button_size.width + theme_cache.button_margin; } + p_item->cells.write[i].text_buf->set_width(item_width); + + label_h = compute_item_height(p_item); + if (r_self_height != nullptr) { + *r_self_height = label_h; + } + label_h += theme_cache.v_separation; + Rect2i item_rect = Rect2i(Point2i(ofs, p_pos.y) - theme_cache.offset + p_draw_ofs, Size2i(item_width, label_h)); Rect2i cell_rect = item_rect; if (i != 0) { @@ -2129,7 +2146,7 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 switch (p_item->cells[i].mode) { case TreeItem::CELL_MODE_STRING: { - draw_item_rect(p_item->cells.write[i], item_rect, p_draw_ofs, cell_color, icon_col, outline_size, font_outline_color); + draw_item_rect(p_item->cells.write[i], item_rect, cell_color, icon_col, outline_size, font_outline_color); } break; case TreeItem::CELL_MODE_CHECK: { Ref<Texture2D> checked = theme_cache.checked; @@ -2153,7 +2170,7 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 item_rect.size.x -= check_w; item_rect.position.x += check_w; - draw_item_rect(p_item->cells.write[i], item_rect, p_draw_ofs, cell_color, icon_col, outline_size, font_outline_color); + draw_item_rect(p_item->cells.write[i], item_rect, cell_color, icon_col, outline_size, font_outline_color); } break; case TreeItem::CELL_MODE_RANGE: { @@ -2165,7 +2182,6 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 Ref<Texture2D> downarrow = theme_cache.select_arrow; int cell_width = item_rect.size.x - downarrow->get_width(); - p_item->cells.write[i].text_buf->set_width(cell_width); if (rtl) { if (outline_size > 0 && font_outline_color.a > 0) { p_item->cells[i].text_buf->draw_outline(ci, text_pos + Vector2(cell_width - text_width, 0), outline_size, font_outline_color); @@ -2232,7 +2248,7 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 } if (!p_item->cells[i].editable) { - draw_item_rect(p_item->cells.write[i], item_rect, p_draw_ofs, cell_color, icon_col, outline_size, font_outline_color); + draw_item_rect(p_item->cells.write[i], item_rect, cell_color, icon_col, outline_size, font_outline_color); break; } @@ -2260,7 +2276,7 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 ir.position += theme_cache.custom_button->get_offset(); } - draw_item_rect(p_item->cells.write[i], ir, p_draw_ofs, cell_color, icon_col, outline_size, font_outline_color); + draw_item_rect(p_item->cells.write[i], ir, cell_color, icon_col, outline_size, font_outline_color); downarrow->draw(ci, arrow_pos); @@ -2352,15 +2368,17 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 while (c) { int child_h = -1; + int child_self_height = 0; if (htotal >= 0) { - child_h = draw_item(children_pos, p_draw_ofs, p_draw_size, c); + child_h = draw_item(children_pos, p_draw_ofs, p_draw_size, c, &child_self_height); + child_self_height += theme_cache.v_separation; } // Draw relationship lines. if (theme_cache.draw_relationship_lines > 0 && (!hide_root || c->parent != root) && c->is_visible()) { int root_ofs = children_pos.x + ((p_item->disable_folding || hide_folding) ? theme_cache.h_separation : theme_cache.item_margin); int parent_ofs = p_pos.x + theme_cache.item_margin; - Point2i root_pos = Point2i(root_ofs, children_pos.y + label_h / 2) - theme_cache.offset + p_draw_ofs; + Point2i root_pos = Point2i(root_ofs, children_pos.y + child_self_height / 2) - theme_cache.offset + p_draw_ofs; if (c->get_visible_child_count() > 0) { root_pos -= Point2i(theme_cache.arrow->get_width(), 0); @@ -3846,14 +3864,14 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) { } } -bool Tree::edit_selected() { +bool Tree::edit_selected(bool p_force_edit) { TreeItem *s = get_selected(); ERR_FAIL_COND_V_MSG(!s, false, "No item selected."); ensure_cursor_is_visible(); int col = get_selected_column(); ERR_FAIL_INDEX_V_MSG(col, columns.size(), false, "No item column selected."); - if (!s->cells[col].editable) { + if (!s->cells[col].editable && !p_force_edit) { return false; } @@ -3959,6 +3977,14 @@ bool Tree::is_editing() { return popup_editor->is_visible(); } +void Tree::set_editor_selection(int p_from_line, int p_to_line, int p_from_column, int p_to_column, int p_caret) { + if (p_from_column == -1 || p_to_column == -1) { + line_editor->select(p_from_line, p_to_line); + } else { + text_editor->select(p_from_line, p_from_column, p_to_line, p_to_column, p_caret); + } +} + Size2 Tree::get_internal_min_size() const { Size2i size; if (root) { @@ -4990,6 +5016,26 @@ TreeItem *Tree::get_item_with_text(const String &p_find) const { return nullptr; } +TreeItem *Tree::get_item_with_metadata(const Variant &p_find, int p_column) const { + if (p_column < 0) { + for (TreeItem *current = root; current; current = current->get_next_in_tree()) { + for (int i = 0; i < columns.size(); i++) { + if (current->get_metadata(i) == p_find) { + return current; + } + } + } + return nullptr; + } + + for (TreeItem *current = root; current; current = current->get_next_in_tree()) { + if (current->get_metadata(p_column) == p_find) { + return current; + } + } + return nullptr; +} + void Tree::_do_incr_search(const String &p_add) { uint64_t time = OS::get_singleton()->get_ticks_usec() / 1000; // convert to msec uint64_t diff = time - last_keypress; @@ -5355,7 +5401,7 @@ void Tree::_bind_methods() { ClassDB::bind_method(D_METHOD("get_edited"), &Tree::get_edited); ClassDB::bind_method(D_METHOD("get_edited_column"), &Tree::get_edited_column); - ClassDB::bind_method(D_METHOD("edit_selected"), &Tree::edit_selected); + ClassDB::bind_method(D_METHOD("edit_selected", "force_edit"), &Tree::edit_selected, DEFVAL(false)); ClassDB::bind_method(D_METHOD("get_custom_popup_rect"), &Tree::get_custom_popup_rect); ClassDB::bind_method(D_METHOD("get_item_area_rect", "item", "column", "button_index"), &Tree::get_item_rect, DEFVAL(-1), DEFVAL(-1)); ClassDB::bind_method(D_METHOD("get_item_at_position", "position"), &Tree::get_item_at_position); diff --git a/scene/gui/tree.h b/scene/gui/tree.h index 75ce6b689d..24b649b040 100644 --- a/scene/gui/tree.h +++ b/scene/gui/tree.h @@ -217,8 +217,8 @@ private: void _propagate_check_through_children(int p_column, bool p_checked, bool p_emit_signal); void _propagate_check_through_parents(int p_column, bool p_emit_signal); - TreeItem *_get_prev_visible(bool p_wrap = false); - TreeItem *_get_next_visible(bool p_wrap = false); + TreeItem *_get_prev_in_tree(bool p_wrap = false, bool p_include_invisible = false); + TreeItem *_get_next_in_tree(bool p_wrap = false, bool p_include_invisible = false); public: void set_text(int p_column, String p_text); @@ -344,6 +344,9 @@ public: TreeItem *get_parent() const; TreeItem *get_first_child() const; + TreeItem *get_prev_in_tree(bool p_wrap = false); + TreeItem *get_next_in_tree(bool p_wrap = false); + TreeItem *get_prev_visible(bool p_wrap = false); TreeItem *get_next_visible(bool p_wrap = false); @@ -476,8 +479,8 @@ private: void update_item_cell(TreeItem *p_item, int p_col); void update_item_cache(TreeItem *p_item); //void draw_item_text(String p_text,const Ref<Texture2D>& p_icon,int p_icon_max_w,bool p_tool,Rect2i p_rect,const Color& p_color); - void draw_item_rect(TreeItem::Cell &p_cell, const Rect2i &p_rect, const Point2 &p_draw_ofs, const Color &p_color, const Color &p_icon_color, int p_ol_size, const Color &p_ol_color); - int draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 &p_draw_size, TreeItem *p_item); + void draw_item_rect(TreeItem::Cell &p_cell, const Rect2i &p_rect, const Color &p_color, const Color &p_icon_color, int p_ol_size, const Color &p_ol_color); + int draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 &p_draw_size, TreeItem *p_item, int *r_self_height = nullptr); void select_single_item(TreeItem *p_selected, TreeItem *p_current, int p_col, TreeItem *p_prev = nullptr, bool *r_in_range = nullptr, bool p_force_deselect = false); int propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int x_limit, bool p_double_click, TreeItem *p_item, MouseButton p_button, const Ref<InputEventWithModifiers> &p_mod); void _line_editor_submit(String p_text); @@ -724,13 +727,15 @@ public: int get_item_offset(TreeItem *p_item) const; Rect2 get_item_rect(TreeItem *p_item, int p_column = -1, int p_button = -1) const; - bool edit_selected(); + bool edit_selected(bool p_force_edit = false); bool is_editing(); + void set_editor_selection(int p_from_line, int p_to_line, int p_from_column = -1, int p_to_column = -1, int p_caret = 0); // First item that starts with the text, from the current focused item down and wraps around. TreeItem *search_item_text(const String &p_find, int *r_col = nullptr, bool p_selectable = false); // First item that matches the whole text, from the first item down. TreeItem *get_item_with_text(const String &p_find) const; + TreeItem *get_item_with_metadata(const Variant &p_find, int p_column = -1) const; Point2 get_scroll() const; void scroll_to_item(TreeItem *p_item, bool p_center_on_item = false); diff --git a/scene/main/node.cpp b/scene/main/node.cpp index 1d7a100f7c..3aaafeae30 100644 --- a/scene/main/node.cpp +++ b/scene/main/node.cpp @@ -47,10 +47,14 @@ #include <stdint.h> VARIANT_ENUM_CAST(Node::ProcessMode); +VARIANT_ENUM_CAST(Node::ProcessThreadGroup); +VARIANT_BITFIELD_CAST(Node::ProcessThreadMessages); VARIANT_ENUM_CAST(Node::InternalMode); int Node::orphan_node_count = 0; +thread_local Node *Node::current_process_thread_group = nullptr; + void Node::_notification(int p_notification) { switch (p_notification) { case NOTIFICATION_PROCESS: { @@ -65,6 +69,7 @@ void Node::_notification(int p_notification) { ERR_FAIL_COND(!get_viewport()); ERR_FAIL_COND(!get_tree()); + // Update process mode. if (data.process_mode == PROCESS_MODE_INHERIT) { if (data.parent) { data.process_owner = data.parent->data.process_owner; @@ -77,6 +82,27 @@ void Node::_notification(int p_notification) { data.process_owner = this; } + { // Update threaded process mode. + if (data.process_thread_group == PROCESS_THREAD_GROUP_INHERIT) { + if (data.parent) { + data.process_thread_group_owner = data.parent->data.process_thread_group_owner; + } + + if (data.process_thread_group_owner) { + data.process_group = data.process_thread_group_owner->data.process_group; + } else { + data.process_group = &data.tree->default_process_group; + } + } else { + data.process_thread_group_owner = this; + _add_process_group(); + } + + if (_is_any_processing()) { + _add_to_process_thread_group(); + } + } + if (data.input) { add_to_group("_vp_input" + itos(get_viewport()->get_instance_id())); } @@ -90,7 +116,7 @@ void Node::_notification(int p_notification) { add_to_group("_vp_unhandled_key_input" + itos(get_viewport()->get_instance_id())); } - get_tree()->node_count++; + get_tree()->nodes_in_tree_count++; orphan_node_count--; } break; @@ -98,7 +124,7 @@ void Node::_notification(int p_notification) { ERR_FAIL_COND(!get_viewport()); ERR_FAIL_COND(!get_tree()); - get_tree()->node_count--; + get_tree()->nodes_in_tree_count--; orphan_node_count++; if (data.input) { @@ -114,7 +140,17 @@ void Node::_notification(int p_notification) { remove_from_group("_vp_unhandled_key_input" + itos(get_viewport()->get_instance_id())); } + // Remove from processing first + if (_is_any_processing()) { + _remove_from_process_thread_group(); + } + // Remove the process group + if (data.process_thread_group_owner == this) { + _remove_process_group(); + } + data.process_thread_group_owner = nullptr; data.process_owner = nullptr; + if (data.path_cache) { memdelete(data.path_cache); data.path_cache = nullptr; @@ -160,6 +196,12 @@ void Node::_notification(int p_notification) { } break; case NOTIFICATION_PREDELETE: { + if (data.inside_tree && !Thread::is_main_thread()) { + cancel_free(); + ERR_PRINT("Attempted to free a node that is currently added to the SceneTree from a thread. This is not permitted, use queue_free() instead. Node has not been freed."); + return; + } + if (data.parent) { data.parent->remove_child(this); } @@ -329,6 +371,7 @@ void Node::_propagate_exit_tree() { } void Node::move_child(Node *p_child, int p_index) { + ERR_FAIL_COND_MSG(data.inside_tree && !Thread::is_main_thread(), "Moving child node positions inside the SceneTree is only allowed from the main thread. Use call_deferred(\"move_child\",child,index)."); ERR_FAIL_NULL(p_child); ERR_FAIL_COND_MSG(p_child->data.parent != this, "Child is not a child of this node."); @@ -440,16 +483,24 @@ void Node::owner_changed_notify() { } void Node::set_physics_process(bool p_process) { + ERR_THREAD_GUARD if (data.physics_process == p_process) { return; } + if (!is_inside_tree()) { + data.physics_process = p_process; + return; + } + + if (_is_any_processing()) { + _remove_from_process_thread_group(); + } + data.physics_process = p_process; - if (data.physics_process) { - add_to_group(SNAME("_physics_process"), false); - } else { - remove_from_group(SNAME("_physics_process")); + if (_is_any_processing()) { + _add_to_process_thread_group(); } } @@ -458,16 +509,24 @@ bool Node::is_physics_processing() const { } void Node::set_physics_process_internal(bool p_process_internal) { + ERR_THREAD_GUARD if (data.physics_process_internal == p_process_internal) { return; } + if (!is_inside_tree()) { + data.physics_process_internal = p_process_internal; + return; + } + + if (_is_any_processing()) { + _remove_from_process_thread_group(); + } + data.physics_process_internal = p_process_internal; - if (data.physics_process_internal) { - add_to_group(SNAME("_physics_process_internal"), false); - } else { - remove_from_group(SNAME("_physics_process_internal")); + if (_is_any_processing()) { + _add_to_process_thread_group(); } } @@ -476,6 +535,7 @@ bool Node::is_physics_processing_internal() const { } void Node::set_process_mode(ProcessMode p_mode) { + ERR_THREAD_GUARD if (data.process_mode == p_mode) { return; } @@ -569,6 +629,7 @@ void Node::_propagate_process_owner(Node *p_owner, int p_pause_notification, int } void Node::set_multiplayer_authority(int p_peer_id, bool p_recursive) { + ERR_THREAD_GUARD data.multiplayer_authority = p_peer_id; if (p_recursive) { @@ -591,6 +652,7 @@ bool Node::is_multiplayer_authority() const { /***** RPC CONFIG ********/ void Node::rpc_config(const StringName &p_method, const Variant &p_config) { + ERR_THREAD_GUARD if (data.rpc_config.get_type() != Variant::DICTIONARY) { data.rpc_config = Dictionary(); } @@ -762,16 +824,24 @@ double Node::get_process_delta_time() const { } void Node::set_process(bool p_process) { + ERR_THREAD_GUARD if (data.process == p_process) { return; } + if (!is_inside_tree()) { + data.process = p_process; + return; + } + + if (_is_any_processing()) { + _remove_from_process_thread_group(); + } + data.process = p_process; - if (data.process) { - add_to_group(SNAME("_process"), false); - } else { - remove_from_group(SNAME("_process")); + if (_is_any_processing()) { + _add_to_process_thread_group(); } } @@ -780,53 +850,201 @@ bool Node::is_processing() const { } void Node::set_process_internal(bool p_process_internal) { + ERR_THREAD_GUARD if (data.process_internal == p_process_internal) { return; } + if (!is_inside_tree()) { + data.process_internal = p_process_internal; + return; + } + + if (_is_any_processing()) { + _remove_from_process_thread_group(); + } + data.process_internal = p_process_internal; - if (data.process_internal) { - add_to_group(SNAME("_process_internal"), false); - } else { - remove_from_group(SNAME("_process_internal")); + if (_is_any_processing()) { + _add_to_process_thread_group(); } } +void Node::_add_process_group() { + get_tree()->_add_process_group(this); +} + +void Node::_remove_process_group() { + get_tree()->_remove_process_group(this); +} + +void Node::_remove_from_process_thread_group() { + get_tree()->_remove_node_from_process_group(this, data.process_thread_group_owner); +} + +void Node::_add_to_process_thread_group() { + get_tree()->_add_node_to_process_group(this, data.process_thread_group_owner); +} + +void Node::_remove_tree_from_process_thread_group() { + if (!is_inside_tree()) { + return; // May not be initialized yet. + } + + for (KeyValue<StringName, Node *> &K : data.children) { + if (K.value->data.process_thread_group != PROCESS_THREAD_GROUP_INHERIT) { + continue; + } + + K.value->_remove_tree_from_process_thread_group(); + } + + if (_is_any_processing()) { + _remove_from_process_thread_group(); + } +} + +void Node::_add_tree_to_process_thread_group(Node *p_owner) { + if (_is_any_processing()) { + _add_to_process_thread_group(); + } + + data.process_thread_group_owner = p_owner; + if (p_owner != nullptr) { + data.process_group = p_owner->data.process_group; + } else { + data.process_group = &data.tree->default_process_group; + } + + for (KeyValue<StringName, Node *> &K : data.children) { + if (K.value->data.process_thread_group != PROCESS_THREAD_GROUP_INHERIT) { + continue; + } + + K.value->_add_to_process_thread_group(); + } +} bool Node::is_processing_internal() const { return data.process_internal; } +void Node::set_process_thread_group_order(int p_order) { + ERR_THREAD_GUARD + if (data.process_thread_group_order == p_order) { + return; + } + // Make sure we are in SceneTree and an actual process owner + if (!is_inside_tree() || data.process_thread_group_owner != this) { + data.process_thread_group_order = p_order; + return; + } + + get_tree()->process_groups_dirty = true; +} + +int Node::get_process_thread_group_order() const { + return data.process_thread_group_order; +} + void Node::set_process_priority(int p_priority) { - data.process_priority = p_priority; + ERR_THREAD_GUARD + if (data.process_priority == p_priority) { + return; + } + // Make sure we are in SceneTree and an actual process owner + if (!is_inside_tree()) { + data.process_priority = p_priority; + return; + } + + if (_is_any_processing()) { + _remove_from_process_thread_group(); + data.process_priority = p_priority; + _add_to_process_thread_group(); + } +} - // Make sure we are in SceneTree. - if (data.tree == nullptr) { +int Node::get_process_priority() const { + return data.process_priority; +} + +void Node::set_physics_process_priority(int p_priority) { + ERR_THREAD_GUARD + if (data.physics_process_priority == p_priority) { return; } + // Make sure we are in SceneTree and an actual physics_process owner + if (!is_inside_tree()) { + data.physics_process_priority = p_priority; + return; + } + + if (_is_any_processing()) { + _remove_from_process_thread_group(); + data.physics_process_priority = p_priority; + _add_to_process_thread_group(); + } +} + +int Node::get_physics_process_priority() const { + return data.physics_process_priority; +} - if (is_processing()) { - data.tree->make_group_changed(SNAME("_process")); +void Node::set_process_thread_group(ProcessThreadGroup p_mode) { + ERR_FAIL_COND_MSG(data.inside_tree && !Thread::is_main_thread(), "Changing the process thread group can only be done from the main thread. Use call_deferred(\"set_process_thread_group\",mode)."); + if (data.process_thread_group == p_mode) { + return; } - if (is_processing_internal()) { - data.tree->make_group_changed(SNAME("_process_internal")); + if (!is_inside_tree()) { + data.process_thread_group = p_mode; + return; } - if (is_physics_processing()) { - data.tree->make_group_changed(SNAME("_physics_process")); + // Mode changed, must update everything. + _remove_tree_from_process_thread_group(); + if (data.process_thread_group != PROCESS_THREAD_GROUP_INHERIT) { + _remove_process_group(); } - if (is_physics_processing_internal()) { - data.tree->make_group_changed(SNAME("_physics_process_internal")); + data.process_thread_group = p_mode; + + if (p_mode == PROCESS_THREAD_GROUP_INHERIT) { + if (data.parent) { + data.process_thread_group_owner = data.parent->data.process_thread_group_owner; + } else { + data.process_thread_group_owner = nullptr; + } + } else { + data.process_thread_group_owner = this; + _add_process_group(); } + + _add_tree_to_process_thread_group(data.process_thread_group_owner); + + notify_property_list_changed(); } -int Node::get_process_priority() const { - return data.process_priority; +Node::ProcessThreadGroup Node::get_process_thread_group() const { + return data.process_thread_group; +} + +void Node::set_process_thread_messages(BitField<ProcessThreadMessages> p_flags) { + ERR_THREAD_GUARD + if (data.process_thread_group_order == p_flags) { + return; + } + + data.process_thread_messages = p_flags; +} + +BitField<Node::ProcessThreadMessages> Node::get_process_thread_messages() const { + return data.process_thread_messages; } void Node::set_process_input(bool p_enable) { + ERR_THREAD_GUARD if (p_enable == data.input) { return; } @@ -848,6 +1066,7 @@ bool Node::is_processing_input() const { } void Node::set_process_shortcut_input(bool p_enable) { + ERR_THREAD_GUARD if (p_enable == data.shortcut_input) { return; } @@ -868,6 +1087,7 @@ bool Node::is_processing_shortcut_input() const { } void Node::set_process_unhandled_input(bool p_enable) { + ERR_THREAD_GUARD if (p_enable == data.unhandled_input) { return; } @@ -888,6 +1108,7 @@ bool Node::is_processing_unhandled_input() const { } void Node::set_process_unhandled_key_input(bool p_enable) { + ERR_THREAD_GUARD if (p_enable == data.unhandled_key_input) { return; } @@ -916,6 +1137,7 @@ void Node::_set_name_nocheck(const StringName &p_name) { } void Node::set_name(const String &p_name) { + ERR_FAIL_COND_MSG(data.inside_tree && !Thread::is_main_thread(), "Changing the name to nodes inside the SceneTree is only allowed from the main thread. Use call_deferred(\"set_name\",new_name)."); String name = p_name.validate_node_name(); ERR_FAIL_COND(name.is_empty()); @@ -1147,6 +1369,9 @@ void Node::_add_child_nocheck(Node *p_child, const StringName &p_name, InternalM } void Node::add_child(Node *p_child, bool p_force_readable_name, InternalMode p_internal) { + ERR_FAIL_COND_MSG(data.inside_tree && !Thread::is_main_thread(), "Adding children to a node inside the SceneTree is only allowed from the main thread. Use call_deferred(\"add_child\",node)."); + + ERR_THREAD_GUARD ERR_FAIL_NULL(p_child); ERR_FAIL_COND_MSG(p_child == this, vformat("Can't add child '%s' to itself.", p_child->get_name())); // adding to itself! ERR_FAIL_COND_MSG(p_child->data.parent, vformat("Can't add child '%s' to '%s', already has a parent '%s'.", p_child->get_name(), get_name(), p_child->data.parent->get_name())); //Fail if node has a parent @@ -1160,6 +1385,7 @@ void Node::add_child(Node *p_child, bool p_force_readable_name, InternalMode p_i } void Node::add_sibling(Node *p_sibling, bool p_force_readable_name) { + ERR_FAIL_COND_MSG(data.inside_tree && !Thread::is_main_thread(), "Adding a sibling to a node inside the SceneTree is only allowed from the main thread. Use call_deferred(\"add_sibling\",node)."); ERR_FAIL_NULL(p_sibling); ERR_FAIL_NULL(data.parent); ERR_FAIL_COND_MSG(p_sibling == this, vformat("Can't add sibling '%s' to itself.", p_sibling->get_name())); // adding to itself! @@ -1171,6 +1397,7 @@ void Node::add_sibling(Node *p_sibling, bool p_force_readable_name) { } void Node::remove_child(Node *p_child) { + ERR_FAIL_COND_MSG(data.inside_tree && !Thread::is_main_thread(), "Removing children from a node inside the SceneTree is only allowed from the main thread. Use call_deferred(\"remove_child\",node)."); ERR_FAIL_NULL(p_child); ERR_FAIL_COND_MSG(data.blocked > 0, "Parent node is busy adding/removing children, `remove_child()` can't be called at this time. Consider using `remove_child.call_deferred(child)` instead."); ERR_FAIL_COND(p_child->data.parent != this); @@ -1241,6 +1468,7 @@ void Node::_update_children_cache_impl() const { } int Node::get_child_count(bool p_include_internal) const { + ERR_THREAD_GUARD_V(0); _update_children_cache(); if (p_include_internal) { @@ -1251,6 +1479,7 @@ int Node::get_child_count(bool p_include_internal) const { } Node *Node::get_child(int p_index, bool p_include_internal) const { + ERR_THREAD_GUARD_V(nullptr); _update_children_cache(); if (p_include_internal) { @@ -1270,6 +1499,7 @@ Node *Node::get_child(int p_index, bool p_include_internal) const { } TypedArray<Node> Node::get_children(bool p_include_internal) const { + ERR_THREAD_GUARD_V(TypedArray<Node>()); TypedArray<Node> arr; int cc = get_child_count(p_include_internal); arr.resize(cc); @@ -1290,6 +1520,7 @@ Node *Node::_get_child_by_name(const StringName &p_name) const { } Node *Node::get_node_or_null(const NodePath &p_path) const { + ERR_THREAD_GUARD_V(nullptr); if (p_path.is_empty()) { return nullptr; } @@ -1395,6 +1626,7 @@ bool Node::has_node(const NodePath &p_path) const { // Finds the first child node (in tree order) whose name matches the given pattern. // Can be recursive or not, and limited to owned nodes. Node *Node::find_child(const String &p_pattern, bool p_recursive, bool p_owned) const { + ERR_THREAD_GUARD_V(nullptr); ERR_FAIL_COND_V(p_pattern.is_empty(), nullptr); _update_children_cache(); Node *const *cptr = data.children_cache.ptr(); @@ -1423,6 +1655,7 @@ Node *Node::find_child(const String &p_pattern, bool p_recursive, bool p_owned) // or both (either pattern or type can be left empty). // Can be recursive or not, and limited to owned nodes. TypedArray<Node> Node::find_children(const String &p_pattern, const String &p_type, bool p_recursive, bool p_owned) const { + ERR_THREAD_GUARD_V(TypedArray<Node>()); TypedArray<Node> ret; ERR_FAIL_COND_V(p_pattern.is_empty() && p_type.is_empty(), ret); _update_children_cache(); @@ -1464,6 +1697,7 @@ TypedArray<Node> Node::find_children(const String &p_pattern, const String &p_ty } void Node::reparent(Node *p_parent, bool p_keep_global_transform) { + ERR_THREAD_GUARD ERR_FAIL_NULL(p_parent); ERR_FAIL_NULL_MSG(data.parent, "Node needs a parent to be reparented."); @@ -1480,6 +1714,7 @@ Node *Node::get_parent() const { } Node *Node::find_parent(const String &p_pattern) const { + ERR_THREAD_GUARD_V(nullptr); Node *p = data.parent; while (p) { if (p->data.name.operator String().match(p_pattern)) { @@ -1492,6 +1727,7 @@ Node *Node::find_parent(const String &p_pattern) const { } Window *Node::get_window() const { + ERR_THREAD_GUARD_V(nullptr); Viewport *vp = get_viewport(); if (vp) { return vp->get_base_window(); @@ -1616,6 +1852,7 @@ void Node::_acquire_unique_name_in_owner() { } void Node::set_unique_name_in_owner(bool p_enabled) { + ERR_MAIN_THREAD_GUARD if (data.unique_name_in_owner == p_enabled) { return; } @@ -1637,6 +1874,7 @@ bool Node::is_unique_name_in_owner() const { } void Node::set_owner(Node *p_owner) { + ERR_MAIN_THREAD_GUARD if (data.owner) { if (data.unique_name_in_owner) { _release_unique_name_in_owner(); @@ -1820,10 +2058,12 @@ NodePath Node::get_path() const { } bool Node::is_in_group(const StringName &p_identifier) const { + ERR_THREAD_GUARD_V(false); return data.grouped.has(p_identifier); } void Node::add_to_group(const StringName &p_identifier, bool p_persistent) { + ERR_THREAD_GUARD ERR_FAIL_COND(!p_identifier.operator String().length()); if (data.grouped.has(p_identifier)) { @@ -1844,6 +2084,7 @@ void Node::add_to_group(const StringName &p_identifier, bool p_persistent) { } void Node::remove_from_group(const StringName &p_identifier) { + ERR_THREAD_GUARD HashMap<StringName, GroupData>::Iterator E = data.grouped.find(p_identifier); if (!E) { @@ -1869,6 +2110,7 @@ TypedArray<StringName> Node::_get_groups() const { } void Node::get_groups(List<GroupInfo> *p_groups) const { + ERR_THREAD_GUARD for (const KeyValue<StringName, GroupData> &E : data.grouped) { GroupInfo gi; gi.name = E.key; @@ -1878,6 +2120,7 @@ void Node::get_groups(List<GroupInfo> *p_groups) const { } int Node::get_persistent_group_count() const { + ERR_THREAD_GUARD_V(0); int count = 0; for (const KeyValue<StringName, GroupData> &E : data.grouped) { @@ -1947,6 +2190,7 @@ void Node::_propagate_deferred_notification(int p_notification, bool p_reverse) } void Node::propagate_notification(int p_notification) { + ERR_THREAD_GUARD data.blocked++; notification(p_notification); @@ -1957,6 +2201,7 @@ void Node::propagate_notification(int p_notification) { } void Node::propagate_call(const StringName &p_method, const Array &p_args, const bool p_parent_first) { + ERR_THREAD_GUARD data.blocked++; if (p_parent_first && has_method(p_method)) { @@ -1987,6 +2232,7 @@ void Node::_propagate_replace_owner(Node *p_owner, Node *p_by_owner) { } Ref<Tween> Node::create_tween() { + ERR_THREAD_GUARD_V(Ref<Tween>()); ERR_FAIL_COND_V_MSG(!data.tree, nullptr, "Can't create Tween when not inside scene tree."); Ref<Tween> tween = get_tree()->create_tween(); tween->bind_node(this); @@ -1994,6 +2240,7 @@ Ref<Tween> Node::create_tween() { } void Node::set_scene_file_path(const String &p_scene_file_path) { + ERR_THREAD_GUARD data.scene_file_path = p_scene_file_path; } @@ -2002,6 +2249,7 @@ String Node::get_scene_file_path() const { } void Node::set_editor_description(const String &p_editor_description) { + ERR_THREAD_GUARD if (data.editor_description == p_editor_description) { return; } @@ -2019,6 +2267,7 @@ String Node::get_editor_description() const { } void Node::set_editable_instance(Node *p_node, bool p_editable) { + ERR_THREAD_GUARD ERR_FAIL_NULL(p_node); ERR_FAIL_COND(!is_ancestor_of(p_node)); if (!p_editable) { @@ -2040,6 +2289,7 @@ bool Node::is_editable_instance(const Node *p_node) const { } Node *Node::get_deepest_editable_node(Node *p_start_node) const { + ERR_THREAD_GUARD_V(nullptr); ERR_FAIL_NULL_V(p_start_node, nullptr); ERR_FAIL_COND_V(!is_ancestor_of(p_start_node), p_start_node); @@ -2059,6 +2309,7 @@ Node *Node::get_deepest_editable_node(Node *p_start_node) const { #ifdef TOOLS_ENABLED void Node::set_property_pinned(const String &p_property, bool p_pinned) { + ERR_THREAD_GUARD bool current_pinned = false; Array pinned = get_meta("_edit_pinned_properties_", Array()); StringName psa = get_property_store_alias(p_property); @@ -2096,6 +2347,7 @@ bool Node::is_part_of_edited_scene() const { #endif void Node::get_storable_properties(HashSet<StringName> &r_storable_properties) const { + ERR_THREAD_GUARD List<PropertyInfo> pi; get_property_list(&pi); for (List<PropertyInfo>::Element *E = pi.front(); E; E = E->next()) { @@ -2106,6 +2358,7 @@ void Node::get_storable_properties(HashSet<StringName> &r_storable_properties) c } String Node::to_string() { + ERR_THREAD_GUARD_V(String()); if (get_script_instance()) { bool valid; String ret = get_script_instance()->to_string(&valid); @@ -2118,6 +2371,7 @@ String Node::to_string() { } void Node::set_scene_instance_state(const Ref<SceneState> &p_state) { + ERR_THREAD_GUARD data.instance_state = p_state; } @@ -2126,6 +2380,7 @@ Ref<SceneState> Node::get_scene_instance_state() const { } void Node::set_scene_inherited_state(const Ref<SceneState> &p_state) { + ERR_THREAD_GUARD data.inherited_state = p_state; } @@ -2142,6 +2397,7 @@ bool Node::get_scene_instance_load_placeholder() const { } Node *Node::_duplicate(int p_flags, HashMap<const Node *, Node *> *r_duplimap) const { + ERR_THREAD_GUARD_V(nullptr); Node *node = nullptr; bool instantiated = false; @@ -2323,6 +2579,7 @@ Node *Node::_duplicate(int p_flags, HashMap<const Node *, Node *> *r_duplimap) c } Node *Node::duplicate(int p_flags) const { + ERR_THREAD_GUARD_V(nullptr); Node *dupe = _duplicate(p_flags); if (dupe && (p_flags & DUPLICATE_SIGNALS)) { @@ -2467,6 +2724,7 @@ static void find_owned_by(Node *p_by, Node *p_node, List<Node *> *p_owned) { } void Node::replace_by(Node *p_node, bool p_keep_groups) { + ERR_THREAD_GUARD ERR_FAIL_NULL(p_node); ERR_FAIL_COND(p_node->data.parent); @@ -2536,6 +2794,7 @@ void Node::_replace_connections_target(Node *p_new_target) { } bool Node::has_node_and_resource(const NodePath &p_path) const { + ERR_THREAD_GUARD_V(false); if (!has_node(p_path)) { return false; } @@ -2570,6 +2829,7 @@ Array Node::_get_node_and_resource(const NodePath &p_path) { } Node *Node::get_node_and_resource(const NodePath &p_path, Ref<Resource> &r_res, Vector<StringName> &r_leftover_subpath, bool p_last_is_property) const { + ERR_THREAD_GUARD_V(nullptr); Node *node = get_node(p_path); r_res = Ref<Resource>(); r_leftover_subpath = Vector<StringName>(); @@ -2739,6 +2999,7 @@ void Node::clear_internal_tree_resource_paths() { } PackedStringArray Node::get_configuration_warnings() const { + ERR_THREAD_GUARD_V(PackedStringArray()); PackedStringArray ret; Vector<String> warnings; @@ -2764,6 +3025,7 @@ String Node::get_configuration_warnings_as_string() const { } void Node::update_configuration_warnings() { + ERR_THREAD_GUARD #ifdef TOOLS_ENABLED if (!is_inside_tree()) { return; @@ -2779,6 +3041,7 @@ bool Node::is_owned_by_parent() const { } void Node::set_display_folded(bool p_folded) { + ERR_THREAD_GUARD data.display_folded = p_folded; } @@ -2791,6 +3054,7 @@ bool Node::is_ready() const { } void Node::request_ready() { + ERR_THREAD_GUARD data.ready_first = true; } @@ -2834,6 +3098,12 @@ void Node::_call_unhandled_key_input(const Ref<InputEvent> &p_event) { unhandled_key_input(p_event); } +void Node::_validate_property(PropertyInfo &p_property) const { + if ((p_property.name == "process_thread_group_order" || p_property.name == "process_thread_messages") && data.process_thread_group == PROCESS_THREAD_GROUP_INHERIT) { + p_property.usage = 0; + } +} + void Node::input(const Ref<InputEvent> &p_event) { } @@ -2846,6 +3116,94 @@ void Node::unhandled_input(const Ref<InputEvent> &p_event) { void Node::unhandled_key_input(const Ref<InputEvent> &p_key_event) { } +Variant Node::_call_deferred_thread_group_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error) { + if (p_argcount < 1) { + r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; + r_error.argument = 0; + return Variant(); + } + + if (p_args[0]->get_type() != Variant::STRING_NAME && p_args[0]->get_type() != Variant::STRING) { + r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument = 0; + r_error.expected = Variant::STRING_NAME; + return Variant(); + } + + r_error.error = Callable::CallError::CALL_OK; + + StringName method = *p_args[0]; + + call_deferred_thread_groupp(method, &p_args[1], p_argcount - 1, true); + + return Variant(); +} + +Variant Node::_call_thread_safe_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error) { + if (p_argcount < 1) { + r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; + r_error.argument = 0; + return Variant(); + } + + if (p_args[0]->get_type() != Variant::STRING_NAME && p_args[0]->get_type() != Variant::STRING) { + r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument = 0; + r_error.expected = Variant::STRING_NAME; + return Variant(); + } + + r_error.error = Callable::CallError::CALL_OK; + + StringName method = *p_args[0]; + + call_thread_safep(method, &p_args[1], p_argcount - 1, true); + + return Variant(); +} + +void Node::call_deferred_thread_groupp(const StringName &p_method, const Variant **p_args, int p_argcount, bool p_show_error) { + ERR_FAIL_COND(!is_inside_tree()); + SceneTree::ProcessGroup *pg = (SceneTree::ProcessGroup *)data.process_group; + pg->call_queue.push_callp(this, p_method, p_args, p_argcount, p_show_error); +} +void Node::set_deferred_thread_group(const StringName &p_property, const Variant &p_value) { + ERR_FAIL_COND(!is_inside_tree()); + SceneTree::ProcessGroup *pg = (SceneTree::ProcessGroup *)data.process_group; + pg->call_queue.push_set(this, p_property, p_value); +} +void Node::notify_deferred_thread_group(int p_notification) { + ERR_FAIL_COND(!is_inside_tree()); + SceneTree::ProcessGroup *pg = (SceneTree::ProcessGroup *)data.process_group; + pg->call_queue.push_notification(this, p_notification); +} + +void Node::call_thread_safep(const StringName &p_method, const Variant **p_args, int p_argcount, bool p_show_error) { + if (is_accessible_from_caller_thread()) { + Callable::CallError ce; + callp(p_method, p_args, p_argcount, ce); + if (p_show_error && ce.error != Callable::CallError::CALL_OK) { + ERR_FAIL_MSG("Error calling method from 'call_threadp': " + Variant::get_call_error_text(this, p_method, p_args, p_argcount, ce) + "."); + } + } else { + call_deferred_thread_groupp(p_method, p_args, p_argcount, p_show_error); + } +} +void Node::set_thread_safe(const StringName &p_property, const Variant &p_value) { + if (is_accessible_from_caller_thread()) { + set(p_property, p_value); + } else { + set_deferred_thread_group(p_property, p_value); + } +} +void Node::notify_thread_safe(int p_notification) { + if (is_accessible_from_caller_thread()) { + notification(p_notification); + } else { + notify_deferred_thread_group(p_notification); + } +} + void Node::_bind_methods() { GLOBAL_DEF(PropertyInfo(Variant::INT, "editor/naming/node_name_num_separator", PROPERTY_HINT_ENUM, "None,Space,Underscore,Dash"), 0); GLOBAL_DEF(PropertyInfo(Variant::INT, "editor/naming/node_name_casing", PROPERTY_HINT_ENUM, "PascalCase,camelCase,snake_case"), NAME_CASING_PASCAL_CASE); @@ -2897,6 +3255,8 @@ void Node::_bind_methods() { ClassDB::bind_method(D_METHOD("set_process", "enable"), &Node::set_process); ClassDB::bind_method(D_METHOD("set_process_priority", "priority"), &Node::set_process_priority); ClassDB::bind_method(D_METHOD("get_process_priority"), &Node::get_process_priority); + ClassDB::bind_method(D_METHOD("set_physics_process_priority", "priority"), &Node::set_physics_process_priority); + ClassDB::bind_method(D_METHOD("get_physics_process_priority"), &Node::get_physics_process_priority); ClassDB::bind_method(D_METHOD("is_processing"), &Node::is_processing); ClassDB::bind_method(D_METHOD("set_process_input", "enable"), &Node::set_process_input); ClassDB::bind_method(D_METHOD("is_processing_input"), &Node::is_processing_input); @@ -2910,6 +3270,15 @@ void Node::_bind_methods() { ClassDB::bind_method(D_METHOD("get_process_mode"), &Node::get_process_mode); ClassDB::bind_method(D_METHOD("can_process"), &Node::can_process); + ClassDB::bind_method(D_METHOD("set_process_thread_group", "mode"), &Node::set_process_thread_group); + ClassDB::bind_method(D_METHOD("get_process_thread_group"), &Node::get_process_thread_group); + + ClassDB::bind_method(D_METHOD("set_process_thread_messages", "flags"), &Node::set_process_thread_messages); + ClassDB::bind_method(D_METHOD("get_process_thread_messages"), &Node::get_process_thread_messages); + + ClassDB::bind_method(D_METHOD("set_process_thread_group_order", "order"), &Node::set_process_thread_group_order); + ClassDB::bind_method(D_METHOD("get_process_thread_group_order"), &Node::get_process_thread_group_order); + ClassDB::bind_method(D_METHOD("set_display_folded", "fold"), &Node::set_display_folded); ClassDB::bind_method(D_METHOD("is_displayed_folded"), &Node::is_displayed_folded); @@ -2977,6 +3346,26 @@ void Node::_bind_methods() { ClassDB::bind_method(D_METHOD("update_configuration_warnings"), &Node::update_configuration_warnings); + { + MethodInfo mi; + mi.name = "call_deferred_thread_group"; + mi.arguments.push_back(PropertyInfo(Variant::STRING_NAME, "method")); + + ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "call_deferred_thread_group", &Node::_call_deferred_thread_group_bind, mi, varray(), false); + } + ClassDB::bind_method(D_METHOD("set_deferred_thread_group", "property", "value"), &Node::set_deferred_thread_group); + ClassDB::bind_method(D_METHOD("notify_deferred_thread_group", "what"), &Node::notify_deferred_thread_group); + + { + MethodInfo mi; + mi.name = "call_thread_safe"; + mi.arguments.push_back(PropertyInfo(Variant::STRING_NAME, "method")); + + ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "call_thread_safe", &Node::_call_thread_safe_bind, mi, varray(), false); + } + ClassDB::bind_method(D_METHOD("set_thread_safe", "property", "value"), &Node::set_thread_safe); + ClassDB::bind_method(D_METHOD("notify_thread_safe", "what"), &Node::notify_thread_safe); + BIND_CONSTANT(NOTIFICATION_ENTER_TREE); BIND_CONSTANT(NOTIFICATION_EXIT_TREE); BIND_CONSTANT(NOTIFICATION_MOVED_IN_PARENT); @@ -3029,6 +3418,14 @@ void Node::_bind_methods() { BIND_ENUM_CONSTANT(PROCESS_MODE_ALWAYS); BIND_ENUM_CONSTANT(PROCESS_MODE_DISABLED); + BIND_ENUM_CONSTANT(PROCESS_THREAD_GROUP_INHERIT); + BIND_ENUM_CONSTANT(PROCESS_THREAD_GROUP_MAIN_THREAD); + BIND_ENUM_CONSTANT(PROCESS_THREAD_GROUP_SUB_THREAD); + + BIND_ENUM_CONSTANT(FLAG_PROCESS_THREAD_MESSAGES); + BIND_ENUM_CONSTANT(FLAG_PROCESS_THREAD_MESSAGES_PHYSICS); + BIND_ENUM_CONSTANT(FLAG_PROCESS_THREAD_MESSAGES_ALL); + BIND_ENUM_CONSTANT(DUPLICATE_SIGNALS); BIND_ENUM_CONSTANT(DUPLICATE_GROUPS); BIND_ENUM_CONSTANT(DUPLICATE_SCRIPTS); @@ -3056,6 +3453,11 @@ void Node::_bind_methods() { ADD_GROUP("Process", "process_"); ADD_PROPERTY(PropertyInfo(Variant::INT, "process_mode", PROPERTY_HINT_ENUM, "Inherit,Pausable,When Paused,Always,Disabled"), "set_process_mode", "get_process_mode"); ADD_PROPERTY(PropertyInfo(Variant::INT, "process_priority"), "set_process_priority", "get_process_priority"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "process_physics_priority"), "set_physics_process_priority", "get_physics_process_priority"); + ADD_SUBGROUP("Thread Group", "process_thread"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "process_thread_group", PROPERTY_HINT_ENUM, "Inherit,Main Thread,Sub Thread"), "set_process_thread_group", "get_process_thread_group"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "process_thread_group_order"), "set_process_thread_group_order", "get_process_thread_group_order"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "process_thread_messages", PROPERTY_HINT_FLAGS, "Process,Physics Process"), "set_process_thread_messages", "get_process_thread_messages"); ADD_GROUP("Editor Description", "editor_"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "editor_description", PROPERTY_HINT_MULTILINE_TEXT), "set_editor_description", "get_editor_description"); diff --git a/scene/main/node.h b/scene/main/node.h index 7cccf8e702..0b242eaf97 100644 --- a/scene/main/node.h +++ b/scene/main/node.h @@ -54,6 +54,18 @@ public: PROCESS_MODE_DISABLED, // never process }; + enum ProcessThreadGroup { + PROCESS_THREAD_GROUP_INHERIT, + PROCESS_THREAD_GROUP_MAIN_THREAD, + PROCESS_THREAD_GROUP_SUB_THREAD, + }; + + enum ProcessThreadMessages { + FLAG_PROCESS_THREAD_MESSAGES = 1, + FLAG_PROCESS_THREAD_MESSAGES_PHYSICS = 2, + FLAG_PROCESS_THREAD_MESSAGES_ALL = 3, + }; + enum DuplicateFlags { DUPLICATE_SIGNALS = 1, DUPLICATE_GROUPS = 2, @@ -80,12 +92,10 @@ public: bool operator()(const Node *p_a, const Node *p_b) const { return p_b->is_greater_than(p_a); } }; - struct ComparatorWithPriority { - bool operator()(const Node *p_a, const Node *p_b) const { return p_b->data.process_priority == p_a->data.process_priority ? p_b->is_greater_than(p_a) : p_b->data.process_priority > p_a->data.process_priority; } - }; - static int orphan_node_count; + void _update_process(bool p_enable, bool p_for_children); + private: struct GroupData { bool persistent = false; @@ -104,6 +114,14 @@ private: } }; + struct ComparatorWithPriority { + bool operator()(const Node *p_a, const Node *p_b) const { return p_b->data.process_priority == p_a->data.process_priority ? p_b->is_greater_than(p_a) : p_b->data.process_priority > p_a->data.process_priority; } + }; + + struct ComparatorWithPhysicsPriority { + bool operator()(const Node *p_a, const Node *p_b) const { return p_b->data.physics_process_priority == p_a->data.physics_process_priority ? p_b->is_greater_than(p_a) : p_b->data.physics_process_priority > p_a->data.physics_process_priority; } + }; + // This Data struct is to avoid namespace pollution in derived classes. struct Data { String scene_file_path; @@ -142,6 +160,11 @@ private: ProcessMode process_mode = PROCESS_MODE_INHERIT; Node *process_owner = nullptr; + ProcessThreadGroup process_thread_group = PROCESS_THREAD_GROUP_INHERIT; + Node *process_thread_group_owner = nullptr; + int process_thread_group_order = 0; + BitField<ProcessThreadMessages> process_thread_messages; + void *process_group = nullptr; // to avoid cyclic dependency int multiplayer_authority = 1; // Server by default. Variant rpc_config; @@ -151,6 +174,7 @@ private: bool physics_process = false; bool process = false; int process_priority = 0; + int physics_process_priority = 0; bool physics_process_internal = false; bool process_internal = false; @@ -220,6 +244,19 @@ private: void _update_children_cache_impl() const; + // Process group management + void _add_process_group(); + void _remove_process_group(); + void _add_to_process_thread_group(); + void _remove_from_process_thread_group(); + void _remove_tree_from_process_thread_group(); + void _add_tree_to_process_thread_group(Node *p_owner); + + static thread_local Node *current_process_thread_group; + + Variant _call_deferred_thread_group_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error); + Variant _call_thread_safe_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error); + protected: void _block() { data.blocked++; } void _unblock() { data.blocked--; } @@ -248,6 +285,8 @@ protected: void _call_unhandled_input(const Ref<InputEvent> &p_event); void _call_unhandled_key_input(const Ref<InputEvent> &p_event); + void _validate_property(PropertyInfo &p_property) const; + protected: virtual void input(const Ref<InputEvent> &p_event); virtual void shortcut_input(const Ref<InputEvent> &p_key_event); @@ -456,6 +495,12 @@ public: void set_process_priority(int p_priority); int get_process_priority() const; + void set_process_thread_group_order(int p_order); + int get_process_thread_group_order() const; + + void set_physics_process_priority(int p_priority); + int get_physics_process_priority() const; + void set_process_input(bool p_enable); bool is_processing_input() const; @@ -468,6 +513,23 @@ public: void set_process_unhandled_key_input(bool p_enable); bool is_processing_unhandled_key_input() const; + _FORCE_INLINE_ bool _is_any_processing() const { + return data.process || data.process_internal || data.physics_process || data.physics_process_internal; + } + _FORCE_INLINE_ bool is_accessible_from_caller_thread() const { + if (current_process_thread_group == nullptr) { + // Not thread processing. Only accessible if node is outside the scene tree, + // or if accessing from the main thread. + return !data.inside_tree || Thread::is_main_thread(); + } else { + // Thread processing + return current_process_thread_group == data.process_thread_group_owner; + } + } + + void set_process_thread_messages(BitField<ProcessThreadMessages> p_flags); + BitField<ProcessThreadMessages> get_process_thread_messages() const; + Node *duplicate(int p_flags = DUPLICATE_GROUPS | DUPLICATE_SIGNALS | DUPLICATE_SCRIPTS) const; #ifdef TOOLS_ENABLED Node *duplicate_from_editor(HashMap<const Node *, Node *> &r_duplimap) const; @@ -499,10 +561,13 @@ public: bool can_process() const; bool can_process_notification(int p_what) const; bool is_enabled() const; - bool is_ready() const; + void request_ready(); + void set_process_thread_group(ProcessThreadGroup p_mode); + ProcessThreadGroup get_process_thread_group() const; + static void print_orphan_nodes(); #ifdef TOOLS_ENABLED @@ -554,6 +619,32 @@ public: Ref<MultiplayerAPI> get_multiplayer() const; + void call_deferred_thread_groupp(const StringName &p_method, const Variant **p_args, int p_argcount, bool p_show_error = false); + template <typename... VarArgs> + void call_deferred_thread_group(const StringName &p_method, VarArgs... p_args) { + Variant args[sizeof...(p_args) + 1] = { p_args..., Variant() }; // +1 makes sure zero sized arrays are also supported. + const Variant *argptrs[sizeof...(p_args) + 1]; + for (uint32_t i = 0; i < sizeof...(p_args); i++) { + argptrs[i] = &args[i]; + } + call_deferred_thread_groupp(p_method, sizeof...(p_args) == 0 ? nullptr : (const Variant **)argptrs, sizeof...(p_args)); + } + void set_deferred_thread_group(const StringName &p_property, const Variant &p_value); + void notify_deferred_thread_group(int p_notification); + + void call_thread_safep(const StringName &p_method, const Variant **p_args, int p_argcount, bool p_show_error = false); + template <typename... VarArgs> + void call_thread_safe(const StringName &p_method, VarArgs... p_args) { + Variant args[sizeof...(p_args) + 1] = { p_args..., Variant() }; // +1 makes sure zero sized arrays are also supported. + const Variant *argptrs[sizeof...(p_args) + 1]; + for (uint32_t i = 0; i < sizeof...(p_args); i++) { + argptrs[i] = &args[i]; + } + call_deferred_thread_groupp(p_method, sizeof...(p_args) == 0 ? nullptr : (const Variant **)argptrs, sizeof...(p_args)); + } + void set_thread_safe(const StringName &p_property, const Variant &p_value); + void notify_thread_safe(int p_notification); + Node(); ~Node(); }; @@ -580,6 +671,18 @@ Error Node::rpc_id(int p_peer_id, const StringName &p_method, VarArgs... p_args) return rpcp(p_peer_id, p_method, sizeof...(p_args) == 0 ? nullptr : (const Variant **)argptrs, sizeof...(p_args)); } +#ifdef DEBUG_ENABLED +#define ERR_THREAD_GUARD ERR_FAIL_COND_MSG(!is_accessible_from_caller_thread(), "Caller thread can't call this function in this node. Use call_deferred() or call_thread_group() instead."); +#define ERR_THREAD_GUARD_V(m_ret) ERR_FAIL_COND_V_MSG(!is_accessible_from_caller_thread(), (m_ret), "Caller thread can't call this function in this node. Use call_deferred() or call_thread_group() instead.") +#define ERR_MAIN_THREAD_GUARD ERR_FAIL_COND_MSG(is_inside_tree() && !Thread::is_main_thread(), "This function in this node can only be accessed from the main thread. Use call_deferred() instead."); +#define ERR_MAIN_THREAD_GUARD_V(m_ret) ERR_FAIL_COND_V_MSG(is_inside_tree() && !Thread::is_main_thread(), (m_ret), "This function in this node can only be accessed from the main thread. Use call_deferred() instead.") +#else +#define ERR_THREAD_GUARD +#define ERR_THREAD_GUARD_V(m_ret) +#define ERR_MAIN_THREAD_GUARD +#define ERR_MAIN_THREAD_GUARD_V(m_ret) +#endif + // Add these macro to your class's 'get_configuration_warnings' function to have warnings show up in the scene tree inspector. #define DEPRECATED_NODE_WARNING warnings.push_back(RTR("This node is marked as deprecated and will be removed in future versions.\nPlease check the Godot documentation for information about migration.")); #define EXPERIMENTAL_NODE_WARNING warnings.push_back(RTR("This node is marked as experimental and may be subject to removal or major changes in future versions.")); diff --git a/scene/main/scene_tree.cpp b/scene/main/scene_tree.cpp index b6b694ff55..892cbb313b 100644 --- a/scene/main/scene_tree.cpp +++ b/scene/main/scene_tree.cpp @@ -38,6 +38,7 @@ #include "core/io/marshalls.h" #include "core/io/resource_loader.h" #include "core/object/message_queue.h" +#include "core/object/worker_thread_pool.h" #include "core/os/keyboard.h" #include "core/os/os.h" #include "core/string/print_string.h" @@ -60,7 +61,6 @@ #include "servers/physics_server_2d.h" #include "servers/physics_server_3d.h" #include "window.h" - #include <stdio.h> #include <stdlib.h> @@ -126,12 +126,13 @@ void SceneTree::node_added(Node *p_node) { } void SceneTree::node_removed(Node *p_node) { + // Nodes can only be removed from the main thread. if (current_scene == p_node) { current_scene = nullptr; } emit_signal(node_removed_name, p_node); - if (call_lock > 0) { - call_skip.insert(p_node); + if (nodes_removed_on_group_call_lock) { + nodes_removed_on_group_call.insert(p_node); } } @@ -140,6 +141,8 @@ void SceneTree::node_renamed(Node *p_node) { } SceneTree::Group *SceneTree::add_to_group(const StringName &p_group, Node *p_node) { + _THREAD_SAFE_METHOD_ + HashMap<StringName, Group>::Iterator E = group_map.find(p_group); if (!E) { E = group_map.insert(p_group, Group()); @@ -153,6 +156,8 @@ SceneTree::Group *SceneTree::add_to_group(const StringName &p_group, Node *p_nod } void SceneTree::remove_from_group(const StringName &p_group, Node *p_node) { + _THREAD_SAFE_METHOD_ + HashMap<StringName, Group>::Iterator E = group_map.find(p_group); ERR_FAIL_COND(!E); @@ -163,6 +168,7 @@ void SceneTree::remove_from_group(const StringName &p_group, Node *p_node) { } void SceneTree::make_group_changed(const StringName &p_group) { + _THREAD_SAFE_METHOD_ HashMap<StringName, Group>::Iterator E = group_map.find(p_group); if (E) { E->value.changed = true; @@ -170,6 +176,8 @@ void SceneTree::make_group_changed(const StringName &p_group) { } void SceneTree::flush_transform_notifications() { + _THREAD_SAFE_METHOD_ + SelfList<Node> *n = xform_change_list.first(); while (n) { Node *node = n->self(); @@ -200,7 +208,7 @@ void SceneTree::_flush_ugc() { ugc_locked = false; } -void SceneTree::_update_group_order(Group &g, bool p_use_priority) { +void SceneTree::_update_group_order(Group &g) { if (!g.changed) { return; } @@ -211,57 +219,62 @@ void SceneTree::_update_group_order(Group &g, bool p_use_priority) { Node **gr_nodes = g.nodes.ptrw(); int gr_node_count = g.nodes.size(); - if (p_use_priority) { - SortArray<Node *, Node::ComparatorWithPriority> node_sort; - node_sort.sort(gr_nodes, gr_node_count); - } else { - SortArray<Node *, Node::Comparator> node_sort; - node_sort.sort(gr_nodes, gr_node_count); - } + SortArray<Node *, Node::Comparator> node_sort; + node_sort.sort(gr_nodes, gr_node_count); + g.changed = false; } void SceneTree::call_group_flagsp(uint32_t p_call_flags, const StringName &p_group, const StringName &p_function, const Variant **p_args, int p_argcount) { - HashMap<StringName, Group>::Iterator E = group_map.find(p_group); - if (!E) { - return; - } - Group &g = E->value; - if (g.nodes.is_empty()) { - return; - } - - if (p_call_flags & GROUP_CALL_UNIQUE && p_call_flags & GROUP_CALL_DEFERRED) { - ERR_FAIL_COND(ugc_locked); + Vector<Node *> nodes_copy; - UGCall ug; - ug.call = p_function; - ug.group = p_group; + { + _THREAD_SAFE_METHOD_ - if (unique_group_calls.has(ug)) { + HashMap<StringName, Group>::Iterator E = group_map.find(p_group); + if (!E) { return; } + Group &g = E->value; + if (g.nodes.is_empty()) { + return; + } + + if (p_call_flags & GROUP_CALL_UNIQUE && p_call_flags & GROUP_CALL_DEFERRED) { + ERR_FAIL_COND(ugc_locked); + + UGCall ug; + ug.call = p_function; + ug.group = p_group; + + if (unique_group_calls.has(ug)) { + return; + } - Vector<Variant> args; - for (int i = 0; i < p_argcount; i++) { - args.push_back(*p_args[i]); + Vector<Variant> args; + for (int i = 0; i < p_argcount; i++) { + args.push_back(*p_args[i]); + } + + unique_group_calls[ug] = args; + return; } - unique_group_calls[ug] = args; - return; + _update_group_order(g); + nodes_copy = g.nodes; } - _update_group_order(g); - - Vector<Node *> nodes_copy = g.nodes; Node **gr_nodes = nodes_copy.ptrw(); int gr_node_count = nodes_copy.size(); - call_lock++; + { + _THREAD_SAFE_METHOD_ + nodes_removed_on_group_call_lock++; + } if (p_call_flags & GROUP_CALL_REVERSE) { for (int i = gr_node_count - 1; i >= 0; i--) { - if (call_lock && call_skip.has(gr_nodes[i])) { + if (nodes_removed_on_group_call_lock && nodes_removed_on_group_call.has(gr_nodes[i])) { continue; } @@ -275,7 +288,7 @@ void SceneTree::call_group_flagsp(uint32_t p_call_flags, const StringName &p_gro } else { for (int i = 0; i < gr_node_count; i++) { - if (call_lock && call_skip.has(gr_nodes[i])) { + if (nodes_removed_on_group_call_lock && nodes_removed_on_group_call.has(gr_nodes[i])) { continue; } @@ -288,33 +301,44 @@ void SceneTree::call_group_flagsp(uint32_t p_call_flags, const StringName &p_gro } } - call_lock--; - if (call_lock == 0) { - call_skip.clear(); + { + _THREAD_SAFE_METHOD_ + nodes_removed_on_group_call_lock--; + if (nodes_removed_on_group_call_lock == 0) { + nodes_removed_on_group_call.clear(); + } } } void SceneTree::notify_group_flags(uint32_t p_call_flags, const StringName &p_group, int p_notification) { - HashMap<StringName, Group>::Iterator E = group_map.find(p_group); - if (!E) { - return; - } - Group &g = E->value; - if (g.nodes.is_empty()) { - return; - } + Vector<Node *> nodes_copy; + { + _THREAD_SAFE_METHOD_ + HashMap<StringName, Group>::Iterator E = group_map.find(p_group); + if (!E) { + return; + } + Group &g = E->value; + if (g.nodes.is_empty()) { + return; + } - _update_group_order(g); + _update_group_order(g); + + nodes_copy = g.nodes; + } - Vector<Node *> nodes_copy = g.nodes; Node **gr_nodes = nodes_copy.ptrw(); int gr_node_count = nodes_copy.size(); - call_lock++; + { + _THREAD_SAFE_METHOD_ + nodes_removed_on_group_call_lock++; + } if (p_call_flags & GROUP_CALL_REVERSE) { for (int i = gr_node_count - 1; i >= 0; i--) { - if (call_lock && call_skip.has(gr_nodes[i])) { + if (nodes_removed_on_group_call.has(gr_nodes[i])) { continue; } @@ -327,7 +351,7 @@ void SceneTree::notify_group_flags(uint32_t p_call_flags, const StringName &p_gr } else { for (int i = 0; i < gr_node_count; i++) { - if (call_lock && call_skip.has(gr_nodes[i])) { + if (nodes_removed_on_group_call.has(gr_nodes[i])) { continue; } @@ -339,33 +363,44 @@ void SceneTree::notify_group_flags(uint32_t p_call_flags, const StringName &p_gr } } - call_lock--; - if (call_lock == 0) { - call_skip.clear(); + { + _THREAD_SAFE_METHOD_ + nodes_removed_on_group_call_lock--; + if (nodes_removed_on_group_call_lock == 0) { + nodes_removed_on_group_call.clear(); + } } } void SceneTree::set_group_flags(uint32_t p_call_flags, const StringName &p_group, const String &p_name, const Variant &p_value) { - HashMap<StringName, Group>::Iterator E = group_map.find(p_group); - if (!E) { - return; - } - Group &g = E->value; - if (g.nodes.is_empty()) { - return; - } + Vector<Node *> nodes_copy; + { + _THREAD_SAFE_METHOD_ - _update_group_order(g); + HashMap<StringName, Group>::Iterator E = group_map.find(p_group); + if (!E) { + return; + } + Group &g = E->value; + if (g.nodes.is_empty()) { + return; + } + + _update_group_order(g); - Vector<Node *> nodes_copy = g.nodes; + nodes_copy = g.nodes; + } Node **gr_nodes = nodes_copy.ptrw(); int gr_node_count = nodes_copy.size(); - call_lock++; + { + _THREAD_SAFE_METHOD_ + nodes_removed_on_group_call_lock++; + } if (p_call_flags & GROUP_CALL_REVERSE) { for (int i = gr_node_count - 1; i >= 0; i--) { - if (call_lock && call_skip.has(gr_nodes[i])) { + if (nodes_removed_on_group_call.has(gr_nodes[i])) { continue; } @@ -378,7 +413,7 @@ void SceneTree::set_group_flags(uint32_t p_call_flags, const StringName &p_group } else { for (int i = 0; i < gr_node_count; i++) { - if (call_lock && call_skip.has(gr_nodes[i])) { + if (nodes_removed_on_group_call.has(gr_nodes[i])) { continue; } @@ -390,9 +425,12 @@ void SceneTree::set_group_flags(uint32_t p_call_flags, const StringName &p_group } } - call_lock--; - if (call_lock == 0) { - call_skip.clear(); + { + _THREAD_SAFE_METHOD_ + nodes_removed_on_group_call_lock--; + if (nodes_removed_on_group_call_lock == 0) { + nodes_removed_on_group_call.clear(); + } } } @@ -423,9 +461,10 @@ bool SceneTree::physics_process(double p_time) { emit_signal(SNAME("physics_frame")); - _notify_group_pause(SNAME("_physics_process_internal"), Node::NOTIFICATION_INTERNAL_PHYSICS_PROCESS); call_group(SNAME("_picking_viewports"), SNAME("_process_picking")); - _notify_group_pause(SNAME("_physics_process"), Node::NOTIFICATION_PHYSICS_PROCESS); + + _process(true); + _flush_ugc(); MessageQueue::get_singleton()->flush(); //small little hack @@ -462,8 +501,7 @@ bool SceneTree::process(double p_time) { flush_transform_notifications(); - _notify_group_pause(SNAME("_process_internal"), Node::NOTIFICATION_INTERNAL_PROCESS); - _notify_group_pause(SNAME("_process"), Node::NOTIFICATION_PROCESS); + _process(false); _flush_ugc(); MessageQueue::get_singleton()->flush(); //small little hack @@ -512,6 +550,7 @@ bool SceneTree::process(double p_time) { } void SceneTree::process_timers(double p_delta, bool p_physics_frame) { + _THREAD_SAFE_METHOD_ List<Ref<SceneTreeTimer>>::Element *L = timers.back(); //last element for (List<Ref<SceneTreeTimer>>::Element *E = timers.front(); E;) { @@ -544,6 +583,7 @@ void SceneTree::process_timers(double p_delta, bool p_physics_frame) { } void SceneTree::process_tweens(double p_delta, bool p_physics) { + _THREAD_SAFE_METHOD_ // This methods works similarly to how SceneTreeTimers are handled. List<Ref<Tween>>::Element *L = tweens.back(); @@ -603,6 +643,8 @@ void SceneTree::finalize() { } void SceneTree::quit(int p_exit_code) { + _THREAD_SAFE_METHOD_ + OS::get_singleton()->set_exit_code(p_exit_code); _quit = true; } @@ -695,6 +737,14 @@ void SceneTree::set_debug_navigation_hint(bool p_enabled) { bool SceneTree::is_debugging_navigation_hint() const { return debug_navigation_hint; } + +void SceneTree::set_debug_avoidance_hint(bool p_enabled) { + debug_avoidance_hint = p_enabled; +} + +bool SceneTree::is_debugging_avoidance_hint() const { + return debug_avoidance_hint; +} #endif void SceneTree::set_debug_collisions_color(const Color &p_color) { @@ -730,6 +780,8 @@ float SceneTree::get_debug_paths_width() const { } Ref<Material> SceneTree::get_debug_paths_material() { + _THREAD_SAFE_METHOD_ + if (debug_paths_material.is_valid()) { return debug_paths_material; } @@ -747,6 +799,8 @@ Ref<Material> SceneTree::get_debug_paths_material() { } Ref<Material> SceneTree::get_debug_collision_material() { + _THREAD_SAFE_METHOD_ + if (collision_material.is_valid()) { return collision_material; } @@ -764,6 +818,8 @@ Ref<Material> SceneTree::get_debug_collision_material() { } Ref<ArrayMesh> SceneTree::get_debug_contact_mesh() { + _THREAD_SAFE_METHOD_ + if (debug_contact_mesh.is_valid()) { return debug_contact_mesh; } @@ -821,6 +877,8 @@ Ref<ArrayMesh> SceneTree::get_debug_contact_mesh() { } void SceneTree::set_pause(bool p_enabled) { + ERR_FAIL_COND_MSG(!Thread::is_main_thread(), "Pause can only be set from the main thread."); + if (p_enabled == paused) { return; } @@ -836,70 +894,282 @@ bool SceneTree::is_paused() const { return paused; } -void SceneTree::_notify_group_pause(const StringName &p_group, int p_notification) { - HashMap<StringName, Group>::Iterator E = group_map.find(p_group); - if (!E) { - return; - } - Group &g = E->value; - if (g.nodes.is_empty()) { +void SceneTree::_process_group(ProcessGroup *p_group, bool p_physics) { + // When reading this function, keep in mind that this code must work in a way where + // if any node is removed, this needs to continue working. + + p_group->call_queue.flush(); // Flush messages before processing. + + Vector<Node *> &nodes = p_physics ? p_group->physics_nodes : p_group->nodes; + if (nodes.is_empty()) { return; } - _update_group_order(g, p_notification == Node::NOTIFICATION_PROCESS || p_notification == Node::NOTIFICATION_INTERNAL_PROCESS || p_notification == Node::NOTIFICATION_PHYSICS_PROCESS || p_notification == Node::NOTIFICATION_INTERNAL_PHYSICS_PROCESS); + bool &node_order_dirty = p_physics ? p_group->physics_node_order_dirty : p_group->node_order_dirty; - //copy, so copy on write happens in case something is removed from process while being called - //performance is not lost because only if something is added/removed the vector is copied. - Vector<Node *> nodes_copy = g.nodes; + if (node_order_dirty) { + nodes.sort_custom<Node::ComparatorWithPhysicsPriority>(); + node_order_dirty = false; + } - int gr_node_count = nodes_copy.size(); - Node **gr_nodes = nodes_copy.ptrw(); + // Make a copy, so if nodes are added/removed from process, this does not break + Vector<Node *> nodes_copy = nodes; - call_lock++; + uint32_t node_count = nodes_copy.size(); + Node **nodes_ptr = (Node **)nodes_copy.ptr(); // Force cast, pointer will not change. - for (int i = 0; i < gr_node_count; i++) { - Node *n = gr_nodes[i]; - if (call_lock && call_skip.has(n)) { + for (uint32_t i = 0; i < node_count; i++) { + Node *n = nodes_ptr[i]; + if (nodes_removed_on_group_call.has(n)) { + // Node may have been removed during process, skip it. + // Keep in mind removals can only happen on the main thread. continue; } - if (!n->can_process()) { + if (!n->can_process() || !n->is_inside_tree()) { continue; } - if (!n->can_process_notification(p_notification)) { + + if (p_physics) { + if (n->is_physics_processing()) { + n->notification(Node::NOTIFICATION_PHYSICS_PROCESS); + } + if (n->is_physics_processing_internal()) { + n->notification(Node::NOTIFICATION_INTERNAL_PHYSICS_PROCESS); + } + } else { + if (n->is_processing()) { + n->notification(Node::NOTIFICATION_PROCESS); + } + if (n->is_processing_internal()) { + n->notification(Node::NOTIFICATION_INTERNAL_PROCESS); + } + } + } + + p_group->call_queue.flush(); // Flush messages also after processing (for potential deferred calls). +} + +void SceneTree::_process_groups_thread(uint32_t p_index, bool p_physics) { + Node::current_process_thread_group = local_process_group_cache[p_index]->owner; + _process_group(local_process_group_cache[p_index], p_physics); + Node::current_process_thread_group = nullptr; +} + +void SceneTree::_process(bool p_physics) { + if (process_groups_dirty) { + { + // First, remove dirty groups. + // This needs to be done when not processing to avoid problems. + ProcessGroup **pg_ptr = (ProcessGroup **)process_groups.ptr(); // discard constness. + uint32_t pg_count = process_groups.size(); + + for (uint32_t i = 0; i < pg_count; i++) { + if (pg_ptr[i]->removed) { + // Replace removed with last. + pg_ptr[i] = pg_ptr[pg_count - 1]; + // Retry + i--; + pg_count--; + } + } + if (pg_count != process_groups.size()) { + process_groups.resize(pg_count); + } + } + { + // Then, re-sort groups. + process_groups.sort_custom<ProcessGroupSort>(); + } + + process_groups_dirty = false; + } + + // Cache the group count, because during processing new groups may be added. + // They will be added at the end, hence for consistency they will be ignored by this process loop. + // No group will be removed from the array during processing (this is done earlier in this function by marking the groups dirty). + uint32_t group_count = process_groups.size(); + + if (group_count == 0) { + return; + } + + process_last_pass++; // Increment pass + uint32_t from = 0; + uint32_t process_count = 0; + nodes_removed_on_group_call_lock++; + + int current_order = process_groups[0]->owner ? process_groups[0]->owner->data.process_thread_group_order : 0; + bool current_threaded = process_groups[0]->owner ? process_groups[0]->owner->data.process_thread_group == Node::PROCESS_THREAD_GROUP_SUB_THREAD : false; + + for (uint32_t i = 0; i <= group_count; i++) { + int order = i < group_count && process_groups[i]->owner ? process_groups[i]->owner->data.process_thread_group_order : 0; + bool threaded = i < group_count && process_groups[i]->owner ? process_groups[i]->owner->data.process_thread_group == Node::PROCESS_THREAD_GROUP_SUB_THREAD : false; + + if (i == group_count || current_order != order || current_threaded != threaded) { + if (process_count > 0) { + // Proceed to process the group. + bool using_threads = process_groups[from]->owner && process_groups[from]->owner->data.process_thread_group == Node::PROCESS_THREAD_GROUP_SUB_THREAD && !node_threading_disabled; + + if (using_threads) { + local_process_group_cache.clear(); + } + for (uint32_t j = from; j < i; j++) { + if (process_groups[j]->last_pass == process_last_pass) { + if (using_threads) { + local_process_group_cache.push_back(process_groups[j]); + } else { + _process_group(process_groups[j], p_physics); + } + } + } + + if (using_threads) { + WorkerThreadPool::GroupID id = WorkerThreadPool::get_singleton()->add_template_group_task(this, &SceneTree::_process_groups_thread, p_physics, local_process_group_cache.size(), -1, true); + WorkerThreadPool::get_singleton()->wait_for_group_task_completion(id); + } + } + + if (i == group_count) { + // This one is invalid, no longer process + break; + } + + from = i; + current_threaded = threaded; + current_order = order; + } + + if (process_groups[i]->removed) { continue; } - n->notification(p_notification); - //ERR_FAIL_COND(gr_node_count != g.nodes.size()); + ProcessGroup *pg = process_groups[i]; + + // Validate group for processing + bool process_valid = false; + if (p_physics) { + if (!pg->physics_nodes.is_empty()) { + process_valid = true; + } else if (pg->owner != nullptr && pg->owner->data.process_thread_messages.has_flag(Node::FLAG_PROCESS_THREAD_MESSAGES_PHYSICS) && pg->call_queue.has_messages()) { + process_valid = true; + } + } else { + if (!pg->nodes.is_empty()) { + process_valid = true; + } else if (pg->owner != nullptr && pg->owner->data.process_thread_messages.has_flag(Node::FLAG_PROCESS_THREAD_MESSAGES) && pg->call_queue.has_messages()) { + process_valid = true; + } + } + + if (process_valid) { + pg->last_pass = process_last_pass; // Enable for processing + process_count++; + } } - call_lock--; - if (call_lock == 0) { - call_skip.clear(); + nodes_removed_on_group_call_lock--; + if (nodes_removed_on_group_call_lock == 0) { + nodes_removed_on_group_call.clear(); } } -void SceneTree::_call_input_pause(const StringName &p_group, CallInputType p_call_type, const Ref<InputEvent> &p_input, Viewport *p_viewport) { - HashMap<StringName, Group>::Iterator E = group_map.find(p_group); - if (!E) { - return; +bool SceneTree::ProcessGroupSort::operator()(const ProcessGroup *p_left, const ProcessGroup *p_right) const { + int left_order = p_left->owner ? p_left->owner->data.process_thread_group_order : 0; + int right_order = p_right->owner ? p_right->owner->data.process_thread_group_order : 0; + + if (left_order == right_order) { + int left_threaded = p_left->owner != nullptr && p_left->owner->data.process_thread_group == Node::PROCESS_THREAD_GROUP_SUB_THREAD ? 0 : 1; + int right_threaded = p_right->owner != nullptr && p_right->owner->data.process_thread_group == Node::PROCESS_THREAD_GROUP_SUB_THREAD ? 0 : 1; + return left_threaded < right_threaded; + } else { + return left_order < right_order; } - Group &g = E->value; - if (g.nodes.is_empty()) { - return; +} + +void SceneTree::_remove_process_group(Node *p_node) { + _THREAD_SAFE_METHOD_ + ProcessGroup *pg = (ProcessGroup *)p_node->data.process_group; + ERR_FAIL_COND(!pg); + ERR_FAIL_COND(pg->removed); + pg->removed = true; + pg->owner = nullptr; + p_node->data.process_group = nullptr; + process_groups_dirty = true; +} + +void SceneTree::_add_process_group(Node *p_node) { + _THREAD_SAFE_METHOD_ + ERR_FAIL_COND(!p_node); + + ProcessGroup *pg = memnew(ProcessGroup); + + pg->owner = p_node; + p_node->data.process_group = pg; + + process_groups.push_back(pg); + + process_groups_dirty = true; +} + +void SceneTree::_remove_node_from_process_group(Node *p_node, Node *p_owner) { + _THREAD_SAFE_METHOD_ + ProcessGroup *pg = p_owner ? (ProcessGroup *)p_owner->data.process_group : &default_process_group; + + if (p_node->is_processing() || p_node->is_processing_internal()) { + bool found = pg->nodes.erase(p_node); + ERR_FAIL_COND(!found); + } + + if (p_node->is_physics_processing() || p_node->is_physics_processing_internal()) { + bool found = pg->physics_nodes.erase(p_node); + ERR_FAIL_COND(!found); + } +} + +void SceneTree::_add_node_to_process_group(Node *p_node, Node *p_owner) { + _THREAD_SAFE_METHOD_ + ProcessGroup *pg = p_owner ? (ProcessGroup *)p_owner->data.process_group : &default_process_group; + + if (p_node->is_processing() || p_node->is_processing_internal()) { + pg->nodes.push_back(p_node); + pg->node_order_dirty = true; } - _update_group_order(g); + if (p_node->is_physics_processing() || p_node->is_physics_processing_internal()) { + pg->physics_nodes.push_back(p_node); + pg->physics_node_order_dirty = true; + } +} - //copy, so copy on write happens in case something is removed from process while being called - //performance is not lost because only if something is added/removed the vector is copied. - Vector<Node *> nodes_copy = g.nodes; +void SceneTree::_call_input_pause(const StringName &p_group, CallInputType p_call_type, const Ref<InputEvent> &p_input, Viewport *p_viewport) { + Vector<Node *> nodes_copy; + { + _THREAD_SAFE_METHOD_ + + HashMap<StringName, Group>::Iterator E = group_map.find(p_group); + if (!E) { + return; + } + Group &g = E->value; + if (g.nodes.is_empty()) { + return; + } + + _update_group_order(g); + + //copy, so copy on write happens in case something is removed from process while being called + //performance is not lost because only if something is added/removed the vector is copied. + nodes_copy = g.nodes; + } int gr_node_count = nodes_copy.size(); Node **gr_nodes = nodes_copy.ptrw(); - call_lock++; + { + _THREAD_SAFE_METHOD_ + nodes_removed_on_group_call_lock++; + } Vector<ObjectID> no_context_node_ids; // Nodes may be deleted due to this shortcut input. @@ -909,7 +1179,7 @@ void SceneTree::_call_input_pause(const StringName &p_group, CallInputType p_cal } Node *n = gr_nodes[i]; - if (call_lock && call_skip.has(n)) { + if (nodes_removed_on_group_call.has(n)) { continue; } @@ -956,9 +1226,12 @@ void SceneTree::_call_input_pause(const StringName &p_group, CallInputType p_cal } } - call_lock--; - if (call_lock == 0) { - call_skip.clear(); + { + _THREAD_SAFE_METHOD_ + nodes_removed_on_group_call_lock--; + if (nodes_removed_on_group_call_lock == 0) { + nodes_removed_on_group_call.clear(); + } } } @@ -995,6 +1268,7 @@ int64_t SceneTree::get_frame() const { } TypedArray<Node> SceneTree::_get_nodes_in_group(const StringName &p_group) { + _THREAD_SAFE_METHOD_ TypedArray<Node> ret; HashMap<StringName, Group>::Iterator E = group_map.find(p_group); if (!E) { @@ -1018,10 +1292,12 @@ TypedArray<Node> SceneTree::_get_nodes_in_group(const StringName &p_group) { } bool SceneTree::has_group(const StringName &p_identifier) const { + _THREAD_SAFE_METHOD_ return group_map.has(p_identifier); } Node *SceneTree::get_first_node_in_group(const StringName &p_group) { + _THREAD_SAFE_METHOD_ HashMap<StringName, Group>::Iterator E = group_map.find(p_group); if (!E) { return nullptr; // No group. @@ -1037,6 +1313,7 @@ Node *SceneTree::get_first_node_in_group(const StringName &p_group) { } void SceneTree::get_nodes_in_group(const StringName &p_group, List<Node *> *p_list) { + _THREAD_SAFE_METHOD_ HashMap<StringName, Group>::Iterator E = group_map.find(p_group); if (!E) { return; @@ -1073,7 +1350,7 @@ void SceneTree::queue_delete(Object *p_object) { } int SceneTree::get_node_count() const { - return node_count; + return nodes_in_tree_count; } void SceneTree::set_edited_scene_root(Node *p_node) { @@ -1091,6 +1368,7 @@ Node *SceneTree::get_edited_scene_root() const { } void SceneTree::set_current_scene(Node *p_scene) { + ERR_FAIL_COND_MSG(!Thread::is_main_thread(), "Changing scene can only be done from the main thread."); ERR_FAIL_COND(p_scene && p_scene->get_parent() != root); current_scene = p_scene; } @@ -1100,6 +1378,7 @@ Node *SceneTree::get_current_scene() const { } void SceneTree::_change_scene(Node *p_to) { + ERR_FAIL_COND_MSG(!Thread::is_main_thread(), "Changing scene can only be done from the main thread."); if (current_scene) { memdelete(current_scene); current_scene = nullptr; @@ -1121,6 +1400,7 @@ void SceneTree::_change_scene(Node *p_to) { } Error SceneTree::change_scene_to_file(const String &p_path) { + ERR_FAIL_COND_V_MSG(!Thread::is_main_thread(), ERR_INVALID_PARAMETER, "Changing scene can only be done from the main thread."); Ref<PackedScene> new_scene = ResourceLoader::load(p_path); if (new_scene.is_null()) { return ERR_CANT_OPEN; @@ -1140,12 +1420,14 @@ Error SceneTree::change_scene_to_packed(const Ref<PackedScene> &p_scene) { } Error SceneTree::reload_current_scene() { + ERR_FAIL_COND_V_MSG(!Thread::is_main_thread(), ERR_INVALID_PARAMETER, "Reloading scene can only be done from the main thread."); ERR_FAIL_COND_V(!current_scene, ERR_UNCONFIGURED); String fname = current_scene->get_scene_file_path(); return change_scene_to_file(fname); } void SceneTree::unload_current_scene() { + ERR_FAIL_COND_MSG(!Thread::is_main_thread(), "Unloading the current scene can only be done from the main thread."); if (current_scene) { memdelete(current_scene); current_scene = nullptr; @@ -1153,11 +1435,13 @@ void SceneTree::unload_current_scene() { } void SceneTree::add_current_scene(Node *p_current) { + ERR_FAIL_COND_MSG(!Thread::is_main_thread(), "Adding a current scene can only be done from the main thread."); current_scene = p_current; root->add_child(p_current); } Ref<SceneTreeTimer> SceneTree::create_timer(double p_delay_sec, bool p_process_always, bool p_process_in_physics, bool p_ignore_time_scale) { + _THREAD_SAFE_METHOD_ Ref<SceneTreeTimer> stt; stt.instantiate(); stt->set_process_always(p_process_always); @@ -1169,12 +1453,14 @@ Ref<SceneTreeTimer> SceneTree::create_timer(double p_delay_sec, bool p_process_a } Ref<Tween> SceneTree::create_tween() { + _THREAD_SAFE_METHOD_ Ref<Tween> tween = memnew(Tween(true)); tweens.push_back(tween); return tween; } TypedArray<Tween> SceneTree::get_processed_tweens() { + _THREAD_SAFE_METHOD_ TypedArray<Tween> ret; ret.resize(tweens.size()); @@ -1188,6 +1474,7 @@ TypedArray<Tween> SceneTree::get_processed_tweens() { } Ref<MultiplayerAPI> SceneTree::get_multiplayer(const NodePath &p_for_path) const { + ERR_FAIL_COND_V_MSG(!Thread::is_main_thread(), Ref<MultiplayerAPI>(), "Multiplayer can only be manipulated from the main thread."); Ref<MultiplayerAPI> out = multiplayer; for (const KeyValue<NodePath, Ref<MultiplayerAPI>> &E : custom_multiplayers) { const Vector<StringName> snames = E.key.get_names(); @@ -1213,6 +1500,7 @@ Ref<MultiplayerAPI> SceneTree::get_multiplayer(const NodePath &p_for_path) const } void SceneTree::set_multiplayer(Ref<MultiplayerAPI> p_multiplayer, const NodePath &p_root_path) { + ERR_FAIL_COND_MSG(!Thread::is_main_thread(), "Multiplayer can only be manipulated from the main thread."); if (p_root_path.is_empty()) { ERR_FAIL_COND(!p_multiplayer.is_valid()); if (multiplayer.is_valid()) { @@ -1232,6 +1520,7 @@ void SceneTree::set_multiplayer(Ref<MultiplayerAPI> p_multiplayer, const NodePat } void SceneTree::set_multiplayer_poll_enabled(bool p_enabled) { + ERR_FAIL_COND_MSG(!Thread::is_main_thread(), "Multiplayer can only be manipulated from the main thread."); multiplayer_poll = p_enabled; } @@ -1385,6 +1674,10 @@ void SceneTree::get_argument_options(const StringName &p_function, int p_idx, Li } } +void SceneTree::set_disable_node_threading(bool p_disable) { + node_threading_disabled = p_disable; +} + SceneTree::SceneTree() { if (singleton == nullptr) { singleton = this; @@ -1397,6 +1690,7 @@ SceneTree::SceneTree() { GLOBAL_DEF("debug/shapes/collision/draw_2d_outlines", true); + process_group_call_queue_allocator = memnew(CallQueue::Allocator(64)); Math::randomize(); // Create with mainloop. @@ -1534,6 +1828,8 @@ SceneTree::SceneTree() { #ifdef TOOLS_ENABLED edited_scene_root = nullptr; #endif + + process_groups.push_back(&default_process_group); } SceneTree::~SceneTree() { @@ -1543,6 +1839,15 @@ SceneTree::~SceneTree() { memdelete(root); } + // Process groups are not deleted immediately, they may remain around. Delete them now. + for (uint32_t i = 0; i < process_groups.size(); i++) { + if (process_groups[i] != &default_process_group) { + memdelete(process_groups[i]); + } + } + + memdelete(process_group_call_queue_allocator); + if (singleton == this) { singleton = nullptr; } diff --git a/scene/main/scene_tree.h b/scene/main/scene_tree.h index fc185b4f37..4e7a10c7e9 100644 --- a/scene/main/scene_tree.h +++ b/scene/main/scene_tree.h @@ -33,6 +33,7 @@ #include "core/os/main_loop.h" #include "core/os/thread_safe.h" +#include "core/templates/paged_allocator.h" #include "core/templates/self_list.h" #include "scene/resources/mesh.h" @@ -86,6 +87,34 @@ public: typedef void (*IdleCallback)(); private: + CallQueue::Allocator *process_group_call_queue_allocator = nullptr; + + struct ProcessGroup { + CallQueue call_queue; + Vector<Node *> nodes; + Vector<Node *> physics_nodes; + bool node_order_dirty = true; + bool physics_node_order_dirty = true; + bool removed = false; + Node *owner = nullptr; + uint64_t last_pass = 0; + }; + + struct ProcessGroupSort { + _FORCE_INLINE_ bool operator()(const ProcessGroup *p_left, const ProcessGroup *p_right) const; + }; + + PagedAllocator<ProcessGroup, true> group_allocator; // Allocate groups on pages, to enhance cache usage. + + LocalVector<ProcessGroup *> process_groups; + bool process_groups_dirty = true; + LocalVector<ProcessGroup *> local_process_group_cache; // Used when processing to group what needs to + uint64_t process_last_pass = 1; + + ProcessGroup default_process_group; + + bool node_threading_disabled = false; + struct Group { Vector<Node *> nodes; bool changed = false; @@ -103,6 +132,7 @@ private: bool debug_collisions_hint = false; bool debug_paths_hint = false; bool debug_navigation_hint = false; + bool debug_avoidance_hint = false; #endif bool paused = false; int root_lock = 0; @@ -117,7 +147,7 @@ private: StringName node_renamed_name = "node_renamed"; int64_t current_frame = 0; - int node_count = 0; + int nodes_in_tree_count = 0; #ifdef TOOLS_ENABLED Node *edited_scene_root = nullptr; @@ -134,8 +164,10 @@ private: }; // Safety for when a node is deleted while a group is being called. - int call_lock = 0; - HashSet<Node *> call_skip; // Skip erased nodes. + + bool processing = false; + int nodes_removed_on_group_call_lock = 0; + HashSet<Node *> nodes_removed_on_group_call; // Skip erased nodes. List<ObjectID> delete_queue; @@ -143,7 +175,7 @@ private: bool ugc_locked = false; void _flush_ugc(); - _FORCE_INLINE_ void _update_group_order(Group &g, bool p_use_priority = false); + _FORCE_INLINE_ void _update_group_order(Group &g); TypedArray<Node> _get_nodes_in_group(const StringName &p_group); @@ -187,7 +219,15 @@ private: void remove_from_group(const StringName &p_group, Node *p_node); void make_group_changed(const StringName &p_group); - void _notify_group_pause(const StringName &p_group, int p_notification); + void _process_group(ProcessGroup *p_group, bool p_physics); + void _process_groups_thread(uint32_t p_index, bool p_physics); + void _process(bool p_physics); + + void _remove_process_group(Node *p_node); + void _add_process_group(Node *p_node); + void _remove_node_from_process_group(Node *p_node, Node *p_owner); + void _add_node_to_process_group(Node *p_node, Node *p_owner); + void _call_group_flags(const Variant **p_args, int p_argcount, Callable::CallError &r_error); void _call_group(const Variant **p_args, int p_argcount, Callable::CallError &r_error); @@ -311,6 +351,9 @@ public: void set_debug_navigation_hint(bool p_enabled); bool is_debugging_navigation_hint() const; + + void set_debug_avoidance_hint(bool p_enabled); + bool is_debugging_avoidance_hint() const; #else void set_debug_collisions_hint(bool p_enabled) {} bool is_debugging_collisions_hint() const { return false; } @@ -383,6 +426,7 @@ public: static void add_idle_callback(IdleCallback p_callback); + void set_disable_node_threading(bool p_disable); //default texture settings SceneTree(); diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp index 821e9d5006..e6043531ac 100644 --- a/scene/main/viewport.cpp +++ b/scene/main/viewport.cpp @@ -72,10 +72,9 @@ void ViewportTexture::setup_local_to_scene() { if (vp) { vp->viewport_textures.erase(this); + vp = nullptr; } - vp = nullptr; - if (loc_scene->is_ready()) { _setup_local_to_scene(loc_scene); } else { @@ -91,8 +90,24 @@ void ViewportTexture::set_viewport_path_in_scene(const NodePath &p_path) { path = p_path; - if (get_local_scene()) { + if (vp) { + vp->viewport_textures.erase(this); + vp = nullptr; + } + + if (proxy_ph.is_valid()) { + RS::get_singleton()->free(proxy_ph); + } + if (proxy.is_valid()) { + RS::get_singleton()->free(proxy); + } + proxy_ph = RID(); + proxy = RID(); + + if (get_local_scene() && !path.is_empty()) { setup_local_to_scene(); + } else { + emit_changed(); } } @@ -171,6 +186,8 @@ void ViewportTexture::_setup_local_to_scene(const Node *p_loc_scene) { proxy = RS::get_singleton()->texture_proxy_create(vp->texture_rid); } vp_pending = false; + + emit_changed(); } void ViewportTexture::_bind_methods() { @@ -433,9 +450,28 @@ int Viewport::_sub_window_find(Window *p_window) { return -1; } +void Viewport::_update_viewport_path() { + if (viewport_textures.is_empty()) { + return; + } + + Node *scene_root = get_scene_file_path().is_empty() ? get_owner() : this; + if (!scene_root && is_inside_tree()) { + scene_root = get_tree()->get_edited_scene_root(); + } + if (scene_root && (scene_root == this || scene_root->is_ancestor_of(this))) { + NodePath path_in_scene = scene_root->get_path_to(this); + for (ViewportTexture *E : viewport_textures) { + E->path = path_in_scene; + } + } +} + void Viewport::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: { + _update_viewport_path(); + if (get_parent()) { parent = get_parent()->get_viewport(); RenderingServer::get_singleton()->viewport_set_parent_viewport(viewport, parent->get_viewport_rid()); @@ -528,6 +564,10 @@ void Viewport::_notification(int p_what) { RenderingServer::get_singleton()->viewport_set_parent_viewport(viewport, RID()); } break; + case NOTIFICATION_PATH_RENAMED: { + _update_viewport_path(); + } break; + case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { if (!get_tree()) { return; diff --git a/scene/main/viewport.h b/scene/main/viewport.h index a7129f488f..7f93b21eed 100644 --- a/scene/main/viewport.h +++ b/scene/main/viewport.h @@ -317,6 +317,8 @@ private: Ref<ViewportTexture> default_texture; HashSet<ViewportTexture *> viewport_textures; + void _update_viewport_path(); + SDFOversize sdf_oversize = SDF_OVERSIZE_120_PERCENT; SDFScale sdf_scale = SDF_SCALE_50_PERCENT; diff --git a/scene/main/window.cpp b/scene/main/window.cpp index 2771a9f69e..79d41abe87 100644 --- a/scene/main/window.cpp +++ b/scene/main/window.cpp @@ -551,9 +551,11 @@ void Window::_make_window() { DisplayServer::get_singleton()->window_set_transient(window_id, transient_parent->window_id); } - for (const Window *E : transient_children) { - if (E->window_id != DisplayServer::INVALID_WINDOW_ID) { - DisplayServer::get_singleton()->window_set_transient(E->window_id, transient_parent->window_id); + if (transient_parent) { + for (const Window *E : transient_children) { + if (E->window_id != DisplayServer::INVALID_WINDOW_ID) { + DisplayServer::get_singleton()->window_set_transient(E->window_id, transient_parent->window_id); + } } } diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp index feff4a6dc9..e4e06a9a79 100644 --- a/scene/register_scene_types.cpp +++ b/scene/register_scene_types.cpp @@ -1142,6 +1142,10 @@ void register_scene_types() { GLOBAL_DEF_BASIC(vformat("%s/layer_%d", PNAME("layer_names/3d_navigation"), i + 1), ""); } + for (int i = 0; i < 32; i++) { + GLOBAL_DEF_BASIC(vformat("%s/layer_%d", PNAME("layer_names/avoidance"), i + 1), ""); + } + if (RenderingServer::get_singleton()) { ColorPicker::init_shaders(); // RenderingServer needs to exist for this to succeed. } diff --git a/scene/resources/canvas_item_material.cpp b/scene/resources/canvas_item_material.cpp index 0d5d735ed0..31c8e68ea5 100644 --- a/scene/resources/canvas_item_material.cpp +++ b/scene/resources/canvas_item_material.cpp @@ -161,7 +161,7 @@ void CanvasItemMaterial::flush_changes() { void CanvasItemMaterial::_queue_shader_change() { MutexLock lock(material_mutex); - if (is_initialized && !element.in_list()) { + if (_is_initialized() && !element.in_list()) { dirty_materials->add(&element); } } @@ -287,8 +287,8 @@ CanvasItemMaterial::CanvasItemMaterial() : set_particles_anim_loop(false); current_key.invalid_key = 1; - is_initialized = true; - _queue_shader_change(); + + _mark_initialized(callable_mp(this, &CanvasItemMaterial::_queue_shader_change)); } CanvasItemMaterial::~CanvasItemMaterial() { diff --git a/scene/resources/canvas_item_material.h b/scene/resources/canvas_item_material.h index cf5df76147..7dddd74a31 100644 --- a/scene/resources/canvas_item_material.h +++ b/scene/resources/canvas_item_material.h @@ -105,7 +105,6 @@ private: _FORCE_INLINE_ void _queue_shader_change(); _FORCE_INLINE_ bool _is_shader_dirty() const; - bool is_initialized = false; BlendMode blend_mode = BLEND_MODE_MIX; LightMode light_mode = LIGHT_MODE_NORMAL; bool particles_animation = false; diff --git a/scene/resources/material.cpp b/scene/resources/material.cpp index d0aa224773..d35c49b266 100644 --- a/scene/resources/material.cpp +++ b/scene/resources/material.cpp @@ -82,6 +82,23 @@ void Material::_validate_property(PropertyInfo &p_property) const { } } +void Material::_mark_initialized(const Callable &p_queue_shader_change_callable) { + // If this is happening as part of resource loading, it is not safe to queue the update + // as an addition to the dirty list, unless the load is happening on the main thread. + if (ResourceLoader::is_within_load() && Thread::get_caller_id() != Thread::get_main_id()) { + DEV_ASSERT(init_state != INIT_STATE_READY); + if (init_state == INIT_STATE_UNINITIALIZED) { // Prevent queueing twice. + // Queue an individual update of this material (the ResourceLoader knows how to handle deferred calls safely). + p_queue_shader_change_callable.call_deferred(); + init_state = INIT_STATE_INITIALIZING; + } + } else { + // Straightforward conditions. + init_state = INIT_STATE_READY; + p_queue_shader_change_callable.callv(Array()); + } +} + void Material::inspect_native_shader_code() { SceneTree *st = Object::cast_to<SceneTree>(OS::get_singleton()->get_main_loop()); RID shader = get_shader_rid(); @@ -1485,7 +1502,7 @@ void BaseMaterial3D::flush_changes() { void BaseMaterial3D::_queue_shader_change() { MutexLock lock(material_mutex); - if (is_initialized && !element.in_list()) { + if (_is_initialized() && !element.in_list()) { dirty_materials->add(&element); } } @@ -3028,8 +3045,7 @@ BaseMaterial3D::BaseMaterial3D(bool p_orm) : flags[FLAG_ALBEDO_TEXTURE_MSDF] = false; flags[FLAG_USE_TEXTURE_REPEAT] = true; - is_initialized = true; - _queue_shader_change(); + _mark_initialized(callable_mp(this, &BaseMaterial3D::_queue_shader_change)); } BaseMaterial3D::~BaseMaterial3D() { diff --git a/scene/resources/material.h b/scene/resources/material.h index 1fa9a24bc5..b70522dda1 100644 --- a/scene/resources/material.h +++ b/scene/resources/material.h @@ -46,6 +46,12 @@ class Material : public Resource { Ref<Material> next_pass; int render_priority; + enum { + INIT_STATE_UNINITIALIZED, + INIT_STATE_INITIALIZING, + INIT_STATE_READY, + } init_state = INIT_STATE_UNINITIALIZED; + void inspect_native_shader_code(); protected: @@ -56,6 +62,9 @@ protected: void _validate_property(PropertyInfo &p_property) const; + void _mark_initialized(const Callable &p_queue_shader_change_callable); + bool _is_initialized() { return init_state == INIT_STATE_READY; } + GDVIRTUAL0RC(RID, _get_shader_rid) GDVIRTUAL0RC(Shader::Mode, _get_shader_mode) GDVIRTUAL0RC(bool, _can_do_next_pass) @@ -452,7 +461,6 @@ private: _FORCE_INLINE_ void _queue_shader_change(); _FORCE_INLINE_ bool _is_shader_dirty() const; - bool is_initialized = false; bool orm; Color albedo; diff --git a/scene/resources/particle_process_material.cpp b/scene/resources/particle_process_material.cpp index 41edbcb726..cb94d5af36 100644 --- a/scene/resources/particle_process_material.cpp +++ b/scene/resources/particle_process_material.cpp @@ -915,7 +915,7 @@ void ParticleProcessMaterial::flush_changes() { void ParticleProcessMaterial::_queue_shader_change() { MutexLock lock(material_mutex); - if (is_initialized && !element.in_list()) { + if (_is_initialized() && !element.in_list()) { dirty_materials->add(&element); } } @@ -1889,8 +1889,7 @@ ParticleProcessMaterial::ParticleProcessMaterial() : current_key.invalid_key = 1; - is_initialized = true; - _queue_shader_change(); + _mark_initialized(callable_mp(this, &ParticleProcessMaterial::_queue_shader_change)); } ParticleProcessMaterial::~ParticleProcessMaterial() { diff --git a/scene/resources/particle_process_material.h b/scene/resources/particle_process_material.h index c32a143cdc..533bd9636f 100644 --- a/scene/resources/particle_process_material.h +++ b/scene/resources/particle_process_material.h @@ -261,7 +261,6 @@ private: _FORCE_INLINE_ void _queue_shader_change(); _FORCE_INLINE_ bool _is_shader_dirty() const; - bool is_initialized = false; Vector3 direction; float spread = 0.0f; float flatness = 0.0f; diff --git a/scene/resources/resource_format_text.cpp b/scene/resources/resource_format_text.cpp index c30e009356..4807af3c27 100644 --- a/scene/resources/resource_format_text.cpp +++ b/scene/resources/resource_format_text.cpp @@ -150,32 +150,31 @@ Error ResourceLoaderText::_parse_ext_resource(VariantParser::Stream *p_stream, R String path = ext_resources[id].path; String type = ext_resources[id].type; + Ref<ResourceLoader::LoadToken> &load_token = ext_resources[id].load_token; - if (ext_resources[id].cache.is_valid()) { - r_res = ext_resources[id].cache; - } else if (use_sub_threads) { - Ref<Resource> res = ResourceLoader::load_threaded_get(path); + if (load_token.is_valid()) { // If not valid, it's OK since then we know this load accepts broken dependencies. + Ref<Resource> res = ResourceLoader::_load_complete(*load_token.ptr(), &err); if (res.is_null()) { - if (ResourceLoader::get_abort_on_missing_resources()) { - error = ERR_FILE_MISSING_DEPENDENCIES; - error_text = "[ext_resource] referenced nonexistent resource at: " + path; - _printerr(); - err = error; - } else { - ResourceLoader::notify_dependency_error(local_path, path, type); + if (!ResourceLoader::is_cleaning_tasks()) { + if (ResourceLoader::get_abort_on_missing_resources()) { + error = ERR_FILE_MISSING_DEPENDENCIES; + error_text = "[ext_resource] referenced non-existent resource at: " + path; + _printerr(); + err = error; + } else { + ResourceLoader::notify_dependency_error(local_path, path, type); + } } } else { - ext_resources[id].cache = res; +#ifdef TOOLS_ENABLED + //remember ID for saving + res->set_id_for_path(path, id); +#endif r_res = res; } } else { - error = ERR_FILE_MISSING_DEPENDENCIES; - error_text = "[ext_resource] referenced non-loaded resource at: " + path; - _printerr(); - err = error; + r_res = Ref<Resource>(); } - } else { - r_res = Ref<Resource>(); } VariantParser::get_token(p_stream, token, line, r_err_str); @@ -462,48 +461,20 @@ Error ResourceLoaderText::load() { path = remaps[path]; } - ExtResource er; - er.path = path; - er.type = type; - - if (use_sub_threads) { - Error err = ResourceLoader::load_threaded_request(path, type, use_sub_threads, ResourceFormatLoader::CACHE_MODE_REUSE, local_path); - - if (err != OK) { - if (ResourceLoader::get_abort_on_missing_resources()) { - error = ERR_FILE_CORRUPT; - error_text = "[ext_resource] referenced broken resource at: " + path; - _printerr(); - return error; - } else { - ResourceLoader::notify_dependency_error(local_path, path, type); - } - } - - } else { - Ref<Resource> res = ResourceLoader::load(path, type); - - if (res.is_null()) { - if (ResourceLoader::get_abort_on_missing_resources()) { - error = ERR_FILE_CORRUPT; - error_text = "[ext_resource] referenced nonexistent resource at: " + path; - _printerr(); - return error; - } else { - ResourceLoader::notify_dependency_error(local_path, path, type); - } + ext_resources[id].path = path; + ext_resources[id].type = type; + ext_resources[id].load_token = ResourceLoader::_load_start(path, type, use_sub_threads ? ResourceLoader::LOAD_THREAD_DISTRIBUTE : ResourceLoader::LOAD_THREAD_FROM_CURRENT, ResourceFormatLoader::CACHE_MODE_REUSE); + if (!ext_resources[id].load_token.is_valid()) { + if (ResourceLoader::get_abort_on_missing_resources()) { + error = ERR_FILE_CORRUPT; + error_text = "[ext_resource] referenced non-existent resource at: " + path; + _printerr(); + return error; } else { -#ifdef TOOLS_ENABLED - //remember ID for saving - res->set_id_for_path(local_path, id); -#endif + ResourceLoader::notify_dependency_error(local_path, path, type); } - - er.cache = res; } - ext_resources[id] = er; - error = VariantParser::parse_tag(&stream, lines, error_text, next_tag, &rp); if (error) { diff --git a/scene/resources/resource_format_text.h b/scene/resources/resource_format_text.h index 25001d8023..c35a594f72 100644 --- a/scene/resources/resource_format_text.h +++ b/scene/resources/resource_format_text.h @@ -48,7 +48,7 @@ class ResourceLoaderText { VariantParser::StreamFile stream; struct ExtResource { - Ref<Resource> cache; + Ref<ResourceLoader::LoadToken> load_token; String path; String type; }; diff --git a/servers/navigation_server_2d.cpp b/servers/navigation_server_2d.cpp index 6de8459e3b..706718be19 100644 --- a/servers/navigation_server_2d.cpp +++ b/servers/navigation_server_2d.cpp @@ -115,6 +115,15 @@ static Vector2 v3_to_v2(const Vector3 &d) { return Vector2(d.x, d.z); } +static Vector<Vector3> vector_v2_to_v3(const Vector<Vector2> &d) { + Vector<Vector3> nd; + nd.resize(d.size()); + for (int i(0); i < nd.size(); i++) { + nd.write[i] = v2_to_v3(d[i]); + } + return nd; +} + static Vector<Vector2> vector_v3_to_v2(const Vector<Vector3> &d) { Vector<Vector2> nd; nd.resize(d.size()); @@ -161,6 +170,22 @@ bool NavigationServer2D::get_debug_enabled() const { } #ifdef DEBUG_ENABLED +void NavigationServer2D::set_debug_navigation_enabled(bool p_enabled) { + NavigationServer3D::get_singleton()->set_debug_navigation_enabled(p_enabled); +} + +bool NavigationServer2D::get_debug_navigation_enabled() const { + return NavigationServer3D::get_singleton()->get_debug_navigation_enabled(); +} + +void NavigationServer2D::set_debug_avoidance_enabled(bool p_enabled) { + NavigationServer3D::get_singleton()->set_debug_avoidance_enabled(p_enabled); +} + +bool NavigationServer2D::get_debug_avoidance_enabled() const { + return NavigationServer3D::get_singleton()->get_debug_avoidance_enabled(); +} + void NavigationServer2D::set_debug_navigation_edge_connection_color(const Color &p_color) { NavigationServer3D::get_singleton()->set_debug_navigation_edge_connection_color(p_color); } @@ -264,6 +289,78 @@ void NavigationServer2D::set_debug_navigation_agent_path_point_size(real_t p_poi real_t NavigationServer2D::get_debug_navigation_agent_path_point_size() const { return NavigationServer3D::get_singleton()->get_debug_navigation_agent_path_point_size(); } + +void NavigationServer2D::set_debug_navigation_avoidance_enable_agents_radius(const bool p_value) { + NavigationServer3D::get_singleton()->set_debug_navigation_avoidance_enable_agents_radius(p_value); +} + +bool NavigationServer2D::get_debug_navigation_avoidance_enable_agents_radius() const { + return NavigationServer3D::get_singleton()->get_debug_navigation_avoidance_enable_agents_radius(); +} + +void NavigationServer2D::set_debug_navigation_avoidance_enable_obstacles_radius(const bool p_value) { + NavigationServer3D::get_singleton()->set_debug_navigation_avoidance_enable_obstacles_radius(p_value); +} + +bool NavigationServer2D::get_debug_navigation_avoidance_enable_obstacles_radius() const { + return NavigationServer3D::get_singleton()->get_debug_navigation_avoidance_enable_obstacles_radius(); +} + +void NavigationServer2D::set_debug_navigation_avoidance_agents_radius_color(const Color &p_color) { + NavigationServer3D::get_singleton()->set_debug_navigation_avoidance_agents_radius_color(p_color); +} + +Color NavigationServer2D::get_debug_navigation_avoidance_agents_radius_color() const { + return NavigationServer3D::get_singleton()->get_debug_navigation_avoidance_agents_radius_color(); +} + +void NavigationServer2D::set_debug_navigation_avoidance_obstacles_radius_color(const Color &p_color) { + NavigationServer3D::get_singleton()->set_debug_navigation_avoidance_obstacles_radius_color(p_color); +} + +Color NavigationServer2D::get_debug_navigation_avoidance_obstacles_radius_color() const { + return NavigationServer3D::get_singleton()->get_debug_navigation_avoidance_obstacles_radius_color(); +} + +void NavigationServer2D::set_debug_navigation_avoidance_static_obstacle_pushin_face_color(const Color &p_color) { + NavigationServer3D::get_singleton()->set_debug_navigation_avoidance_static_obstacle_pushin_face_color(p_color); +} + +Color NavigationServer2D::get_debug_navigation_avoidance_static_obstacle_pushin_face_color() const { + return NavigationServer3D::get_singleton()->get_debug_navigation_avoidance_static_obstacle_pushin_face_color(); +} + +void NavigationServer2D::set_debug_navigation_avoidance_static_obstacle_pushout_face_color(const Color &p_color) { + NavigationServer3D::get_singleton()->set_debug_navigation_avoidance_static_obstacle_pushout_face_color(p_color); +} + +Color NavigationServer2D::get_debug_navigation_avoidance_static_obstacle_pushout_face_color() const { + return NavigationServer3D::get_singleton()->get_debug_navigation_avoidance_static_obstacle_pushout_face_color(); +} + +void NavigationServer2D::set_debug_navigation_avoidance_static_obstacle_pushin_edge_color(const Color &p_color) { + NavigationServer3D::get_singleton()->set_debug_navigation_avoidance_static_obstacle_pushin_edge_color(p_color); +} + +Color NavigationServer2D::get_debug_navigation_avoidance_static_obstacle_pushin_edge_color() const { + return NavigationServer3D::get_singleton()->get_debug_navigation_avoidance_static_obstacle_pushin_edge_color(); +} + +void NavigationServer2D::set_debug_navigation_avoidance_static_obstacle_pushout_edge_color(const Color &p_color) { + NavigationServer3D::get_singleton()->set_debug_navigation_avoidance_static_obstacle_pushout_edge_color(p_color); +} + +Color NavigationServer2D::get_debug_navigation_avoidance_static_obstacle_pushout_edge_color() const { + return NavigationServer3D::get_singleton()->get_debug_navigation_avoidance_static_obstacle_pushout_edge_color(); +} + +void NavigationServer2D::set_debug_navigation_avoidance_enable_obstacles_static(const bool p_value) { + NavigationServer3D::get_singleton()->set_debug_navigation_avoidance_enable_obstacles_static(p_value); +} + +bool NavigationServer2D::get_debug_navigation_avoidance_enable_obstacles_static() const { + return NavigationServer3D::get_singleton()->get_debug_navigation_avoidance_enable_obstacles_static(); +} #endif // DEBUG_ENABLED void NavigationServer2D::_bind_methods() { @@ -285,6 +382,7 @@ void NavigationServer2D::_bind_methods() { ClassDB::bind_method(D_METHOD("map_get_links", "map"), &NavigationServer2D::map_get_links); ClassDB::bind_method(D_METHOD("map_get_regions", "map"), &NavigationServer2D::map_get_regions); ClassDB::bind_method(D_METHOD("map_get_agents", "map"), &NavigationServer2D::map_get_agents); + ClassDB::bind_method(D_METHOD("map_get_obstacles", "map"), &NavigationServer2D::map_get_obstacles); ClassDB::bind_method(D_METHOD("map_force_update", "map"), &NavigationServer2D::map_force_update); @@ -327,18 +425,31 @@ void NavigationServer2D::_bind_methods() { ClassDB::bind_method(D_METHOD("link_get_owner_id", "link"), &NavigationServer2D::link_get_owner_id); ClassDB::bind_method(D_METHOD("agent_create"), &NavigationServer2D::agent_create); + ClassDB::bind_method(D_METHOD("agent_set_avoidance_enabled", "agent", "enabled"), &NavigationServer2D::agent_set_avoidance_enabled); + ClassDB::bind_method(D_METHOD("agent_get_avoidance_enabled", "agent"), &NavigationServer2D::agent_get_avoidance_enabled); ClassDB::bind_method(D_METHOD("agent_set_map", "agent", "map"), &NavigationServer2D::agent_set_map); ClassDB::bind_method(D_METHOD("agent_get_map", "agent"), &NavigationServer2D::agent_get_map); ClassDB::bind_method(D_METHOD("agent_set_neighbor_distance", "agent", "distance"), &NavigationServer2D::agent_set_neighbor_distance); ClassDB::bind_method(D_METHOD("agent_set_max_neighbors", "agent", "count"), &NavigationServer2D::agent_set_max_neighbors); - ClassDB::bind_method(D_METHOD("agent_set_time_horizon", "agent", "time"), &NavigationServer2D::agent_set_time_horizon); + ClassDB::bind_method(D_METHOD("agent_set_time_horizon_agents", "agent", "time_horizon"), &NavigationServer2D::agent_set_time_horizon_agents); + ClassDB::bind_method(D_METHOD("agent_set_time_horizon_obstacles", "agent", "time_horizon"), &NavigationServer2D::agent_set_time_horizon_obstacles); ClassDB::bind_method(D_METHOD("agent_set_radius", "agent", "radius"), &NavigationServer2D::agent_set_radius); ClassDB::bind_method(D_METHOD("agent_set_max_speed", "agent", "max_speed"), &NavigationServer2D::agent_set_max_speed); + ClassDB::bind_method(D_METHOD("agent_set_velocity_forced", "agent", "velocity"), &NavigationServer2D::agent_set_velocity_forced); ClassDB::bind_method(D_METHOD("agent_set_velocity", "agent", "velocity"), &NavigationServer2D::agent_set_velocity); - ClassDB::bind_method(D_METHOD("agent_set_target_velocity", "agent", "target_velocity"), &NavigationServer2D::agent_set_target_velocity); ClassDB::bind_method(D_METHOD("agent_set_position", "agent", "position"), &NavigationServer2D::agent_set_position); ClassDB::bind_method(D_METHOD("agent_is_map_changed", "agent"), &NavigationServer2D::agent_is_map_changed); - ClassDB::bind_method(D_METHOD("agent_set_callback", "agent", "callback"), &NavigationServer2D::agent_set_callback); + ClassDB::bind_method(D_METHOD("agent_set_avoidance_callback", "agent", "callback"), &NavigationServer2D::agent_set_avoidance_callback); + ClassDB::bind_method(D_METHOD("agent_set_avoidance_layers", "agent", "layers"), &NavigationServer2D::agent_set_avoidance_layers); + ClassDB::bind_method(D_METHOD("agent_set_avoidance_mask", "agent", "mask"), &NavigationServer2D::agent_set_avoidance_mask); + ClassDB::bind_method(D_METHOD("agent_set_avoidance_priority", "agent", "priority"), &NavigationServer2D::agent_set_avoidance_priority); + + ClassDB::bind_method(D_METHOD("obstacle_create"), &NavigationServer2D::obstacle_create); + ClassDB::bind_method(D_METHOD("obstacle_set_map", "obstacle", "map"), &NavigationServer2D::obstacle_set_map); + ClassDB::bind_method(D_METHOD("obstacle_get_map", "obstacle"), &NavigationServer2D::obstacle_get_map); + ClassDB::bind_method(D_METHOD("obstacle_set_position", "obstacle", "position"), &NavigationServer2D::obstacle_set_position); + ClassDB::bind_method(D_METHOD("obstacle_set_vertices", "obstacle", "vertices"), &NavigationServer2D::obstacle_set_vertices); + ClassDB::bind_method(D_METHOD("obstacle_set_avoidance_layers", "obstacle", "layers"), &NavigationServer2D::obstacle_set_avoidance_layers); ClassDB::bind_method(D_METHOD("free_rid", "rid"), &NavigationServer2D::free); @@ -378,6 +489,8 @@ TypedArray<RID> FORWARD_1_C(map_get_regions, RID, p_map, rid_to_rid); TypedArray<RID> FORWARD_1_C(map_get_agents, RID, p_map, rid_to_rid); +TypedArray<RID> FORWARD_1_C(map_get_obstacles, RID, p_map, rid_to_rid); + RID FORWARD_1_C(region_get_map, RID, p_region, rid_to_rid); RID FORWARD_1_C(agent_get_map, RID, p_agent, rid_to_rid); @@ -450,24 +563,43 @@ ObjectID FORWARD_1_C(link_get_owner_id, RID, p_link, rid_to_rid); RID NavigationServer2D::agent_create() { RID agent = NavigationServer3D::get_singleton()->agent_create(); - NavigationServer3D::get_singleton()->agent_set_ignore_y(agent, true); return agent; } +void FORWARD_2(agent_set_avoidance_enabled, RID, p_agent, bool, p_enabled, rid_to_rid, bool_to_bool); +bool FORWARD_1_C(agent_get_avoidance_enabled, RID, p_agent, rid_to_rid); void FORWARD_2(agent_set_map, RID, p_agent, RID, p_map, rid_to_rid, rid_to_rid); void FORWARD_2(agent_set_neighbor_distance, RID, p_agent, real_t, p_dist, rid_to_rid, real_to_real); void FORWARD_2(agent_set_max_neighbors, RID, p_agent, int, p_count, rid_to_rid, int_to_int); -void FORWARD_2(agent_set_time_horizon, RID, p_agent, real_t, p_time, rid_to_rid, real_to_real); +void FORWARD_2(agent_set_time_horizon_agents, RID, p_agent, real_t, p_time_horizon, rid_to_rid, real_to_real); +void FORWARD_2(agent_set_time_horizon_obstacles, RID, p_agent, real_t, p_time_horizon, rid_to_rid, real_to_real); void FORWARD_2(agent_set_radius, RID, p_agent, real_t, p_radius, rid_to_rid, real_to_real); void FORWARD_2(agent_set_max_speed, RID, p_agent, real_t, p_max_speed, rid_to_rid, real_to_real); +void FORWARD_2(agent_set_velocity_forced, RID, p_agent, Vector2, p_velocity, rid_to_rid, v2_to_v3); void FORWARD_2(agent_set_velocity, RID, p_agent, Vector2, p_velocity, rid_to_rid, v2_to_v3); -void FORWARD_2(agent_set_target_velocity, RID, p_agent, Vector2, p_velocity, rid_to_rid, v2_to_v3); void FORWARD_2(agent_set_position, RID, p_agent, Vector2, p_position, rid_to_rid, v2_to_v3); -void FORWARD_2(agent_set_ignore_y, RID, p_agent, bool, p_ignore, rid_to_rid, bool_to_bool); + bool FORWARD_1_C(agent_is_map_changed, RID, p_agent, rid_to_rid); -void FORWARD_2(agent_set_callback, RID, p_agent, Callable, p_callback, rid_to_rid, callable_to_callable); void FORWARD_1(free, RID, p_object, rid_to_rid); +void FORWARD_2(agent_set_avoidance_callback, RID, p_agent, Callable, p_callback, rid_to_rid, callable_to_callable); + +void FORWARD_2(agent_set_avoidance_layers, RID, p_agent, uint32_t, p_layers, rid_to_rid, uint32_to_uint32); +void FORWARD_2(agent_set_avoidance_mask, RID, p_agent, uint32_t, p_mask, rid_to_rid, uint32_to_uint32); +void FORWARD_2(agent_set_avoidance_priority, RID, p_agent, real_t, p_priority, rid_to_rid, real_to_real); + +RID NavigationServer2D::obstacle_create() { + RID obstacle = NavigationServer3D::get_singleton()->obstacle_create(); + return obstacle; +} +void FORWARD_2(obstacle_set_map, RID, p_obstacle, RID, p_map, rid_to_rid, rid_to_rid); +RID FORWARD_1_C(obstacle_get_map, RID, p_obstacle, rid_to_rid); +void FORWARD_2(obstacle_set_position, RID, p_obstacle, Vector2, p_position, rid_to_rid, v2_to_v3); +void FORWARD_2(obstacle_set_avoidance_layers, RID, p_obstacle, uint32_t, p_layers, rid_to_rid, uint32_to_uint32); + +void NavigationServer2D::obstacle_set_vertices(RID p_obstacle, const Vector<Vector2> &p_vertices) { + NavigationServer3D::get_singleton()->obstacle_set_vertices(p_obstacle, vector_v2_to_v3(p_vertices)); +} void NavigationServer2D::query_path(const Ref<NavigationPathQueryParameters2D> &p_query_parameters, Ref<NavigationPathQueryResult2D> p_query_result) const { ERR_FAIL_COND(!p_query_parameters.is_valid()); diff --git a/servers/navigation_server_2d.h b/servers/navigation_server_2d.h index 43ef742bdb..d6e84d73b6 100644 --- a/servers/navigation_server_2d.h +++ b/servers/navigation_server_2d.h @@ -91,6 +91,7 @@ public: virtual TypedArray<RID> map_get_links(RID p_map) const; virtual TypedArray<RID> map_get_regions(RID p_map) const; virtual TypedArray<RID> map_get_agents(RID p_map) const; + virtual TypedArray<RID> map_get_obstacles(RID p_map) const; virtual void map_force_update(RID p_map); @@ -171,6 +172,8 @@ public: /// Put the agent in the map. virtual void agent_set_map(RID p_agent, RID p_map); virtual RID agent_get_map(RID p_agent) const; + virtual void agent_set_avoidance_enabled(RID p_agent, bool p_enabled); + virtual bool agent_get_avoidance_enabled(RID p_agent) const; /// The maximum distance (center point to /// center point) to other agents this agent @@ -197,7 +200,9 @@ public: /// other agents, but the less freedom this /// agent has in choosing its velocities. /// Must be positive. - virtual void agent_set_time_horizon(RID p_agent, real_t p_time); + + virtual void agent_set_time_horizon_agents(RID p_agent, real_t p_time_horizon); + virtual void agent_set_time_horizon_obstacles(RID p_agent, real_t p_time_horizon); /// The radius of this agent. /// Must be non-negative. @@ -207,23 +212,33 @@ public: /// Must be non-negative. virtual void agent_set_max_speed(RID p_agent, real_t p_max_speed); - /// Current velocity of the agent - virtual void agent_set_velocity(RID p_agent, Vector2 p_velocity); + /// forces and agent velocity change in the avoidance simulation, adds simulation instability if done recklessly + virtual void agent_set_velocity_forced(RID p_agent, Vector2 p_velocity); - /// The new target velocity. - virtual void agent_set_target_velocity(RID p_agent, Vector2 p_velocity); + /// The wanted velocity for the agent as a "suggestion" to the avoidance simulation. + /// The simulation will try to fulfil this velocity wish if possible but may change the velocity depending on other agent's and obstacles'. + virtual void agent_set_velocity(RID p_agent, Vector2 p_velocity); /// Position of the agent in world space. virtual void agent_set_position(RID p_agent, Vector2 p_position); - /// Agent ignore the Y axis and avoid collisions by moving only on the horizontal plane - virtual void agent_set_ignore_y(RID p_agent, bool p_ignore); - /// Returns true if the map got changed the previous frame. virtual bool agent_is_map_changed(RID p_agent) const; /// Callback called at the end of the RVO process - virtual void agent_set_callback(RID p_agent, Callable p_callback); + virtual void agent_set_avoidance_callback(RID p_agent, Callable p_callback); + + virtual void agent_set_avoidance_layers(RID p_agent, uint32_t p_layers); + virtual void agent_set_avoidance_mask(RID p_agent, uint32_t p_mask); + virtual void agent_set_avoidance_priority(RID p_agent, real_t p_priority); + + /// Creates the obstacle. + virtual RID obstacle_create(); + virtual void obstacle_set_map(RID p_obstacle, RID p_map); + virtual RID obstacle_get_map(RID p_obstacle) const; + virtual void obstacle_set_position(RID p_obstacle, Vector2 p_position); + virtual void obstacle_set_vertices(RID p_obstacle, const Vector<Vector2> &p_vertices); + virtual void obstacle_set_avoidance_layers(RID p_obstacle, uint32_t p_layers); /// Returns a customized navigation path using a query parameters object virtual void query_path(const Ref<NavigationPathQueryParameters2D> &p_query_parameters, Ref<NavigationPathQueryResult2D> p_query_result) const; @@ -238,6 +253,12 @@ public: bool get_debug_enabled() const; #ifdef DEBUG_ENABLED + void set_debug_navigation_enabled(bool p_enabled); + bool get_debug_navigation_enabled() const; + + void set_debug_avoidance_enabled(bool p_enabled); + bool get_debug_avoidance_enabled() const; + void set_debug_navigation_edge_connection_color(const Color &p_color); Color get_debug_navigation_edge_connection_color() const; @@ -276,6 +297,33 @@ public: void set_debug_navigation_agent_path_point_size(real_t p_point_size); real_t get_debug_navigation_agent_path_point_size() const; + + void set_debug_navigation_avoidance_enable_agents_radius(const bool p_value); + bool get_debug_navigation_avoidance_enable_agents_radius() const; + + void set_debug_navigation_avoidance_enable_obstacles_radius(const bool p_value); + bool get_debug_navigation_avoidance_enable_obstacles_radius() const; + + void set_debug_navigation_avoidance_agents_radius_color(const Color &p_color); + Color get_debug_navigation_avoidance_agents_radius_color() const; + + void set_debug_navigation_avoidance_obstacles_radius_color(const Color &p_color); + Color get_debug_navigation_avoidance_obstacles_radius_color() const; + + void set_debug_navigation_avoidance_static_obstacle_pushin_face_color(const Color &p_color); + Color get_debug_navigation_avoidance_static_obstacle_pushin_face_color() const; + + void set_debug_navigation_avoidance_static_obstacle_pushout_face_color(const Color &p_color); + Color get_debug_navigation_avoidance_static_obstacle_pushout_face_color() const; + + void set_debug_navigation_avoidance_static_obstacle_pushin_edge_color(const Color &p_color); + Color get_debug_navigation_avoidance_static_obstacle_pushin_edge_color() const; + + void set_debug_navigation_avoidance_static_obstacle_pushout_edge_color(const Color &p_color); + Color get_debug_navigation_avoidance_static_obstacle_pushout_edge_color() const; + + void set_debug_navigation_avoidance_enable_obstacles_static(const bool p_value); + bool get_debug_navigation_avoidance_enable_obstacles_static() const; #endif // DEBUG_ENABLED #ifdef DEBUG_ENABLED diff --git a/servers/navigation_server_3d.cpp b/servers/navigation_server_3d.cpp index 10f5e71c91..e2da97b18a 100644 --- a/servers/navigation_server_3d.cpp +++ b/servers/navigation_server_3d.cpp @@ -56,6 +56,7 @@ void NavigationServer3D::_bind_methods() { ClassDB::bind_method(D_METHOD("map_get_links", "map"), &NavigationServer3D::map_get_links); ClassDB::bind_method(D_METHOD("map_get_regions", "map"), &NavigationServer3D::map_get_regions); ClassDB::bind_method(D_METHOD("map_get_agents", "map"), &NavigationServer3D::map_get_agents); + ClassDB::bind_method(D_METHOD("map_get_obstacles", "map"), &NavigationServer3D::map_get_obstacles); ClassDB::bind_method(D_METHOD("map_force_update", "map"), &NavigationServer3D::map_force_update); @@ -99,18 +100,36 @@ void NavigationServer3D::_bind_methods() { ClassDB::bind_method(D_METHOD("link_get_owner_id", "link"), &NavigationServer3D::link_get_owner_id); ClassDB::bind_method(D_METHOD("agent_create"), &NavigationServer3D::agent_create); + ClassDB::bind_method(D_METHOD("agent_set_avoidance_enabled", "agent", "enabled"), &NavigationServer3D::agent_set_avoidance_enabled); + ClassDB::bind_method(D_METHOD("agent_get_avoidance_enabled", "agent"), &NavigationServer3D::agent_get_avoidance_enabled); + ClassDB::bind_method(D_METHOD("agent_set_use_3d_avoidance", "agent", "enabled"), &NavigationServer3D::agent_set_use_3d_avoidance); + ClassDB::bind_method(D_METHOD("agent_get_use_3d_avoidance", "agent"), &NavigationServer3D::agent_get_use_3d_avoidance); + ClassDB::bind_method(D_METHOD("agent_set_map", "agent", "map"), &NavigationServer3D::agent_set_map); ClassDB::bind_method(D_METHOD("agent_get_map", "agent"), &NavigationServer3D::agent_get_map); ClassDB::bind_method(D_METHOD("agent_set_neighbor_distance", "agent", "distance"), &NavigationServer3D::agent_set_neighbor_distance); ClassDB::bind_method(D_METHOD("agent_set_max_neighbors", "agent", "count"), &NavigationServer3D::agent_set_max_neighbors); - ClassDB::bind_method(D_METHOD("agent_set_time_horizon", "agent", "time"), &NavigationServer3D::agent_set_time_horizon); + ClassDB::bind_method(D_METHOD("agent_set_time_horizon_agents", "agent", "time_horizon"), &NavigationServer3D::agent_set_time_horizon_agents); + ClassDB::bind_method(D_METHOD("agent_set_time_horizon_obstacles", "agent", "time_horizon"), &NavigationServer3D::agent_set_time_horizon_obstacles); ClassDB::bind_method(D_METHOD("agent_set_radius", "agent", "radius"), &NavigationServer3D::agent_set_radius); + ClassDB::bind_method(D_METHOD("agent_set_height", "agent", "height"), &NavigationServer3D::agent_set_height); ClassDB::bind_method(D_METHOD("agent_set_max_speed", "agent", "max_speed"), &NavigationServer3D::agent_set_max_speed); + ClassDB::bind_method(D_METHOD("agent_set_velocity_forced", "agent", "velocity"), &NavigationServer3D::agent_set_velocity_forced); ClassDB::bind_method(D_METHOD("agent_set_velocity", "agent", "velocity"), &NavigationServer3D::agent_set_velocity); - ClassDB::bind_method(D_METHOD("agent_set_target_velocity", "agent", "target_velocity"), &NavigationServer3D::agent_set_target_velocity); ClassDB::bind_method(D_METHOD("agent_set_position", "agent", "position"), &NavigationServer3D::agent_set_position); ClassDB::bind_method(D_METHOD("agent_is_map_changed", "agent"), &NavigationServer3D::agent_is_map_changed); - ClassDB::bind_method(D_METHOD("agent_set_callback", "agent", "callback"), &NavigationServer3D::agent_set_callback); + ClassDB::bind_method(D_METHOD("agent_set_avoidance_callback", "agent", "callback"), &NavigationServer3D::agent_set_avoidance_callback); + ClassDB::bind_method(D_METHOD("agent_set_avoidance_layers", "agent", "layers"), &NavigationServer3D::agent_set_avoidance_layers); + ClassDB::bind_method(D_METHOD("agent_set_avoidance_mask", "agent", "mask"), &NavigationServer3D::agent_set_avoidance_mask); + ClassDB::bind_method(D_METHOD("agent_set_avoidance_priority", "agent", "priority"), &NavigationServer3D::agent_set_avoidance_priority); + + ClassDB::bind_method(D_METHOD("obstacle_create"), &NavigationServer3D::obstacle_create); + ClassDB::bind_method(D_METHOD("obstacle_set_map", "obstacle", "map"), &NavigationServer3D::obstacle_set_map); + ClassDB::bind_method(D_METHOD("obstacle_get_map", "obstacle"), &NavigationServer3D::obstacle_get_map); + ClassDB::bind_method(D_METHOD("obstacle_set_height", "obstacle", "height"), &NavigationServer3D::obstacle_set_height); + ClassDB::bind_method(D_METHOD("obstacle_set_position", "obstacle", "position"), &NavigationServer3D::obstacle_set_position); + ClassDB::bind_method(D_METHOD("obstacle_set_vertices", "obstacle", "vertices"), &NavigationServer3D::obstacle_set_vertices); + ClassDB::bind_method(D_METHOD("obstacle_set_avoidance_layers", "obstacle", "layers"), &NavigationServer3D::obstacle_set_avoidance_layers); ClassDB::bind_method(D_METHOD("free_rid", "rid"), &NavigationServer3D::free); @@ -122,6 +141,7 @@ void NavigationServer3D::_bind_methods() { ADD_SIGNAL(MethodInfo("map_changed", PropertyInfo(Variant::RID, "map"))); ADD_SIGNAL(MethodInfo("navigation_debug_changed")); + ADD_SIGNAL(MethodInfo("avoidance_debug_changed")); ClassDB::bind_method(D_METHOD("get_process_info", "process_info"), &NavigationServer3D::get_process_info); @@ -152,6 +172,9 @@ NavigationServer3D::NavigationServer3D() { GLOBAL_DEF_BASIC("navigation/3d/default_edge_connection_margin", 0.25); GLOBAL_DEF_BASIC("navigation/3d/default_link_connection_radius", 1.0); + GLOBAL_DEF("navigation/avoidance/thread_model/avoidance_use_multiple_threads", true); + GLOBAL_DEF("navigation/avoidance/thread_model/avoidance_use_high_priority_threads", true); + #ifdef DEBUG_ENABLED debug_navigation_edge_connection_color = GLOBAL_DEF("debug/shapes/navigation/edge_connection_color", Color(1.0, 0.0, 1.0, 1.0)); debug_navigation_geometry_edge_color = GLOBAL_DEF("debug/shapes/navigation/geometry_edge_color", Color(0.5, 1.0, 1.0, 1.0)); @@ -172,13 +195,24 @@ NavigationServer3D::NavigationServer3D() { debug_navigation_enable_agent_paths = GLOBAL_DEF("debug/shapes/navigation/enable_agent_paths", true); debug_navigation_enable_agent_paths_xray = GLOBAL_DEF("debug/shapes/navigation/enable_agent_paths_xray", true); - debug_navigation_agent_path_point_size = GLOBAL_DEF("debug/shapes/navigation/agent_path_point_size", 4.0); + debug_navigation_avoidance_agents_radius_color = GLOBAL_DEF("debug/shapes/avoidance/agents_radius_color", Color(1.0, 1.0, 0.0, 0.25)); + debug_navigation_avoidance_obstacles_radius_color = GLOBAL_DEF("debug/shapes/avoidance/obstacles_radius_color", Color(1.0, 0.5, 0.0, 0.25)); + debug_navigation_avoidance_static_obstacle_pushin_face_color = GLOBAL_DEF("debug/shapes/avoidance/obstacles_static_face_pushin_color", Color(1.0, 0.0, 0.0, 0.0)); + debug_navigation_avoidance_static_obstacle_pushin_edge_color = GLOBAL_DEF("debug/shapes/avoidance/obstacles_static_edge_pushin_color", Color(1.0, 0.0, 0.0, 1.0)); + debug_navigation_avoidance_static_obstacle_pushout_face_color = GLOBAL_DEF("debug/shapes/avoidance/obstacles_static_face_pushout_color", Color(1.0, 1.0, 0.0, 0.5)); + debug_navigation_avoidance_static_obstacle_pushout_edge_color = GLOBAL_DEF("debug/shapes/avoidance/obstacles_static_edge_pushout_color", Color(1.0, 1.0, 0.0, 1.0)); + debug_navigation_avoidance_enable_agents_radius = GLOBAL_DEF("debug/shapes/avoidance/enable_agents_radius", true); + debug_navigation_avoidance_enable_obstacles_radius = GLOBAL_DEF("debug/shapes/avoidance/enable_obstacles_radius", true); + debug_navigation_avoidance_enable_obstacles_static = GLOBAL_DEF("debug/shapes/avoidance/enable_obstacles_static", true); + if (Engine::get_singleton()->is_editor_hint()) { // enable NavigationServer3D when in Editor or else navigation mesh edge connections are invisible // on runtime tests SceneTree has "Visible Navigation" set and main iteration takes care of this set_debug_enabled(true); + set_debug_navigation_enabled(true); + set_debug_avoidance_enabled(true); } #endif // DEBUG_ENABLED } @@ -207,11 +241,18 @@ bool NavigationServer3D::get_debug_enabled() const { #ifdef DEBUG_ENABLED void NavigationServer3D::_emit_navigation_debug_changed_signal() { - if (debug_dirty) { - debug_dirty = false; + if (navigation_debug_dirty) { + navigation_debug_dirty = false; emit_signal(SNAME("navigation_debug_changed")); } } + +void NavigationServer3D::_emit_avoidance_debug_changed_signal() { + if (avoidance_debug_dirty) { + avoidance_debug_dirty = false; + emit_signal(SNAME("avoidance_debug_changed")); + } +} #endif // DEBUG_ENABLED #ifdef DEBUG_ENABLED @@ -351,6 +392,7 @@ Ref<StandardMaterial3D> NavigationServer3D::get_debug_navigation_agent_path_line Ref<StandardMaterial3D> material = Ref<StandardMaterial3D>(memnew(StandardMaterial3D)); material->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); + material->set_albedo(debug_navigation_agent_path_color); if (debug_navigation_enable_agent_paths_xray) { material->set_flag(StandardMaterial3D::FLAG_DISABLE_DEPTH_TEST, true); @@ -367,7 +409,6 @@ Ref<StandardMaterial3D> NavigationServer3D::get_debug_navigation_agent_path_poin } Ref<StandardMaterial3D> material = Ref<StandardMaterial3D>(memnew(StandardMaterial3D)); - material->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); material->set_albedo(debug_navigation_agent_path_color); material->set_flag(StandardMaterial3D::FLAG_USE_POINT_SIZE, true); material->set_point_size(debug_navigation_agent_path_point_size); @@ -380,6 +421,103 @@ Ref<StandardMaterial3D> NavigationServer3D::get_debug_navigation_agent_path_poin return debug_navigation_agent_path_point_material; } +Ref<StandardMaterial3D> NavigationServer3D::get_debug_navigation_avoidance_agents_radius_material() { + if (debug_navigation_avoidance_agents_radius_material.is_valid()) { + return debug_navigation_avoidance_agents_radius_material; + } + + Ref<StandardMaterial3D> material = Ref<StandardMaterial3D>(memnew(StandardMaterial3D)); + material->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA); + material->set_cull_mode(StandardMaterial3D::CULL_DISABLED); + material->set_albedo(debug_navigation_avoidance_agents_radius_color); + material->set_render_priority(StandardMaterial3D::RENDER_PRIORITY_MIN + 2); + + debug_navigation_avoidance_agents_radius_material = material; + return debug_navigation_avoidance_agents_radius_material; +} + +Ref<StandardMaterial3D> NavigationServer3D::get_debug_navigation_avoidance_obstacles_radius_material() { + if (debug_navigation_avoidance_obstacles_radius_material.is_valid()) { + return debug_navigation_avoidance_obstacles_radius_material; + } + + Ref<StandardMaterial3D> material = Ref<StandardMaterial3D>(memnew(StandardMaterial3D)); + material->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); + material->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA); + material->set_cull_mode(StandardMaterial3D::CULL_DISABLED); + material->set_albedo(debug_navigation_avoidance_obstacles_radius_color); + material->set_render_priority(StandardMaterial3D::RENDER_PRIORITY_MIN + 2); + + debug_navigation_avoidance_obstacles_radius_material = material; + return debug_navigation_avoidance_obstacles_radius_material; +} + +Ref<StandardMaterial3D> NavigationServer3D::get_debug_navigation_avoidance_static_obstacle_pushin_face_material() { + if (debug_navigation_avoidance_static_obstacle_pushin_face_material.is_valid()) { + return debug_navigation_avoidance_static_obstacle_pushin_face_material; + } + + Ref<StandardMaterial3D> material = Ref<StandardMaterial3D>(memnew(StandardMaterial3D)); + material->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); + material->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA); + material->set_cull_mode(StandardMaterial3D::CULL_DISABLED); + material->set_albedo(debug_navigation_avoidance_static_obstacle_pushin_face_color); + material->set_render_priority(StandardMaterial3D::RENDER_PRIORITY_MIN + 2); + + debug_navigation_avoidance_static_obstacle_pushin_face_material = material; + return debug_navigation_avoidance_static_obstacle_pushin_face_material; +} + +Ref<StandardMaterial3D> NavigationServer3D::get_debug_navigation_avoidance_static_obstacle_pushout_face_material() { + if (debug_navigation_avoidance_static_obstacle_pushout_face_material.is_valid()) { + return debug_navigation_avoidance_static_obstacle_pushout_face_material; + } + + Ref<StandardMaterial3D> material = Ref<StandardMaterial3D>(memnew(StandardMaterial3D)); + material->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); + material->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA); + material->set_cull_mode(StandardMaterial3D::CULL_DISABLED); + material->set_albedo(debug_navigation_avoidance_static_obstacle_pushout_face_color); + material->set_render_priority(StandardMaterial3D::RENDER_PRIORITY_MIN + 2); + + debug_navigation_avoidance_static_obstacle_pushout_face_material = material; + return debug_navigation_avoidance_static_obstacle_pushout_face_material; +} + +Ref<StandardMaterial3D> NavigationServer3D::get_debug_navigation_avoidance_static_obstacle_pushin_edge_material() { + if (debug_navigation_avoidance_static_obstacle_pushin_edge_material.is_valid()) { + return debug_navigation_avoidance_static_obstacle_pushin_edge_material; + } + + Ref<StandardMaterial3D> material = Ref<StandardMaterial3D>(memnew(StandardMaterial3D)); + material->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); + //material->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA); + //material->set_cull_mode(StandardMaterial3D::CULL_DISABLED); + material->set_albedo(debug_navigation_avoidance_static_obstacle_pushin_edge_color); + //material->set_render_priority(StandardMaterial3D::RENDER_PRIORITY_MIN + 2); + material->set_flag(StandardMaterial3D::FLAG_DISABLE_DEPTH_TEST, true); + + debug_navigation_avoidance_static_obstacle_pushin_edge_material = material; + return debug_navigation_avoidance_static_obstacle_pushin_edge_material; +} + +Ref<StandardMaterial3D> NavigationServer3D::get_debug_navigation_avoidance_static_obstacle_pushout_edge_material() { + if (debug_navigation_avoidance_static_obstacle_pushout_edge_material.is_valid()) { + return debug_navigation_avoidance_static_obstacle_pushout_edge_material; + } + + Ref<StandardMaterial3D> material = Ref<StandardMaterial3D>(memnew(StandardMaterial3D)); + material->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); + ///material->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA); + //material->set_cull_mode(StandardMaterial3D::CULL_DISABLED); + material->set_albedo(debug_navigation_avoidance_static_obstacle_pushout_edge_color); + //material->set_render_priority(StandardMaterial3D::RENDER_PRIORITY_MIN + 2); + material->set_flag(StandardMaterial3D::FLAG_DISABLE_DEPTH_TEST, true); + + debug_navigation_avoidance_static_obstacle_pushout_edge_material = material; + return debug_navigation_avoidance_static_obstacle_pushout_edge_material; +} + void NavigationServer3D::set_debug_navigation_edge_connection_color(const Color &p_color) { debug_navigation_edge_connection_color = p_color; if (debug_navigation_edge_connections_material.is_valid()) { @@ -484,7 +622,7 @@ Color NavigationServer3D::get_debug_navigation_agent_path_color() const { void NavigationServer3D::set_debug_navigation_enable_edge_connections(const bool p_value) { debug_navigation_enable_edge_connections = p_value; - debug_dirty = true; + navigation_debug_dirty = true; call_deferred("_emit_navigation_debug_changed_signal"); } @@ -505,7 +643,7 @@ bool NavigationServer3D::get_debug_navigation_enable_edge_connections_xray() con void NavigationServer3D::set_debug_navigation_enable_edge_lines(const bool p_value) { debug_navigation_enable_edge_lines = p_value; - debug_dirty = true; + navigation_debug_dirty = true; call_deferred("_emit_navigation_debug_changed_signal"); } @@ -526,7 +664,7 @@ bool NavigationServer3D::get_debug_navigation_enable_edge_lines_xray() const { void NavigationServer3D::set_debug_navigation_enable_geometry_face_random_color(const bool p_value) { debug_navigation_enable_geometry_face_random_color = p_value; - debug_dirty = true; + navigation_debug_dirty = true; call_deferred("_emit_navigation_debug_changed_signal"); } @@ -536,7 +674,7 @@ bool NavigationServer3D::get_debug_navigation_enable_geometry_face_random_color( void NavigationServer3D::set_debug_navigation_enable_link_connections(const bool p_value) { debug_navigation_enable_link_connections = p_value; - debug_dirty = true; + navigation_debug_dirty = true; call_deferred("_emit_navigation_debug_changed_signal"); } @@ -555,6 +693,102 @@ bool NavigationServer3D::get_debug_navigation_enable_link_connections_xray() con return debug_navigation_enable_link_connections_xray; } +void NavigationServer3D::set_debug_navigation_avoidance_enable_agents_radius(const bool p_value) { + debug_navigation_avoidance_enable_agents_radius = p_value; + avoidance_debug_dirty = true; + call_deferred("_emit_avoidance_debug_changed_signal"); +} + +bool NavigationServer3D::get_debug_navigation_avoidance_enable_agents_radius() const { + return debug_navigation_avoidance_enable_agents_radius; +} + +void NavigationServer3D::set_debug_navigation_avoidance_enable_obstacles_radius(const bool p_value) { + debug_navigation_avoidance_enable_obstacles_radius = p_value; + avoidance_debug_dirty = true; + call_deferred("_emit_avoidance_debug_changed_signal"); +} + +bool NavigationServer3D::get_debug_navigation_avoidance_enable_obstacles_radius() const { + return debug_navigation_avoidance_enable_obstacles_radius; +} + +void NavigationServer3D::set_debug_navigation_avoidance_enable_obstacles_static(const bool p_value) { + debug_navigation_avoidance_enable_obstacles_static = p_value; + avoidance_debug_dirty = true; + call_deferred("_emit_avoidance_debug_changed_signal"); +} + +bool NavigationServer3D::get_debug_navigation_avoidance_enable_obstacles_static() const { + return debug_navigation_avoidance_enable_obstacles_static; +} + +void NavigationServer3D::set_debug_navigation_avoidance_agents_radius_color(const Color &p_color) { + debug_navigation_avoidance_agents_radius_color = p_color; + if (debug_navigation_avoidance_agents_radius_material.is_valid()) { + debug_navigation_avoidance_agents_radius_material->set_albedo(debug_navigation_avoidance_agents_radius_color); + } +} + +Color NavigationServer3D::get_debug_navigation_avoidance_agents_radius_color() const { + return debug_navigation_avoidance_agents_radius_color; +} + +void NavigationServer3D::set_debug_navigation_avoidance_obstacles_radius_color(const Color &p_color) { + debug_navigation_avoidance_obstacles_radius_color = p_color; + if (debug_navigation_avoidance_obstacles_radius_material.is_valid()) { + debug_navigation_avoidance_obstacles_radius_material->set_albedo(debug_navigation_avoidance_obstacles_radius_color); + } +} + +Color NavigationServer3D::get_debug_navigation_avoidance_obstacles_radius_color() const { + return debug_navigation_avoidance_obstacles_radius_color; +} + +void NavigationServer3D::set_debug_navigation_avoidance_static_obstacle_pushin_face_color(const Color &p_color) { + debug_navigation_avoidance_static_obstacle_pushin_face_color = p_color; + if (debug_navigation_avoidance_static_obstacle_pushin_face_material.is_valid()) { + debug_navigation_avoidance_static_obstacle_pushin_face_material->set_albedo(debug_navigation_avoidance_static_obstacle_pushin_face_color); + } +} + +Color NavigationServer3D::get_debug_navigation_avoidance_static_obstacle_pushin_face_color() const { + return debug_navigation_avoidance_static_obstacle_pushin_face_color; +} + +void NavigationServer3D::set_debug_navigation_avoidance_static_obstacle_pushout_face_color(const Color &p_color) { + debug_navigation_avoidance_static_obstacle_pushout_face_color = p_color; + if (debug_navigation_avoidance_static_obstacle_pushout_face_material.is_valid()) { + debug_navigation_avoidance_static_obstacle_pushout_face_material->set_albedo(debug_navigation_avoidance_static_obstacle_pushout_face_color); + } +} + +Color NavigationServer3D::get_debug_navigation_avoidance_static_obstacle_pushout_face_color() const { + return debug_navigation_avoidance_static_obstacle_pushout_face_color; +} + +void NavigationServer3D::set_debug_navigation_avoidance_static_obstacle_pushin_edge_color(const Color &p_color) { + debug_navigation_avoidance_static_obstacle_pushin_edge_color = p_color; + if (debug_navigation_avoidance_static_obstacle_pushin_edge_material.is_valid()) { + debug_navigation_avoidance_static_obstacle_pushin_edge_material->set_albedo(debug_navigation_avoidance_static_obstacle_pushin_edge_color); + } +} + +Color NavigationServer3D::get_debug_navigation_avoidance_static_obstacle_pushin_edge_color() const { + return debug_navigation_avoidance_static_obstacle_pushin_edge_color; +} + +void NavigationServer3D::set_debug_navigation_avoidance_static_obstacle_pushout_edge_color(const Color &p_color) { + debug_navigation_avoidance_static_obstacle_pushout_edge_color = p_color; + if (debug_navigation_avoidance_static_obstacle_pushout_edge_material.is_valid()) { + debug_navigation_avoidance_static_obstacle_pushout_edge_material->set_albedo(debug_navigation_avoidance_static_obstacle_pushout_edge_color); + } +} + +Color NavigationServer3D::get_debug_navigation_avoidance_static_obstacle_pushout_edge_color() const { + return debug_navigation_avoidance_static_obstacle_pushout_edge_color; +} + void NavigationServer3D::set_debug_navigation_enable_agent_paths(const bool p_value) { if (debug_navigation_enable_agent_paths != p_value) { debug_dirty = true; @@ -585,6 +819,26 @@ bool NavigationServer3D::get_debug_navigation_enable_agent_paths_xray() const { return debug_navigation_enable_agent_paths_xray; } +void NavigationServer3D::set_debug_navigation_enabled(bool p_enabled) { + debug_navigation_enabled = p_enabled; + navigation_debug_dirty = true; + call_deferred("_emit_navigation_debug_changed_signal"); +} + +bool NavigationServer3D::get_debug_navigation_enabled() const { + return debug_navigation_enabled; +} + +void NavigationServer3D::set_debug_avoidance_enabled(bool p_enabled) { + debug_avoidance_enabled = p_enabled; + avoidance_debug_dirty = true; + call_deferred("_emit_avoidance_debug_changed_signal"); +} + +bool NavigationServer3D::get_debug_avoidance_enabled() const { + return debug_avoidance_enabled; +} + #endif // DEBUG_ENABLED void NavigationServer3D::query_path(const Ref<NavigationPathQueryParameters3D> &p_query_parameters, Ref<NavigationPathQueryResult3D> p_query_result) const { diff --git a/servers/navigation_server_3d.h b/servers/navigation_server_3d.h index 7348dab65a..eb8fc59041 100644 --- a/servers/navigation_server_3d.h +++ b/servers/navigation_server_3d.h @@ -103,6 +103,7 @@ public: virtual TypedArray<RID> map_get_links(RID p_map) const = 0; virtual TypedArray<RID> map_get_regions(RID p_map) const = 0; virtual TypedArray<RID> map_get_agents(RID p_map) const = 0; + virtual TypedArray<RID> map_get_obstacles(RID p_map) const = 0; virtual void map_force_update(RID p_map) = 0; @@ -187,6 +188,12 @@ public: virtual void agent_set_map(RID p_agent, RID p_map) = 0; virtual RID agent_get_map(RID p_agent) const = 0; + virtual void agent_set_avoidance_enabled(RID p_agent, bool p_enabled) = 0; + virtual bool agent_get_avoidance_enabled(RID p_agent) const = 0; + + virtual void agent_set_use_3d_avoidance(RID p_agent, bool p_enabled) = 0; + virtual bool agent_get_use_3d_avoidance(RID p_agent) const = 0; + /// The maximum distance (center point to /// center point) to other agents this agent /// takes into account in the navigation. The @@ -204,41 +211,53 @@ public: /// be safe. virtual void agent_set_max_neighbors(RID p_agent, int p_count) = 0; - /// The minimal amount of time for which this - /// agent's velocities that are computed by the - /// simulation are safe with respect to other - /// agents. The larger this number, the sooner - /// this agent will respond to the presence of - /// other agents, but the less freedom this - /// agent has in choosing its velocities. - /// Must be positive. - virtual void agent_set_time_horizon(RID p_agent, real_t p_time) = 0; + // Sets the minimum amount of time in seconds that an agent's + // must be able to stay on the calculated velocity while still avoiding collisions with agent's + // if this value is set to high an agent will often fall back to using a very low velocity just to be safe + virtual void agent_set_time_horizon_agents(RID p_agent, real_t p_time_horizon) = 0; + + /// Sets the minimum amount of time in seconds that an agent's + // must be able to stay on the calculated velocity while still avoiding collisions with obstacle's + // if this value is set to high an agent will often fall back to using a very low velocity just to be safe + virtual void agent_set_time_horizon_obstacles(RID p_agent, real_t p_time_horizon) = 0; /// The radius of this agent. /// Must be non-negative. virtual void agent_set_radius(RID p_agent, real_t p_radius) = 0; + virtual void agent_set_height(RID p_agent, real_t p_height) = 0; /// The maximum speed of this agent. /// Must be non-negative. virtual void agent_set_max_speed(RID p_agent, real_t p_max_speed) = 0; - /// Current velocity of the agent - virtual void agent_set_velocity(RID p_agent, Vector3 p_velocity) = 0; + /// forces and agent velocity change in the avoidance simulation, adds simulation instability if done recklessly + virtual void agent_set_velocity_forced(RID p_agent, Vector3 p_velocity) = 0; - /// The new target velocity. - virtual void agent_set_target_velocity(RID p_agent, Vector3 p_velocity) = 0; + /// The wanted velocity for the agent as a "suggestion" to the avoidance simulation. + /// The simulation will try to fulfil this velocity wish if possible but may change the velocity depending on other agent's and obstacles'. + virtual void agent_set_velocity(RID p_agent, Vector3 p_velocity) = 0; /// Position of the agent in world space. virtual void agent_set_position(RID p_agent, Vector3 p_position) = 0; - /// Agent ignore the Y axis and avoid collisions by moving only on the horizontal plane - virtual void agent_set_ignore_y(RID p_agent, bool p_ignore) = 0; - /// Returns true if the map got changed the previous frame. virtual bool agent_is_map_changed(RID p_agent) const = 0; /// Callback called at the end of the RVO process - virtual void agent_set_callback(RID p_agent, Callable p_callback) = 0; + virtual void agent_set_avoidance_callback(RID p_agent, Callable p_callback) = 0; + + virtual void agent_set_avoidance_layers(RID p_agent, uint32_t p_layers) = 0; + virtual void agent_set_avoidance_mask(RID p_agent, uint32_t p_mask) = 0; + virtual void agent_set_avoidance_priority(RID p_agent, real_t p_priority) = 0; + + /// Creates the obstacle. + virtual RID obstacle_create() = 0; + virtual void obstacle_set_map(RID p_obstacle, RID p_map) = 0; + virtual RID obstacle_get_map(RID p_obstacle) const = 0; + virtual void obstacle_set_height(RID p_obstacle, real_t p_height) = 0; + virtual void obstacle_set_position(RID p_obstacle, Vector3 p_position) = 0; + virtual void obstacle_set_vertices(RID p_obstacle, const Vector<Vector3> &p_vertices) = 0; + virtual void obstacle_set_avoidance_layers(RID p_obstacle, uint32_t p_layers) = 0; /// Destroy the `RID` virtual void free(RID p_object) = 0; @@ -282,8 +301,15 @@ private: #ifdef DEBUG_ENABLED bool debug_dirty = true; + + bool debug_navigation_enabled = false; + bool navigation_debug_dirty = true; void _emit_navigation_debug_changed_signal(); + bool debug_avoidance_enabled = false; + bool avoidance_debug_dirty = true; + void _emit_avoidance_debug_changed_signal(); + Color debug_navigation_edge_connection_color = Color(1.0, 0.0, 1.0, 1.0); Color debug_navigation_geometry_edge_color = Color(0.5, 1.0, 1.0, 1.0); Color debug_navigation_geometry_face_color = Color(0.5, 1.0, 1.0, 0.4); @@ -295,6 +321,14 @@ private: real_t debug_navigation_agent_path_point_size = 4.0; + Color debug_navigation_avoidance_agents_radius_color = Color(1.0, 1.0, 0.0, 0.25); + Color debug_navigation_avoidance_obstacles_radius_color = Color(1.0, 0.5, 0.0, 0.25); + + Color debug_navigation_avoidance_static_obstacle_pushin_face_color = Color(1.0, 0.0, 0.0, 0.0); + Color debug_navigation_avoidance_static_obstacle_pushout_face_color = Color(1.0, 1.0, 0.0, 0.5); + Color debug_navigation_avoidance_static_obstacle_pushin_edge_color = Color(1.0, 0.0, 0.0, 1.0); + Color debug_navigation_avoidance_static_obstacle_pushout_edge_color = Color(1.0, 1.0, 0.0, 1.0); + bool debug_navigation_enable_edge_connections = true; bool debug_navigation_enable_edge_connections_xray = true; bool debug_navigation_enable_edge_lines = true; @@ -305,6 +339,10 @@ private: bool debug_navigation_enable_agent_paths = true; bool debug_navigation_enable_agent_paths_xray = true; + bool debug_navigation_avoidance_enable_agents_radius = true; + bool debug_navigation_avoidance_enable_obstacles_radius = true; + bool debug_navigation_avoidance_enable_obstacles_static = true; + Ref<StandardMaterial3D> debug_navigation_geometry_edge_material; Ref<StandardMaterial3D> debug_navigation_geometry_face_material; Ref<StandardMaterial3D> debug_navigation_geometry_edge_disabled_material; @@ -312,11 +350,24 @@ private: Ref<StandardMaterial3D> debug_navigation_edge_connections_material; Ref<StandardMaterial3D> debug_navigation_link_connections_material; Ref<StandardMaterial3D> debug_navigation_link_connections_disabled_material; + Ref<StandardMaterial3D> debug_navigation_avoidance_agents_radius_material; + Ref<StandardMaterial3D> debug_navigation_avoidance_obstacles_radius_material; + + Ref<StandardMaterial3D> debug_navigation_avoidance_static_obstacle_pushin_face_material; + Ref<StandardMaterial3D> debug_navigation_avoidance_static_obstacle_pushout_face_material; + Ref<StandardMaterial3D> debug_navigation_avoidance_static_obstacle_pushin_edge_material; + Ref<StandardMaterial3D> debug_navigation_avoidance_static_obstacle_pushout_edge_material; Ref<StandardMaterial3D> debug_navigation_agent_path_line_material; Ref<StandardMaterial3D> debug_navigation_agent_path_point_material; public: + void set_debug_navigation_enabled(bool p_enabled); + bool get_debug_navigation_enabled() const; + + void set_debug_avoidance_enabled(bool p_enabled); + bool get_debug_avoidance_enabled() const; + void set_debug_navigation_edge_connection_color(const Color &p_color); Color get_debug_navigation_edge_connection_color() const; @@ -341,6 +392,24 @@ public: void set_debug_navigation_agent_path_color(const Color &p_color); Color get_debug_navigation_agent_path_color() const; + void set_debug_navigation_avoidance_agents_radius_color(const Color &p_color); + Color get_debug_navigation_avoidance_agents_radius_color() const; + + void set_debug_navigation_avoidance_obstacles_radius_color(const Color &p_color); + Color get_debug_navigation_avoidance_obstacles_radius_color() const; + + void set_debug_navigation_avoidance_static_obstacle_pushin_face_color(const Color &p_color); + Color get_debug_navigation_avoidance_static_obstacle_pushin_face_color() const; + + void set_debug_navigation_avoidance_static_obstacle_pushout_face_color(const Color &p_color); + Color get_debug_navigation_avoidance_static_obstacle_pushout_face_color() const; + + void set_debug_navigation_avoidance_static_obstacle_pushin_edge_color(const Color &p_color); + Color get_debug_navigation_avoidance_static_obstacle_pushin_edge_color() const; + + void set_debug_navigation_avoidance_static_obstacle_pushout_edge_color(const Color &p_color); + Color get_debug_navigation_avoidance_static_obstacle_pushout_edge_color() const; + void set_debug_navigation_enable_edge_connections(const bool p_value); bool get_debug_navigation_enable_edge_connections() const; @@ -371,6 +440,15 @@ public: void set_debug_navigation_agent_path_point_size(real_t p_point_size); real_t get_debug_navigation_agent_path_point_size() const; + void set_debug_navigation_avoidance_enable_agents_radius(const bool p_value); + bool get_debug_navigation_avoidance_enable_agents_radius() const; + + void set_debug_navigation_avoidance_enable_obstacles_radius(const bool p_value); + bool get_debug_navigation_avoidance_enable_obstacles_radius() const; + + void set_debug_navigation_avoidance_enable_obstacles_static(const bool p_value); + bool get_debug_navigation_avoidance_enable_obstacles_static() const; + Ref<StandardMaterial3D> get_debug_navigation_geometry_face_material(); Ref<StandardMaterial3D> get_debug_navigation_geometry_edge_material(); Ref<StandardMaterial3D> get_debug_navigation_geometry_face_disabled_material(); @@ -381,6 +459,14 @@ public: Ref<StandardMaterial3D> get_debug_navigation_agent_path_line_material(); Ref<StandardMaterial3D> get_debug_navigation_agent_path_point_material(); + + Ref<StandardMaterial3D> get_debug_navigation_avoidance_agents_radius_material(); + Ref<StandardMaterial3D> get_debug_navigation_avoidance_obstacles_radius_material(); + + Ref<StandardMaterial3D> get_debug_navigation_avoidance_static_obstacle_pushin_face_material(); + Ref<StandardMaterial3D> get_debug_navigation_avoidance_static_obstacle_pushout_face_material(); + Ref<StandardMaterial3D> get_debug_navigation_avoidance_static_obstacle_pushin_edge_material(); + Ref<StandardMaterial3D> get_debug_navigation_avoidance_static_obstacle_pushout_edge_material(); #endif // DEBUG_ENABLED }; diff --git a/servers/navigation_server_3d_dummy.h b/servers/navigation_server_3d_dummy.h index fd9226e59e..c6c0eb4b34 100644 --- a/servers/navigation_server_3d_dummy.h +++ b/servers/navigation_server_3d_dummy.h @@ -57,6 +57,7 @@ public: TypedArray<RID> map_get_links(RID p_map) const override { return TypedArray<RID>(); } TypedArray<RID> map_get_regions(RID p_map) const override { return TypedArray<RID>(); } TypedArray<RID> map_get_agents(RID p_map) const override { return TypedArray<RID>(); } + TypedArray<RID> map_get_obstacles(RID p_map) const override { return TypedArray<RID>(); } void map_force_update(RID p_map) override {} RID region_create() override { return RID(); } void region_set_enter_cost(RID p_region, real_t p_enter_cost) override {} @@ -96,17 +97,32 @@ public: RID agent_create() override { return RID(); } void agent_set_map(RID p_agent, RID p_map) override {} RID agent_get_map(RID p_agent) const override { return RID(); } + void agent_set_avoidance_enabled(RID p_agent, bool p_enabled) override {} + bool agent_get_avoidance_enabled(RID p_agent) const override { return false; } + void agent_set_use_3d_avoidance(RID p_agent, bool p_enabled) override {} + bool agent_get_use_3d_avoidance(RID p_agent) const override { return false; } void agent_set_neighbor_distance(RID p_agent, real_t p_distance) override {} void agent_set_max_neighbors(RID p_agent, int p_count) override {} - void agent_set_time_horizon(RID p_agent, real_t p_time) override {} + void agent_set_time_horizon_agents(RID p_agent, real_t p_time_horizon) override {} + void agent_set_time_horizon_obstacles(RID p_agent, real_t p_time_horizon) override {} void agent_set_radius(RID p_agent, real_t p_radius) override {} + void agent_set_height(RID p_agent, real_t p_height) override {} void agent_set_max_speed(RID p_agent, real_t p_max_speed) override {} + void agent_set_velocity_forced(RID p_agent, Vector3 p_velocity) override {} void agent_set_velocity(RID p_agent, Vector3 p_velocity) override {} - void agent_set_target_velocity(RID p_agent, Vector3 p_velocity) override {} void agent_set_position(RID p_agent, Vector3 p_position) override {} - void agent_set_ignore_y(RID p_agent, bool p_ignore) override {} bool agent_is_map_changed(RID p_agent) const override { return false; } - void agent_set_callback(RID p_agent, Callable p_callback) override {} + void agent_set_avoidance_callback(RID p_agent, Callable p_callback) override {} + void agent_set_avoidance_layers(RID p_agent, uint32_t p_layers) override {} + void agent_set_avoidance_mask(RID p_agent, uint32_t p_mask) override {} + void agent_set_avoidance_priority(RID p_agent, real_t p_priority) override {} + RID obstacle_create() override { return RID(); } + void obstacle_set_map(RID p_obstacle, RID p_map) override {} + RID obstacle_get_map(RID p_obstacle) const override { return RID(); } + void obstacle_set_height(RID p_obstacle, real_t p_height) override {} + void obstacle_set_position(RID p_obstacle, Vector3 p_position) override {} + void obstacle_set_vertices(RID p_obstacle, const Vector<Vector3> &p_vertices) override {} + void obstacle_set_avoidance_layers(RID p_obstacle, uint32_t p_layers) override {} void free(RID p_object) override {} void set_active(bool p_active) override {} void process(real_t delta_time) override {} diff --git a/tests/core/math/test_math_funcs.h b/tests/core/math/test_math_funcs.h index da96ace63f..b6cb9620f1 100644 --- a/tests/core/math/test_math_funcs.h +++ b/tests/core/math/test_math_funcs.h @@ -472,7 +472,7 @@ TEST_CASE_TEMPLATE("[Math] wrapf", T, float, double) { CHECK(Math::wrapf(300'000'000'000.0, -20.0, 160.0) == doctest::Approx((T)120.0)); // float's precision is too low for 300'000'000'000.0, so we reduce it by a factor of 1000. - CHECK(Math::wrapf((float)300'000'000.0, (float)-20.0, (float)160.0) == doctest::Approx((T)128.0)); + CHECK(Math::wrapf((float)15'000'000.0, (float)-20.0, (float)160.0) == doctest::Approx((T)60.0)); } TEST_CASE_TEMPLATE("[Math] fract", T, float, double) { diff --git a/tests/scene/test_curve_3d.h b/tests/scene/test_curve_3d.h new file mode 100644 index 0000000000..0f0d413354 --- /dev/null +++ b/tests/scene/test_curve_3d.h @@ -0,0 +1,261 @@ +/**************************************************************************/ +/* test_curve_3d.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef TEST_CURVE_3D_H +#define TEST_CURVE_3D_H + +#include "core/math/math_funcs.h" +#include "scene/resources/curve.h" + +#include "tests/test_macros.h" + +namespace TestCurve3D { + +void add_sample_curve_points(Ref<Curve3D> &curve) { + Vector3 p0 = Vector3(0, 0, 0); + Vector3 p1 = Vector3(50, 0, 0); + Vector3 p2 = Vector3(50, 50, 50); + Vector3 p3 = Vector3(0, 50, 0); + + Vector3 control0 = p1 - p0; + Vector3 control1 = p3 - p2; + + curve->add_point(p0, Vector3(), control0); + curve->add_point(p3, control1, Vector3()); +} + +TEST_CASE("[Curve3D] Default curve is empty") { + const Ref<Curve3D> curve = memnew(Curve3D); + CHECK(curve->get_point_count() == 0); +} + +TEST_CASE("[Curve3D] Point management") { + Ref<Curve3D> curve = memnew(Curve3D); + + SUBCASE("Functions for adding/removing points should behave as expected") { + curve->set_point_count(2); + CHECK(curve->get_point_count() == 2); + + curve->remove_point(0); + CHECK(curve->get_point_count() == 1); + + curve->add_point(Vector3()); + CHECK(curve->get_point_count() == 2); + + curve->clear_points(); + CHECK(curve->get_point_count() == 0); + } + + SUBCASE("Functions for changing single point properties should behave as expected") { + Vector3 new_in = Vector3(1, 1, 1); + Vector3 new_out = Vector3(1, 1, 1); + Vector3 new_pos = Vector3(1, 1, 1); + real_t new_tilt = 1; + + curve->add_point(Vector3()); + + CHECK(curve->get_point_in(0) != new_in); + curve->set_point_in(0, new_in); + CHECK(curve->get_point_in(0) == new_in); + + CHECK(curve->get_point_out(0) != new_out); + curve->set_point_out(0, new_out); + CHECK(curve->get_point_out(0) == new_out); + + CHECK(curve->get_point_position(0) != new_pos); + curve->set_point_position(0, new_pos); + CHECK(curve->get_point_position(0) == new_pos); + + CHECK(curve->get_point_tilt(0) != new_tilt); + curve->set_point_tilt(0, new_tilt); + CHECK(curve->get_point_tilt(0) == new_tilt); + } +} + +TEST_CASE("[Curve3D] Baked") { + Ref<Curve3D> curve = memnew(Curve3D); + + SUBCASE("Single Point") { + curve->add_point(Vector3()); + + CHECK(curve->get_baked_length() == 0); + CHECK(curve->get_baked_points().size() == 1); + CHECK(curve->get_baked_tilts().size() == 1); + CHECK(curve->get_baked_up_vectors().size() == 1); + } + + SUBCASE("Straight line") { + curve->add_point(Vector3()); + curve->add_point(Vector3(0, 50, 0)); + + CHECK(Math::is_equal_approx(curve->get_baked_length(), 50)); + CHECK(curve->get_baked_points().size() == 369); + CHECK(curve->get_baked_tilts().size() == 369); + CHECK(curve->get_baked_up_vectors().size() == 369); + } + + SUBCASE("Beziér Curve") { + add_sample_curve_points(curve); + + real_t len = curve->get_baked_length(); + real_t n_points = curve->get_baked_points().size(); + // Curve length should be bigger than a straight line between points + CHECK(len > 50); + + SUBCASE("Increase bake interval") { + curve->set_bake_interval(10.0); + CHECK(curve->get_bake_interval() == 10.0); + // Lower resolution should imply less points and smaller length + CHECK(curve->get_baked_length() < len); + CHECK(curve->get_baked_points().size() < n_points); + CHECK(curve->get_baked_tilts().size() < n_points); + CHECK(curve->get_baked_up_vectors().size() < n_points); + } + + SUBCASE("Disable up vectors") { + curve->set_up_vector_enabled(false); + CHECK(curve->is_up_vector_enabled() == false); + CHECK(curve->get_baked_up_vectors().size() == 0); + } + } +} + +TEST_CASE("[Curve3D] Sampling") { + // Sampling over a simple straight line to make assertions simpler + Ref<Curve3D> curve = memnew(Curve3D); + curve->add_point(Vector3()); + curve->add_point(Vector3(0, 50, 0)); + + SUBCASE("sample") { + CHECK(curve->sample(0, 0) == Vector3(0, 0, 0)); + CHECK(curve->sample(0, 0.5) == Vector3(0, 25, 0)); + CHECK(curve->sample(0, 1) == Vector3(0, 50, 0)); + } + + SUBCASE("samplef") { + CHECK(curve->samplef(0) == Vector3(0, 0, 0)); + CHECK(curve->samplef(0.5) == Vector3(0, 25, 0)); + CHECK(curve->samplef(1) == Vector3(0, 50, 0)); + } + + SUBCASE("sample_baked, cubic = false") { + CHECK(curve->sample_baked(curve->get_closest_offset(Vector3(0, 0, 0))) == Vector3(0, 0, 0)); + CHECK(curve->sample_baked(curve->get_closest_offset(Vector3(0, 25, 0))) == Vector3(0, 25, 0)); + CHECK(curve->sample_baked(curve->get_closest_offset(Vector3(0, 50, 0))) == Vector3(0, 50, 0)); + } + + SUBCASE("sample_baked, cubic = true") { + CHECK(curve->sample_baked(curve->get_closest_offset(Vector3(0, 0, 0)), true) == Vector3(0, 0, 0)); + CHECK(curve->sample_baked(curve->get_closest_offset(Vector3(0, 25, 0)), true) == Vector3(0, 25, 0)); + CHECK(curve->sample_baked(curve->get_closest_offset(Vector3(0, 50, 0)), true) == Vector3(0, 50, 0)); + } + + SUBCASE("sample_baked_with_rotation") { + CHECK(curve->sample_baked_with_rotation(curve->get_closest_offset(Vector3(0, 0, 0))) == Transform3D(Basis(Vector3(0, 0, 1), Vector3(1, 0, 0), Vector3(0, 1, 0)), Vector3(0, 0, 0))); + CHECK(curve->sample_baked_with_rotation(curve->get_closest_offset(Vector3(0, 25, 0))) == Transform3D(Basis(Vector3(0, 0, 1), Vector3(1, 0, 0), Vector3(0, 1, 0)), Vector3(0, 25, 0))); + CHECK(curve->sample_baked_with_rotation(curve->get_closest_offset(Vector3(0, 50, 0))) == Transform3D(Basis(Vector3(0, 0, 1), Vector3(1, 0, 0), Vector3(0, 1, 0)), Vector3(0, 50, 0))); + } + + SUBCASE("sample_baked_tilt") { + CHECK(curve->sample_baked_tilt(curve->get_closest_offset(Vector3(0, 0, 0))) == 0); + CHECK(curve->sample_baked_tilt(curve->get_closest_offset(Vector3(0, 25, 0))) == 0); + CHECK(curve->sample_baked_tilt(curve->get_closest_offset(Vector3(0, 50, 0))) == 0); + } + + SUBCASE("sample_baked_up_vector, p_apply_tilt = false") { + CHECK(curve->sample_baked_up_vector(curve->get_closest_offset(Vector3(0, 0, 0))) == Vector3(1, 0, 0)); + CHECK(curve->sample_baked_up_vector(curve->get_closest_offset(Vector3(0, 25, 0))) == Vector3(1, 0, 0)); + CHECK(curve->sample_baked_up_vector(curve->get_closest_offset(Vector3(0, 50, 0))) == Vector3(1, 0, 0)); + } + + SUBCASE("sample_baked_up_vector, p_apply_tilt = true") { + CHECK(curve->sample_baked_up_vector(curve->get_closest_offset(Vector3(0, 0, 0)), true) == Vector3(1, 0, 0)); + CHECK(curve->sample_baked_up_vector(curve->get_closest_offset(Vector3(0, 25, 0)), true) == Vector3(1, 0, 0)); + CHECK(curve->sample_baked_up_vector(curve->get_closest_offset(Vector3(0, 50, 0)), true) == Vector3(1, 0, 0)); + } + + SUBCASE("get_closest_point") { + CHECK(curve->get_closest_point(Vector3(0, 0, 0)) == Vector3(0, 0, 0)); + CHECK(curve->get_closest_point(Vector3(0, 25, 0)) == Vector3(0, 25, 0)); + CHECK(curve->get_closest_point(Vector3(50, 25, 0)) == Vector3(0, 25, 0)); + CHECK(curve->get_closest_point(Vector3(0, 50, 0)) == Vector3(0, 50, 0)); + CHECK(curve->get_closest_point(Vector3(50, 50, 0)) == Vector3(0, 50, 0)); + CHECK(curve->get_closest_point(Vector3(0, 100, 0)) == Vector3(0, 50, 0)); + } +} + +TEST_CASE("[Curve3D] Tessellation") { + Ref<Curve3D> curve = memnew(Curve3D); + add_sample_curve_points(curve); + + const int default_size = curve->tessellate().size(); + + SUBCASE("Increase to max stages should increase num of points") { + CHECK(curve->tessellate(6).size() > default_size); + } + + SUBCASE("Decrease to max stages should decrease num of points") { + CHECK(curve->tessellate(4).size() < default_size); + } + + SUBCASE("Increase to tolerance should decrease num of points") { + CHECK(curve->tessellate(5, 5).size() < default_size); + } + + SUBCASE("Decrease to tolerance should increase num of points") { + CHECK(curve->tessellate(5, 3).size() > default_size); + } + + SUBCASE("Adding a straight segment should only add the last point to tessellate return array") { + curve->add_point(Vector3(0, 100, 0)); + PackedVector3Array tes = curve->tessellate(); + CHECK(tes.size() == default_size + 1); + CHECK(tes[tes.size() - 1] == Vector3(0, 100, 0)); + CHECK(tes[tes.size() - 2] == Vector3(0, 50, 0)); + } +} + +TEST_CASE("[Curve3D] Even length tessellation") { + Ref<Curve3D> curve = memnew(Curve3D); + add_sample_curve_points(curve); + + const int default_size = curve->tessellate_even_length().size(); + + // Default tessellate_even_length tolerance_length is 20.0, by adding a 100 units + // straight, we expect the total size to be increased by more than 5, + // that is, the algo will pick a length < 20.0 and will divide the straight as + // well as the curve as opposed to tessellate() which only adds the final point. + curve->add_point(Vector3(0, 150, 0)); + CHECK(curve->tessellate_even_length().size() > default_size + 5); +} + +} // namespace TestCurve3D + +#endif // TEST_CURVE_3D_H diff --git a/tests/test_main.cpp b/tests/test_main.cpp index a0359162d9..e2808f3aac 100644 --- a/tests/test_main.cpp +++ b/tests/test_main.cpp @@ -93,6 +93,7 @@ #include "tests/scene/test_code_edit.h" #include "tests/scene/test_curve.h" #include "tests/scene/test_curve_2d.h" +#include "tests/scene/test_curve_3d.h" #include "tests/scene/test_gradient.h" #include "tests/scene/test_navigation_agent_2d.h" #include "tests/scene/test_navigation_agent_3d.h" diff --git a/thirdparty/README.md b/thirdparty/README.md index 41d18bdad0..32bcea6c1b 100644 --- a/thirdparty/README.md +++ b/thirdparty/README.md @@ -378,6 +378,7 @@ File extracted from upstream release tarball: Applied the patch in `patches/windows-arm64-hardclock.diff` - Added 2 files `godot_core_mbedtls_platform.c` and `godot_core_mbedtls_config.h` providing configuration for light bundling with core. +- Added the file `godot_module_mbedtls_config.h` to customize the build configuration when bundling the full library. ## meshoptimizer @@ -622,18 +623,27 @@ Files extracted from upstream source: ## rvo2 +For 2D in `rvo2_2d` folder + +- Upstream: https://github.com/snape/RVO2 +- Version: git (f7c5380235f6c9ac8d19cbf71fc94e2d4758b0a3, 2021) +- License: Apache 2.0 + +For 3D in `rvo2_3d` folder + - Upstream: https://github.com/snape/RVO2-3D - Version: git (bfc048670a4e85066e86a1f923d8ea92e3add3b2, 2021) - License: Apache 2.0 Files extracted from upstream source: -- All .cpp and .h files in the `src/` folder except for Export.h, RVO.h, RVOSimulator.cpp and RVOSimulator.h +- All .cpp and .h files in the `src/` folder except for Export.h and RVO.h - LICENSE -Important: Some files have Godot-made changes; so to enrich the features -originally proposed by this library and better integrate this library with -Godot. See the patch in the `patches` folder for details. +Important: Nearly all files have Godot-made changes and renames +to make the 2D and 3D rvo libraries compatible with each other +and solve conflicts and also enrich the feature set originally +proposed by these libraries and better integrate them with Godot. ## spirv-reflect diff --git a/thirdparty/mbedtls/include/godot_core_mbedtls_config.h b/thirdparty/mbedtls/include/godot_core_mbedtls_config.h index 9e7b2742a7..d27bf608fb 100644 --- a/thirdparty/mbedtls/include/godot_core_mbedtls_config.h +++ b/thirdparty/mbedtls/include/godot_core_mbedtls_config.h @@ -1,3 +1,38 @@ +/**************************************************************************/ +/* godot_core_mbedtls_config.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 GODOT_CORE_MBEDTLS_CONFIG_H +#define GODOT_CORE_MBEDTLS_CONFIG_H + +#include <limits.h> + // For AES #define MBEDTLS_CIPHER_MODE_CBC #define MBEDTLS_CIPHER_MODE_CFB @@ -15,4 +50,4 @@ #define MBEDTLS_PLATFORM_ZEROIZE_ALT #define MBEDTLS_NO_DEFAULT_ENTROPY_SOURCES -#include <limits.h> +#endif // GODOT_CORE_MBEDTLS_CONFIG_H diff --git a/thirdparty/mbedtls/include/godot_module_mbedtls_config.h b/thirdparty/mbedtls/include/godot_module_mbedtls_config.h new file mode 100644 index 0000000000..c35f158041 --- /dev/null +++ b/thirdparty/mbedtls/include/godot_module_mbedtls_config.h @@ -0,0 +1,58 @@ +/**************************************************************************/ +/* godot_module_mbedtls_config.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 GODOT_MODULE_MBEDTLS_CONFIG_H +#define GODOT_MODULE_MBEDTLS_CONFIG_H + +#include "platform_config.h" + +#ifdef GODOT_MBEDTLS_INCLUDE_H + +// Allow platforms to customize the mbedTLS configuration. +#include GODOT_MBEDTLS_INCLUDE_H + +#else + +// Include default mbedTLS config. +#include <mbedtls/config.h> + +// Disable weak cryptography. +#undef MBEDTLS_KEY_EXCHANGE_DHE_PSK_ENABLED +#undef MBEDTLS_KEY_EXCHANGE_DHE_RSA_ENABLED +#undef MBEDTLS_SSL_CBC_RECORD_SPLITTING +#undef MBEDTLS_SSL_PROTO_TLS1 +#undef MBEDTLS_SSL_PROTO_TLS1_1 +#undef MBEDTLS_ARC4_C +#undef MBEDTLS_DES_C +#undef MBEDTLS_DHM_C + +#endif // GODOT_MBEDTLS_INCLUDE_H + +#endif // GODOT_MODULE_MBEDTLS_CONFIG_H diff --git a/thirdparty/rvo2/patches/rvo2-godot-changes.patch b/thirdparty/rvo2/patches/rvo2-godot-changes.patch deleted file mode 100644 index 16dbc203ed..0000000000 --- a/thirdparty/rvo2/patches/rvo2-godot-changes.patch +++ /dev/null @@ -1,282 +0,0 @@ -diff --git a/thirdparty/rvo2/Agent.cpp b/thirdparty/rvo2/Agent.cpp -index 5e49a3554c..b35eee9c12 100644 ---- a/thirdparty/rvo2/Agent.cpp -+++ b/thirdparty/rvo2/Agent.cpp -@@ -105,18 +105,17 @@ namespace RVO { - */ - void linearProgram4(const std::vector<Plane> &planes, size_t beginPlane, float radius, Vector3 &result); - -- Agent::Agent(RVOSimulator *sim) : sim_(sim), id_(0), maxNeighbors_(0), maxSpeed_(0.0f), neighborDist_(0.0f), radius_(0.0f), timeHorizon_(0.0f) { } -+ Agent::Agent() : id_(0), maxNeighbors_(0), maxSpeed_(0.0f), neighborDist_(0.0f), radius_(0.0f), timeHorizon_(0.0f), ignore_y_(false) { } - -- void Agent::computeNeighbors() -+ void Agent::computeNeighbors(KdTree *kdTree_) - { - agentNeighbors_.clear(); -- - if (maxNeighbors_ > 0) { -- sim_->kdTree_->computeAgentNeighbors(this, neighborDist_ * neighborDist_); -+ kdTree_->computeAgentNeighbors(this, neighborDist_ * neighborDist_); - } - } - -- void Agent::computeNewVelocity() -+ void Agent::computeNewVelocity(float timeStep) - { - orcaPlanes_.clear(); - const float invTimeHorizon = 1.0f / timeHorizon_; -@@ -124,10 +123,24 @@ namespace RVO { - /* Create agent ORCA planes. */ - for (size_t i = 0; i < agentNeighbors_.size(); ++i) { - const Agent *const other = agentNeighbors_[i].second; -- const Vector3 relativePosition = other->position_ - position_; -- const Vector3 relativeVelocity = velocity_ - other->velocity_; -- const float distSq = absSq(relativePosition); -+ -+ Vector3 relativePosition = other->position_ - position_; -+ Vector3 relativeVelocity = velocity_ - other->velocity_; - const float combinedRadius = radius_ + other->radius_; -+ -+ // This is a Godot feature that allow the agents to avoid the collision -+ // by moving only on the horizontal plane relative to the player velocity. -+ if (ignore_y_) { -+ // Skip if these are in two different heights -+#define ABS(m_v) (((m_v) < 0) ? (-(m_v)) : (m_v)) -+ if (ABS(relativePosition[1]) > combinedRadius * 2) { -+ continue; -+ } -+ relativePosition[1] = 0; -+ relativeVelocity[1] = 0; -+ } -+ -+ const float distSq = absSq(relativePosition); - const float combinedRadiusSq = sqr(combinedRadius); - - Plane plane; -@@ -165,7 +178,7 @@ namespace RVO { - } - else { - /* Collision. */ -- const float invTimeStep = 1.0f / sim_->timeStep_; -+ const float invTimeStep = 1.0f / timeStep; - const Vector3 w = relativeVelocity - invTimeStep * relativePosition; - const float wLength = abs(w); - const Vector3 unitW = w / wLength; -@@ -183,6 +196,11 @@ namespace RVO { - if (planeFail < orcaPlanes_.size()) { - linearProgram4(orcaPlanes_, planeFail, maxSpeed_, newVelocity_); - } -+ -+ if (ignore_y_) { -+ // Not 100% necessary, but better to have. -+ newVelocity_[1] = prefVelocity_[1]; -+ } - } - - void Agent::insertAgentNeighbor(const Agent *agent, float &rangeSq) -@@ -211,12 +229,6 @@ namespace RVO { - } - } - -- void Agent::update() -- { -- velocity_ = newVelocity_; -- position_ += velocity_ * sim_->timeStep_; -- } -- - bool linearProgram1(const std::vector<Plane> &planes, size_t planeNo, const Line &line, float radius, const Vector3 &optVelocity, bool directionOpt, Vector3 &result) - { - const float dotProduct = line.point * line.direction; -diff --git a/thirdparty/rvo2/Agent.h b/thirdparty/rvo2/Agent.h -index d3922ec645..45fbead2f5 100644 ---- a/thirdparty/rvo2/Agent.h -+++ b/thirdparty/rvo2/Agent.h -@@ -41,30 +41,52 @@ - #include <utility> - #include <vector> - --#include "RVOSimulator.h" - #include "Vector3.h" - -+// Note: Slightly modified to work better in Godot. -+// - The agent can be created by anyone. -+// - The simulator pointer is removed. -+// - The update function is removed. -+// - The compute velocity function now need the timeStep. -+// - Moved the `Plane` class here. -+// - Added a new parameter `ignore_y_` in the `Agent`. This parameter is used to control a godot feature that allows to avoid collisions by moving on the horizontal plane. - namespace RVO { -+ /** -+ * \brief Defines a plane. -+ */ -+ class Plane { -+ public: -+ /** -+ * \brief A point on the plane. -+ */ -+ Vector3 point; -+ -+ /** -+ * \brief The normal to the plane. -+ */ -+ Vector3 normal; -+ }; -+ - /** - * \brief Defines an agent in the simulation. - */ - class Agent { -- private: -+ public: - /** - * \brief Constructs an agent instance. - * \param sim The simulator instance. - */ -- explicit Agent(RVOSimulator *sim); -+ explicit Agent(); - - /** - * \brief Computes the neighbors of this agent. - */ -- void computeNeighbors(); -+ void computeNeighbors(class KdTree *kdTree_); - - /** - * \brief Computes the new velocity of this agent. - */ -- void computeNewVelocity(); -+ void computeNewVelocity(float timeStep); - - /** - * \brief Inserts an agent neighbor into the set of neighbors of this agent. -@@ -73,16 +95,10 @@ namespace RVO { - */ - void insertAgentNeighbor(const Agent *agent, float &rangeSq); - -- /** -- * \brief Updates the three-dimensional position and three-dimensional velocity of this agent. -- */ -- void update(); -- - Vector3 newVelocity_; - Vector3 position_; - Vector3 prefVelocity_; - Vector3 velocity_; -- RVOSimulator *sim_; - size_t id_; - size_t maxNeighbors_; - float maxSpeed_; -@@ -91,9 +107,11 @@ namespace RVO { - float timeHorizon_; - std::vector<std::pair<float, const Agent *> > agentNeighbors_; - std::vector<Plane> orcaPlanes_; -+ /// This is a godot feature that allows the Agent to avoid collision by mooving -+ /// on the horizontal plane. -+ bool ignore_y_; - - friend class KdTree; -- friend class RVOSimulator; - }; - } - -diff --git a/thirdparty/rvo2/KdTree.cpp b/thirdparty/rvo2/KdTree.cpp -index 5e9e9777a6..c857f299df 100644 ---- a/thirdparty/rvo2/KdTree.cpp -+++ b/thirdparty/rvo2/KdTree.cpp -@@ -36,16 +36,15 @@ - - #include "Agent.h" - #include "Definitions.h" --#include "RVOSimulator.h" - - namespace RVO { - const size_t RVO3D_MAX_LEAF_SIZE = 10; - -- KdTree::KdTree(RVOSimulator *sim) : sim_(sim) { } -+ KdTree::KdTree() { } - -- void KdTree::buildAgentTree() -+ void KdTree::buildAgentTree(std::vector<Agent *> agents) - { -- agents_ = sim_->agents_; -+ agents_.swap(agents); - - if (!agents_.empty()) { - agentTree_.resize(2 * agents_.size() - 1); -diff --git a/thirdparty/rvo2/KdTree.h b/thirdparty/rvo2/KdTree.h -index a09384c20f..69d8920ce0 100644 ---- a/thirdparty/rvo2/KdTree.h -+++ b/thirdparty/rvo2/KdTree.h -@@ -41,6 +41,9 @@ - - #include "Vector3.h" - -+// Note: Slightly modified to work better with Godot. -+// - Removed `sim_`. -+// - KdTree things are public - namespace RVO { - class Agent; - class RVOSimulator; -@@ -49,7 +52,7 @@ namespace RVO { - * \brief Defines <i>k</i>d-trees for agents in the simulation. - */ - class KdTree { -- private: -+ public: - /** - * \brief Defines an agent <i>k</i>d-tree node. - */ -@@ -90,12 +93,12 @@ namespace RVO { - * \brief Constructs a <i>k</i>d-tree instance. - * \param sim The simulator instance. - */ -- explicit KdTree(RVOSimulator *sim); -+ explicit KdTree(); - - /** - * \brief Builds an agent <i>k</i>d-tree. - */ -- void buildAgentTree(); -+ void buildAgentTree(std::vector<Agent *> agents); - - void buildAgentTreeRecursive(size_t begin, size_t end, size_t node); - -@@ -110,7 +113,6 @@ namespace RVO { - - std::vector<Agent *> agents_; - std::vector<AgentTreeNode> agentTree_; -- RVOSimulator *sim_; - - friend class Agent; - friend class RVOSimulator; -diff --git a/thirdparty/rvo2/Vector3.h b/thirdparty/rvo2/Vector3.h -index 6c3223bb87..f44e311f29 100644 ---- a/thirdparty/rvo2/Vector3.h -+++ b/thirdparty/rvo2/Vector3.h -@@ -41,7 +41,7 @@ - #include <cstddef> - #include <ostream> - --#include "Export.h" -+#define RVO3D_EXPORT - - namespace RVO { - /** -@@ -59,17 +59,6 @@ namespace RVO { - val_[2] = 0.0f; - } - -- /** -- * \brief Constructs and initializes a three-dimensional vector from the specified three-dimensional vector. -- * \param vector The three-dimensional vector containing the xyz-coordinates. -- */ -- inline Vector3(const Vector3 &vector) -- { -- val_[0] = vector[0]; -- val_[1] = vector[1]; -- val_[2] = vector[2]; -- } -- - /** - * \brief Constructs and initializes a three-dimensional vector from the specified three-element array. - * \param val The three-element array containing the xyz-coordinates. diff --git a/thirdparty/rvo2/rvo2_2d/Agent2d.cpp b/thirdparty/rvo2/rvo2_2d/Agent2d.cpp new file mode 100644 index 0000000000..3ff95a4922 --- /dev/null +++ b/thirdparty/rvo2/rvo2_2d/Agent2d.cpp @@ -0,0 +1,594 @@ +/* + * Agent2d.cpp + * RVO2 Library + * + * Copyright 2008 University of North Carolina at Chapel Hill + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Please send all bug reports to <geom@cs.unc.edu>. + * + * The authors may be contacted via: + * + * Jur van den Berg, Stephen J. Guy, Jamie Snape, Ming C. Lin, Dinesh Manocha + * Dept. of Computer Science + * 201 S. Columbia St. + * Frederick P. Brooks, Jr. Computer Science Bldg. + * Chapel Hill, N.C. 27599-3175 + * United States of America + * + * <http://gamma.cs.unc.edu/RVO2/> + */ + +#include "Agent2d.h" + +#include "KdTree2d.h" +#include "Obstacle2d.h" + +namespace RVO2D { + Agent2D::Agent2D() : maxNeighbors_(0), maxSpeed_(0.0f), neighborDist_(0.0f), radius_(0.0f), timeHorizon_(0.0f), timeHorizonObst_(0.0f), id_(0) { } + + void Agent2D::computeNeighbors(RVOSimulator2D *sim_) + { + obstacleNeighbors_.clear(); + float rangeSq = sqr(timeHorizonObst_ * maxSpeed_ + radius_); + sim_->kdTree_->computeObstacleNeighbors(this, rangeSq); + + agentNeighbors_.clear(); + + if (maxNeighbors_ > 0) { + rangeSq = sqr(neighborDist_); + sim_->kdTree_->computeAgentNeighbors(this, rangeSq); + } + } + + /* Search for the best new velocity. */ + void Agent2D::computeNewVelocity(RVOSimulator2D *sim_) + { + orcaLines_.clear(); + + const float invTimeHorizonObst = 1.0f / timeHorizonObst_; + + /* Create obstacle ORCA lines. */ + for (size_t i = 0; i < obstacleNeighbors_.size(); ++i) { + + const Obstacle2D *obstacle1 = obstacleNeighbors_[i].second; + const Obstacle2D *obstacle2 = obstacle1->nextObstacle_; + + const Vector2 relativePosition1 = obstacle1->point_ - position_; + const Vector2 relativePosition2 = obstacle2->point_ - position_; + + /* + * Check if velocity obstacle of obstacle is already taken care of by + * previously constructed obstacle ORCA lines. + */ + bool alreadyCovered = false; + + for (size_t j = 0; j < orcaLines_.size(); ++j) { + if (det(invTimeHorizonObst * relativePosition1 - orcaLines_[j].point, orcaLines_[j].direction) - invTimeHorizonObst * radius_ >= -RVO_EPSILON && det(invTimeHorizonObst * relativePosition2 - orcaLines_[j].point, orcaLines_[j].direction) - invTimeHorizonObst * radius_ >= -RVO_EPSILON) { + alreadyCovered = true; + break; + } + } + + if (alreadyCovered) { + continue; + } + + /* Not yet covered. Check for collisions. */ + + const float distSq1 = absSq(relativePosition1); + const float distSq2 = absSq(relativePosition2); + + const float radiusSq = sqr(radius_); + + const Vector2 obstacleVector = obstacle2->point_ - obstacle1->point_; + const float s = (-relativePosition1 * obstacleVector) / absSq(obstacleVector); + const float distSqLine = absSq(-relativePosition1 - s * obstacleVector); + + Line line; + + if (s < 0.0f && distSq1 <= radiusSq) { + /* Collision with left vertex. Ignore if non-convex. */ + if (obstacle1->isConvex_) { + line.point = Vector2(0.0f, 0.0f); + line.direction = normalize(Vector2(-relativePosition1.y(), relativePosition1.x())); + orcaLines_.push_back(line); + } + + continue; + } + else if (s > 1.0f && distSq2 <= radiusSq) { + /* Collision with right vertex. Ignore if non-convex + * or if it will be taken care of by neighoring obstace */ + if (obstacle2->isConvex_ && det(relativePosition2, obstacle2->unitDir_) >= 0.0f) { + line.point = Vector2(0.0f, 0.0f); + line.direction = normalize(Vector2(-relativePosition2.y(), relativePosition2.x())); + orcaLines_.push_back(line); + } + + continue; + } + else if (s >= 0.0f && s < 1.0f && distSqLine <= radiusSq) { + /* Collision with obstacle segment. */ + line.point = Vector2(0.0f, 0.0f); + line.direction = -obstacle1->unitDir_; + orcaLines_.push_back(line); + continue; + } + + /* + * No collision. + * Compute legs. When obliquely viewed, both legs can come from a single + * vertex. Legs extend cut-off line when nonconvex vertex. + */ + + Vector2 leftLegDirection, rightLegDirection; + + if (s < 0.0f && distSqLine <= radiusSq) { + /* + * Obstacle viewed obliquely so that left vertex + * defines velocity obstacle. + */ + if (!obstacle1->isConvex_) { + /* Ignore obstacle. */ + continue; + } + + obstacle2 = obstacle1; + + const float leg1 = std::sqrt(distSq1 - radiusSq); + leftLegDirection = Vector2(relativePosition1.x() * leg1 - relativePosition1.y() * radius_, relativePosition1.x() * radius_ + relativePosition1.y() * leg1) / distSq1; + rightLegDirection = Vector2(relativePosition1.x() * leg1 + relativePosition1.y() * radius_, -relativePosition1.x() * radius_ + relativePosition1.y() * leg1) / distSq1; + } + else if (s > 1.0f && distSqLine <= radiusSq) { + /* + * Obstacle viewed obliquely so that + * right vertex defines velocity obstacle. + */ + if (!obstacle2->isConvex_) { + /* Ignore obstacle. */ + continue; + } + + obstacle1 = obstacle2; + + const float leg2 = std::sqrt(distSq2 - radiusSq); + leftLegDirection = Vector2(relativePosition2.x() * leg2 - relativePosition2.y() * radius_, relativePosition2.x() * radius_ + relativePosition2.y() * leg2) / distSq2; + rightLegDirection = Vector2(relativePosition2.x() * leg2 + relativePosition2.y() * radius_, -relativePosition2.x() * radius_ + relativePosition2.y() * leg2) / distSq2; + } + else { + /* Usual situation. */ + if (obstacle1->isConvex_) { + const float leg1 = std::sqrt(distSq1 - radiusSq); + leftLegDirection = Vector2(relativePosition1.x() * leg1 - relativePosition1.y() * radius_, relativePosition1.x() * radius_ + relativePosition1.y() * leg1) / distSq1; + } + else { + /* Left vertex non-convex; left leg extends cut-off line. */ + leftLegDirection = -obstacle1->unitDir_; + } + + if (obstacle2->isConvex_) { + const float leg2 = std::sqrt(distSq2 - radiusSq); + rightLegDirection = Vector2(relativePosition2.x() * leg2 + relativePosition2.y() * radius_, -relativePosition2.x() * radius_ + relativePosition2.y() * leg2) / distSq2; + } + else { + /* Right vertex non-convex; right leg extends cut-off line. */ + rightLegDirection = obstacle1->unitDir_; + } + } + + /* + * Legs can never point into neighboring edge when convex vertex, + * take cutoff-line of neighboring edge instead. If velocity projected on + * "foreign" leg, no constraint is added. + */ + + const Obstacle2D *const leftNeighbor = obstacle1->prevObstacle_; + + bool isLeftLegForeign = false; + bool isRightLegForeign = false; + + if (obstacle1->isConvex_ && det(leftLegDirection, -leftNeighbor->unitDir_) >= 0.0f) { + /* Left leg points into obstacle. */ + leftLegDirection = -leftNeighbor->unitDir_; + isLeftLegForeign = true; + } + + if (obstacle2->isConvex_ && det(rightLegDirection, obstacle2->unitDir_) <= 0.0f) { + /* Right leg points into obstacle. */ + rightLegDirection = obstacle2->unitDir_; + isRightLegForeign = true; + } + + /* Compute cut-off centers. */ + const Vector2 leftCutoff = invTimeHorizonObst * (obstacle1->point_ - position_); + const Vector2 rightCutoff = invTimeHorizonObst * (obstacle2->point_ - position_); + const Vector2 cutoffVec = rightCutoff - leftCutoff; + + /* Project current velocity on velocity obstacle. */ + + /* Check if current velocity is projected on cutoff circles. */ + const float t = (obstacle1 == obstacle2 ? 0.5f : ((velocity_ - leftCutoff) * cutoffVec) / absSq(cutoffVec)); + const float tLeft = ((velocity_ - leftCutoff) * leftLegDirection); + const float tRight = ((velocity_ - rightCutoff) * rightLegDirection); + + if ((t < 0.0f && tLeft < 0.0f) || (obstacle1 == obstacle2 && tLeft < 0.0f && tRight < 0.0f)) { + /* Project on left cut-off circle. */ + const Vector2 unitW = normalize(velocity_ - leftCutoff); + + line.direction = Vector2(unitW.y(), -unitW.x()); + line.point = leftCutoff + radius_ * invTimeHorizonObst * unitW; + orcaLines_.push_back(line); + continue; + } + else if (t > 1.0f && tRight < 0.0f) { + /* Project on right cut-off circle. */ + const Vector2 unitW = normalize(velocity_ - rightCutoff); + + line.direction = Vector2(unitW.y(), -unitW.x()); + line.point = rightCutoff + radius_ * invTimeHorizonObst * unitW; + orcaLines_.push_back(line); + continue; + } + + /* + * Project on left leg, right leg, or cut-off line, whichever is closest + * to velocity. + */ + const float distSqCutoff = ((t < 0.0f || t > 1.0f || obstacle1 == obstacle2) ? std::numeric_limits<float>::infinity() : absSq(velocity_ - (leftCutoff + t * cutoffVec))); + const float distSqLeft = ((tLeft < 0.0f) ? std::numeric_limits<float>::infinity() : absSq(velocity_ - (leftCutoff + tLeft * leftLegDirection))); + const float distSqRight = ((tRight < 0.0f) ? std::numeric_limits<float>::infinity() : absSq(velocity_ - (rightCutoff + tRight * rightLegDirection))); + + if (distSqCutoff <= distSqLeft && distSqCutoff <= distSqRight) { + /* Project on cut-off line. */ + line.direction = -obstacle1->unitDir_; + line.point = leftCutoff + radius_ * invTimeHorizonObst * Vector2(-line.direction.y(), line.direction.x()); + orcaLines_.push_back(line); + continue; + } + else if (distSqLeft <= distSqRight) { + /* Project on left leg. */ + if (isLeftLegForeign) { + continue; + } + + line.direction = leftLegDirection; + line.point = leftCutoff + radius_ * invTimeHorizonObst * Vector2(-line.direction.y(), line.direction.x()); + orcaLines_.push_back(line); + continue; + } + else { + /* Project on right leg. */ + if (isRightLegForeign) { + continue; + } + + line.direction = -rightLegDirection; + line.point = rightCutoff + radius_ * invTimeHorizonObst * Vector2(-line.direction.y(), line.direction.x()); + orcaLines_.push_back(line); + continue; + } + } + + const size_t numObstLines = orcaLines_.size(); + + const float invTimeHorizon = 1.0f / timeHorizon_; + + /* Create agent ORCA lines. */ + for (size_t i = 0; i < agentNeighbors_.size(); ++i) { + const Agent2D *const other = agentNeighbors_[i].second; + + //const float timeHorizon_mod = (avoidance_priority_ - other->avoidance_priority_ + 1.0f) * 0.5f; + //const float invTimeHorizon = (1.0f / timeHorizon_) * timeHorizon_mod; + + const Vector2 relativePosition = other->position_ - position_; + const Vector2 relativeVelocity = velocity_ - other->velocity_; + const float distSq = absSq(relativePosition); + const float combinedRadius = radius_ + other->radius_; + const float combinedRadiusSq = sqr(combinedRadius); + + Line line; + Vector2 u; + + if (distSq > combinedRadiusSq) { + /* No collision. */ + const Vector2 w = relativeVelocity - invTimeHorizon * relativePosition; + /* Vector from cutoff center to relative velocity. */ + const float wLengthSq = absSq(w); + + const float dotProduct1 = w * relativePosition; + + if (dotProduct1 < 0.0f && sqr(dotProduct1) > combinedRadiusSq * wLengthSq) { + /* Project on cut-off circle. */ + const float wLength = std::sqrt(wLengthSq); + const Vector2 unitW = w / wLength; + + line.direction = Vector2(unitW.y(), -unitW.x()); + u = (combinedRadius * invTimeHorizon - wLength) * unitW; + } + else { + /* Project on legs. */ + const float leg = std::sqrt(distSq - combinedRadiusSq); + + if (det(relativePosition, w) > 0.0f) { + /* Project on left leg. */ + line.direction = Vector2(relativePosition.x() * leg - relativePosition.y() * combinedRadius, relativePosition.x() * combinedRadius + relativePosition.y() * leg) / distSq; + } + else { + /* Project on right leg. */ + line.direction = -Vector2(relativePosition.x() * leg + relativePosition.y() * combinedRadius, -relativePosition.x() * combinedRadius + relativePosition.y() * leg) / distSq; + } + + const float dotProduct2 = relativeVelocity * line.direction; + + u = dotProduct2 * line.direction - relativeVelocity; + } + } + else { + /* Collision. Project on cut-off circle of time timeStep. */ + const float invTimeStep = 1.0f / sim_->timeStep_; + + /* Vector from cutoff center to relative velocity. */ + const Vector2 w = relativeVelocity - invTimeStep * relativePosition; + + const float wLength = abs(w); + const Vector2 unitW = w / wLength; + + line.direction = Vector2(unitW.y(), -unitW.x()); + u = (combinedRadius * invTimeStep - wLength) * unitW; + } + + line.point = velocity_ + 0.5f * u; + orcaLines_.push_back(line); + } + + size_t lineFail = linearProgram2(orcaLines_, maxSpeed_, prefVelocity_, false, newVelocity_); + + if (lineFail < orcaLines_.size()) { + linearProgram3(orcaLines_, numObstLines, lineFail, maxSpeed_, newVelocity_); + } + } + + void Agent2D::insertAgentNeighbor(const Agent2D *agent, float &rangeSq) + { + // no point processing same agent + if (this == agent) { + return; + } + // ignore other agent if layers/mask bitmasks have no matching bit + if ((avoidance_mask_ & agent->avoidance_layers_) == 0) { + return; + } + // ignore other agent if this agent is below or above + if ((elevation_ > agent->elevation_ + agent->height_) || (elevation_ + height_ < agent->elevation_)) { + return; + } + + if (avoidance_priority_ > agent->avoidance_priority_) { + return; + } + + const float distSq = absSq(position_ - agent->position_); + + if (distSq < rangeSq) { + if (agentNeighbors_.size() < maxNeighbors_) { + agentNeighbors_.push_back(std::make_pair(distSq, agent)); + } + + size_t i = agentNeighbors_.size() - 1; + + while (i != 0 && distSq < agentNeighbors_[i - 1].first) { + agentNeighbors_[i] = agentNeighbors_[i - 1]; + --i; + } + + agentNeighbors_[i] = std::make_pair(distSq, agent); + + if (agentNeighbors_.size() == maxNeighbors_) { + rangeSq = agentNeighbors_.back().first; + } + } + } + + void Agent2D::insertObstacleNeighbor(const Obstacle2D *obstacle, float rangeSq) + { + const Obstacle2D *const nextObstacle = obstacle->nextObstacle_; + + // ignore obstacle if no matching layer/mask + if ((avoidance_mask_ & nextObstacle->avoidance_layers_) == 0) { + return; + } + // ignore obstacle if below or above + if ((elevation_ > obstacle->elevation_ + obstacle->height_) || (elevation_ + height_ < obstacle->elevation_)) { + return; + } + + const float distSq = distSqPointLineSegment(obstacle->point_, nextObstacle->point_, position_); + + if (distSq < rangeSq) { + obstacleNeighbors_.push_back(std::make_pair(distSq, obstacle)); + + size_t i = obstacleNeighbors_.size() - 1; + + while (i != 0 && distSq < obstacleNeighbors_[i - 1].first) { + obstacleNeighbors_[i] = obstacleNeighbors_[i - 1]; + --i; + } + + obstacleNeighbors_[i] = std::make_pair(distSq, obstacle); + } + //} + } + + void Agent2D::update(RVOSimulator2D *sim_) + { + velocity_ = newVelocity_; + position_ += velocity_ * sim_->timeStep_; + } + + bool linearProgram1(const std::vector<Line> &lines, size_t lineNo, float radius, const Vector2 &optVelocity, bool directionOpt, Vector2 &result) + { + const float dotProduct = lines[lineNo].point * lines[lineNo].direction; + const float discriminant = sqr(dotProduct) + sqr(radius) - absSq(lines[lineNo].point); + + if (discriminant < 0.0f) { + /* Max speed circle fully invalidates line lineNo. */ + return false; + } + + const float sqrtDiscriminant = std::sqrt(discriminant); + float tLeft = -dotProduct - sqrtDiscriminant; + float tRight = -dotProduct + sqrtDiscriminant; + + for (size_t i = 0; i < lineNo; ++i) { + const float denominator = det(lines[lineNo].direction, lines[i].direction); + const float numerator = det(lines[i].direction, lines[lineNo].point - lines[i].point); + + if (std::fabs(denominator) <= RVO_EPSILON) { + /* Lines lineNo and i are (almost) parallel. */ + if (numerator < 0.0f) { + return false; + } + else { + continue; + } + } + + const float t = numerator / denominator; + + if (denominator >= 0.0f) { + /* Line i bounds line lineNo on the right. */ + tRight = std::min(tRight, t); + } + else { + /* Line i bounds line lineNo on the left. */ + tLeft = std::max(tLeft, t); + } + + if (tLeft > tRight) { + return false; + } + } + + if (directionOpt) { + /* Optimize direction. */ + if (optVelocity * lines[lineNo].direction > 0.0f) { + /* Take right extreme. */ + result = lines[lineNo].point + tRight * lines[lineNo].direction; + } + else { + /* Take left extreme. */ + result = lines[lineNo].point + tLeft * lines[lineNo].direction; + } + } + else { + /* Optimize closest point. */ + const float t = lines[lineNo].direction * (optVelocity - lines[lineNo].point); + + if (t < tLeft) { + result = lines[lineNo].point + tLeft * lines[lineNo].direction; + } + else if (t > tRight) { + result = lines[lineNo].point + tRight * lines[lineNo].direction; + } + else { + result = lines[lineNo].point + t * lines[lineNo].direction; + } + } + + return true; + } + + size_t linearProgram2(const std::vector<Line> &lines, float radius, const Vector2 &optVelocity, bool directionOpt, Vector2 &result) + { + if (directionOpt) { + /* + * Optimize direction. Note that the optimization velocity is of unit + * length in this case. + */ + result = optVelocity * radius; + } + else if (absSq(optVelocity) > sqr(radius)) { + /* Optimize closest point and outside circle. */ + result = normalize(optVelocity) * radius; + } + else { + /* Optimize closest point and inside circle. */ + result = optVelocity; + } + + for (size_t i = 0; i < lines.size(); ++i) { + if (det(lines[i].direction, lines[i].point - result) > 0.0f) { + /* Result does not satisfy constraint i. Compute new optimal result. */ + const Vector2 tempResult = result; + + if (!linearProgram1(lines, i, radius, optVelocity, directionOpt, result)) { + result = tempResult; + return i; + } + } + } + + return lines.size(); + } + + void linearProgram3(const std::vector<Line> &lines, size_t numObstLines, size_t beginLine, float radius, Vector2 &result) + { + float distance = 0.0f; + + for (size_t i = beginLine; i < lines.size(); ++i) { + if (det(lines[i].direction, lines[i].point - result) > distance) { + /* Result does not satisfy constraint of line i. */ + std::vector<Line> projLines(lines.begin(), lines.begin() + static_cast<ptrdiff_t>(numObstLines)); + + for (size_t j = numObstLines; j < i; ++j) { + Line line; + + float determinant = det(lines[i].direction, lines[j].direction); + + if (std::fabs(determinant) <= RVO_EPSILON) { + /* Line i and line j are parallel. */ + if (lines[i].direction * lines[j].direction > 0.0f) { + /* Line i and line j point in the same direction. */ + continue; + } + else { + /* Line i and line j point in opposite direction. */ + line.point = 0.5f * (lines[i].point + lines[j].point); + } + } + else { + line.point = lines[i].point + (det(lines[j].direction, lines[i].point - lines[j].point) / determinant) * lines[i].direction; + } + + line.direction = normalize(lines[j].direction - lines[i].direction); + projLines.push_back(line); + } + + const Vector2 tempResult = result; + + if (linearProgram2(projLines, radius, Vector2(-lines[i].direction.y(), lines[i].direction.x()), true, result) < projLines.size()) { + /* This should in principle not happen. The result is by definition + * already in the feasible region of this linear program. If it fails, + * it is due to small floating point error, and the current result is + * kept. + */ + result = tempResult; + } + + distance = det(lines[i].direction, lines[i].point - result); + } + } + } +} diff --git a/thirdparty/rvo2/rvo2_2d/Agent2d.h b/thirdparty/rvo2/rvo2_2d/Agent2d.h new file mode 100644 index 0000000000..c666c2de7b --- /dev/null +++ b/thirdparty/rvo2/rvo2_2d/Agent2d.h @@ -0,0 +1,160 @@ +/* + * Agent2d.h + * RVO2 Library + * + * Copyright 2008 University of North Carolina at Chapel Hill + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Please send all bug reports to <geom@cs.unc.edu>. + * + * The authors may be contacted via: + * + * Jur van den Berg, Stephen J. Guy, Jamie Snape, Ming C. Lin, Dinesh Manocha + * Dept. of Computer Science + * 201 S. Columbia St. + * Frederick P. Brooks, Jr. Computer Science Bldg. + * Chapel Hill, N.C. 27599-3175 + * United States of America + * + * <http://gamma.cs.unc.edu/RVO2/> + */ + +#ifndef RVO2D_AGENT_H_ +#define RVO2D_AGENT_H_ + +/** + * \file Agent2d.h + * \brief Contains the Agent class. + */ + +#include "Definitions.h" +#include "RVOSimulator2d.h" + +namespace RVO2D { + /** + * \brief Defines an agent in the simulation. + */ + class Agent2D { + public: + /** + * \brief Constructs an agent instance. + * \param sim The simulator instance. + */ + explicit Agent2D(); + + /** + * \brief Computes the neighbors of this agent. + */ + void computeNeighbors(RVOSimulator2D *sim_); + + /** + * \brief Computes the new velocity of this agent. + */ + void computeNewVelocity(RVOSimulator2D *sim_); + + /** + * \brief Inserts an agent neighbor into the set of neighbors of + * this agent. + * \param agent A pointer to the agent to be inserted. + * \param rangeSq The squared range around this agent. + */ + void insertAgentNeighbor(const Agent2D *agent, float &rangeSq); + + /** + * \brief Inserts a static obstacle neighbor into the set of neighbors + * of this agent. + * \param obstacle The number of the static obstacle to be + * inserted. + * \param rangeSq The squared range around this agent. + */ + void insertObstacleNeighbor(const Obstacle2D *obstacle, float rangeSq); + + /** + * \brief Updates the two-dimensional position and two-dimensional + * velocity of this agent. + */ + void update(RVOSimulator2D *sim_); + + std::vector<std::pair<float, const Agent2D *> > agentNeighbors_; + size_t maxNeighbors_; + float maxSpeed_; + float neighborDist_; + Vector2 newVelocity_; + std::vector<std::pair<float, const Obstacle2D *> > obstacleNeighbors_; + std::vector<Line> orcaLines_; + Vector2 position_; + Vector2 prefVelocity_; + float radius_; + float timeHorizon_; + float timeHorizonObst_; + Vector2 velocity_; + float height_ = 0.0; + float elevation_ = 0.0; + uint32_t avoidance_layers_ = 1; + uint32_t avoidance_mask_ = 1; + float avoidance_priority_ = 1.0; + + size_t id_; + + friend class KdTree2D; + friend class RVOSimulator2D; + }; + + /** + * \relates Agent + * \brief Solves a one-dimensional linear program on a specified line + * subject to linear constraints defined by lines and a circular + * constraint. + * \param lines Lines defining the linear constraints. + * \param lineNo The specified line constraint. + * \param radius The radius of the circular constraint. + * \param optVelocity The optimization velocity. + * \param directionOpt True if the direction should be optimized. + * \param result A reference to the result of the linear program. + * \return True if successful. + */ + bool linearProgram1(const std::vector<Line> &lines, size_t lineNo, + float radius, const Vector2 &optVelocity, + bool directionOpt, Vector2 &result); + + /** + * \relates Agent + * \brief Solves a two-dimensional linear program subject to linear + * constraints defined by lines and a circular constraint. + * \param lines Lines defining the linear constraints. + * \param radius The radius of the circular constraint. + * \param optVelocity The optimization velocity. + * \param directionOpt True if the direction should be optimized. + * \param result A reference to the result of the linear program. + * \return The number of the line it fails on, and the number of lines if successful. + */ + size_t linearProgram2(const std::vector<Line> &lines, float radius, + const Vector2 &optVelocity, bool directionOpt, + Vector2 &result); + + /** + * \relates Agent + * \brief Solves a two-dimensional linear program subject to linear + * constraints defined by lines and a circular constraint. + * \param lines Lines defining the linear constraints. + * \param numObstLines Count of obstacle lines. + * \param beginLine The line on which the 2-d linear program failed. + * \param radius The radius of the circular constraint. + * \param result A reference to the result of the linear program. + */ + void linearProgram3(const std::vector<Line> &lines, size_t numObstLines, size_t beginLine, + float radius, Vector2 &result); +} + +#endif /* RVO2D_AGENT_H_ */ diff --git a/thirdparty/rvo2/rvo2_2d/Definitions.h b/thirdparty/rvo2/rvo2_2d/Definitions.h new file mode 100644 index 0000000000..a5553d8378 --- /dev/null +++ b/thirdparty/rvo2/rvo2_2d/Definitions.h @@ -0,0 +1,110 @@ +/* + * Definitions.h + * RVO2 Library + * + * Copyright 2008 University of North Carolina at Chapel Hill + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Please send all bug reports to <geom@cs.unc.edu>. + * + * The authors may be contacted via: + * + * Jur van den Berg, Stephen J. Guy, Jamie Snape, Ming C. Lin, Dinesh Manocha + * Dept. of Computer Science + * 201 S. Columbia St. + * Frederick P. Brooks, Jr. Computer Science Bldg. + * Chapel Hill, N.C. 27599-3175 + * United States of America + * + * <http://gamma.cs.unc.edu/RVO2/> + */ + +#ifndef RVO2D_DEFINITIONS_H_ +#define RVO2D_DEFINITIONS_H_ + +/** + * \file Definitions.h + * \brief Contains functions and constants used in multiple classes. + */ + +#include <algorithm> +#include <cmath> +#include <cstddef> +#include <cstdint> +#include <limits> +#include <vector> + +#include "Vector2.h" + +/** + * \brief A sufficiently small positive number. + */ +const float RVO_EPSILON = 0.00001f; + +namespace RVO2D { + class Agent2D; + class Obstacle2D; + class RVOSimulator2D; + + /** + * \brief Computes the squared distance from a line segment with the + * specified endpoints to a specified point. + * \param a The first endpoint of the line segment. + * \param b The second endpoint of the line segment. + * \param c The point to which the squared distance is to + * be calculated. + * \return The squared distance from the line segment to the point. + */ + inline float distSqPointLineSegment(const Vector2 &a, const Vector2 &b, + const Vector2 &c) + { + const float r = ((c - a) * (b - a)) / absSq(b - a); + + if (r < 0.0f) { + return absSq(c - a); + } + else if (r > 1.0f) { + return absSq(c - b); + } + else { + return absSq(c - (a + r * (b - a))); + } + } + + /** + * \brief Computes the signed distance from a line connecting the + * specified points to a specified point. + * \param a The first point on the line. + * \param b The second point on the line. + * \param c The point to which the signed distance is to + * be calculated. + * \return Positive when the point c lies to the left of the line ab. + */ + inline float leftOf(const Vector2 &a, const Vector2 &b, const Vector2 &c) + { + return det(a - c, b - a); + } + + /** + * \brief Computes the square of a float. + * \param a The float to be squared. + * \return The square of the float. + */ + inline float sqr(float a) + { + return a * a; + } +} + +#endif /* RVO2D_DEFINITIONS_H_ */ diff --git a/thirdparty/rvo2/rvo2_2d/KdTree2d.cpp b/thirdparty/rvo2/rvo2_2d/KdTree2d.cpp new file mode 100644 index 0000000000..184bc74fe2 --- /dev/null +++ b/thirdparty/rvo2/rvo2_2d/KdTree2d.cpp @@ -0,0 +1,357 @@ +/* + * KdTree2d.cpp + * RVO2 Library + * + * Copyright 2008 University of North Carolina at Chapel Hill + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Please send all bug reports to <geom@cs.unc.edu>. + * + * The authors may be contacted via: + * + * Jur van den Berg, Stephen J. Guy, Jamie Snape, Ming C. Lin, Dinesh Manocha + * Dept. of Computer Science + * 201 S. Columbia St. + * Frederick P. Brooks, Jr. Computer Science Bldg. + * Chapel Hill, N.C. 27599-3175 + * United States of America + * + * <http://gamma.cs.unc.edu/RVO2/> + */ + +#include "KdTree2d.h" + +#include "Agent2d.h" +#include "RVOSimulator2d.h" +#include "Obstacle2d.h" + +namespace RVO2D { + KdTree2D::KdTree2D(RVOSimulator2D *sim) : obstacleTree_(NULL), sim_(sim) { } + + KdTree2D::~KdTree2D() + { + deleteObstacleTree(obstacleTree_); + } + + void KdTree2D::buildAgentTree(std::vector<Agent2D *> agents) + { + agents_.swap(agents); + + if (!agents_.empty()) { + agentTree_.resize(2 * agents_.size() - 1); + buildAgentTreeRecursive(0, agents_.size(), 0); + } + } + + void KdTree2D::buildAgentTreeRecursive(size_t begin, size_t end, size_t node) + { + agentTree_[node].begin = begin; + agentTree_[node].end = end; + agentTree_[node].minX = agentTree_[node].maxX = agents_[begin]->position_.x(); + agentTree_[node].minY = agentTree_[node].maxY = agents_[begin]->position_.y(); + + for (size_t i = begin + 1; i < end; ++i) { + agentTree_[node].maxX = std::max(agentTree_[node].maxX, agents_[i]->position_.x()); + agentTree_[node].minX = std::min(agentTree_[node].minX, agents_[i]->position_.x()); + agentTree_[node].maxY = std::max(agentTree_[node].maxY, agents_[i]->position_.y()); + agentTree_[node].minY = std::min(agentTree_[node].minY, agents_[i]->position_.y()); + } + + if (end - begin > MAX_LEAF_SIZE) { + /* No leaf node. */ + const bool isVertical = (agentTree_[node].maxX - agentTree_[node].minX > agentTree_[node].maxY - agentTree_[node].minY); + const float splitValue = (isVertical ? 0.5f * (agentTree_[node].maxX + agentTree_[node].minX) : 0.5f * (agentTree_[node].maxY + agentTree_[node].minY)); + + size_t left = begin; + size_t right = end; + + while (left < right) { + while (left < right && (isVertical ? agents_[left]->position_.x() : agents_[left]->position_.y()) < splitValue) { + ++left; + } + + while (right > left && (isVertical ? agents_[right - 1]->position_.x() : agents_[right - 1]->position_.y()) >= splitValue) { + --right; + } + + if (left < right) { + std::swap(agents_[left], agents_[right - 1]); + ++left; + --right; + } + } + + if (left == begin) { + ++left; + ++right; + } + + agentTree_[node].left = node + 1; + agentTree_[node].right = node + 2 * (left - begin); + + buildAgentTreeRecursive(begin, left, agentTree_[node].left); + buildAgentTreeRecursive(left, end, agentTree_[node].right); + } + } + + void KdTree2D::buildObstacleTree(std::vector<Obstacle2D *> obstacles) + { + deleteObstacleTree(obstacleTree_); + + obstacleTree_ = buildObstacleTreeRecursive(obstacles); + } + + + KdTree2D::ObstacleTreeNode *KdTree2D::buildObstacleTreeRecursive(const std::vector<Obstacle2D *> &obstacles) + { + if (obstacles.empty()) { + return NULL; + } + else { + ObstacleTreeNode *const node = new ObstacleTreeNode; + + size_t optimalSplit = 0; + size_t minLeft = obstacles.size(); + size_t minRight = obstacles.size(); + + for (size_t i = 0; i < obstacles.size(); ++i) { + size_t leftSize = 0; + size_t rightSize = 0; + + const Obstacle2D *const obstacleI1 = obstacles[i]; + const Obstacle2D *const obstacleI2 = obstacleI1->nextObstacle_; + + /* Compute optimal split node. */ + for (size_t j = 0; j < obstacles.size(); ++j) { + if (i == j) { + continue; + } + + const Obstacle2D *const obstacleJ1 = obstacles[j]; + const Obstacle2D *const obstacleJ2 = obstacleJ1->nextObstacle_; + + const float j1LeftOfI = leftOf(obstacleI1->point_, obstacleI2->point_, obstacleJ1->point_); + const float j2LeftOfI = leftOf(obstacleI1->point_, obstacleI2->point_, obstacleJ2->point_); + + if (j1LeftOfI >= -RVO_EPSILON && j2LeftOfI >= -RVO_EPSILON) { + ++leftSize; + } + else if (j1LeftOfI <= RVO_EPSILON && j2LeftOfI <= RVO_EPSILON) { + ++rightSize; + } + else { + ++leftSize; + ++rightSize; + } + + if (std::make_pair(std::max(leftSize, rightSize), std::min(leftSize, rightSize)) >= std::make_pair(std::max(minLeft, minRight), std::min(minLeft, minRight))) { + break; + } + } + + if (std::make_pair(std::max(leftSize, rightSize), std::min(leftSize, rightSize)) < std::make_pair(std::max(minLeft, minRight), std::min(minLeft, minRight))) { + minLeft = leftSize; + minRight = rightSize; + optimalSplit = i; + } + } + + /* Build split node. */ + std::vector<Obstacle2D *> leftObstacles(minLeft); + std::vector<Obstacle2D *> rightObstacles(minRight); + + size_t leftCounter = 0; + size_t rightCounter = 0; + const size_t i = optimalSplit; + + const Obstacle2D *const obstacleI1 = obstacles[i]; + const Obstacle2D *const obstacleI2 = obstacleI1->nextObstacle_; + + for (size_t j = 0; j < obstacles.size(); ++j) { + if (i == j) { + continue; + } + + Obstacle2D *const obstacleJ1 = obstacles[j]; + Obstacle2D *const obstacleJ2 = obstacleJ1->nextObstacle_; + + const float j1LeftOfI = leftOf(obstacleI1->point_, obstacleI2->point_, obstacleJ1->point_); + const float j2LeftOfI = leftOf(obstacleI1->point_, obstacleI2->point_, obstacleJ2->point_); + + if (j1LeftOfI >= -RVO_EPSILON && j2LeftOfI >= -RVO_EPSILON) { + leftObstacles[leftCounter++] = obstacles[j]; + } + else if (j1LeftOfI <= RVO_EPSILON && j2LeftOfI <= RVO_EPSILON) { + rightObstacles[rightCounter++] = obstacles[j]; + } + else { + /* Split obstacle j. */ + const float t = det(obstacleI2->point_ - obstacleI1->point_, obstacleJ1->point_ - obstacleI1->point_) / det(obstacleI2->point_ - obstacleI1->point_, obstacleJ1->point_ - obstacleJ2->point_); + + const Vector2 splitpoint = obstacleJ1->point_ + t * (obstacleJ2->point_ - obstacleJ1->point_); + + Obstacle2D *const newObstacle = new Obstacle2D(); + newObstacle->point_ = splitpoint; + newObstacle->prevObstacle_ = obstacleJ1; + newObstacle->nextObstacle_ = obstacleJ2; + newObstacle->isConvex_ = true; + newObstacle->unitDir_ = obstacleJ1->unitDir_; + + newObstacle->id_ = sim_->obstacles_.size(); + + sim_->obstacles_.push_back(newObstacle); + + obstacleJ1->nextObstacle_ = newObstacle; + obstacleJ2->prevObstacle_ = newObstacle; + + if (j1LeftOfI > 0.0f) { + leftObstacles[leftCounter++] = obstacleJ1; + rightObstacles[rightCounter++] = newObstacle; + } + else { + rightObstacles[rightCounter++] = obstacleJ1; + leftObstacles[leftCounter++] = newObstacle; + } + } + } + + node->obstacle = obstacleI1; + node->left = buildObstacleTreeRecursive(leftObstacles); + node->right = buildObstacleTreeRecursive(rightObstacles); + return node; + } + } + + void KdTree2D::computeAgentNeighbors(Agent2D *agent, float &rangeSq) const + { + queryAgentTreeRecursive(agent, rangeSq, 0); + } + + void KdTree2D::computeObstacleNeighbors(Agent2D *agent, float rangeSq) const + { + queryObstacleTreeRecursive(agent, rangeSq, obstacleTree_); + } + + void KdTree2D::deleteObstacleTree(ObstacleTreeNode *node) + { + if (node != NULL) { + deleteObstacleTree(node->left); + deleteObstacleTree(node->right); + delete node; + } + } + + void KdTree2D::queryAgentTreeRecursive(Agent2D *agent, float &rangeSq, size_t node) const + { + if (agentTree_[node].end - agentTree_[node].begin <= MAX_LEAF_SIZE) { + for (size_t i = agentTree_[node].begin; i < agentTree_[node].end; ++i) { + agent->insertAgentNeighbor(agents_[i], rangeSq); + } + } + else { + const float distSqLeft = sqr(std::max(0.0f, agentTree_[agentTree_[node].left].minX - agent->position_.x())) + sqr(std::max(0.0f, agent->position_.x() - agentTree_[agentTree_[node].left].maxX)) + sqr(std::max(0.0f, agentTree_[agentTree_[node].left].minY - agent->position_.y())) + sqr(std::max(0.0f, agent->position_.y() - agentTree_[agentTree_[node].left].maxY)); + + const float distSqRight = sqr(std::max(0.0f, agentTree_[agentTree_[node].right].minX - agent->position_.x())) + sqr(std::max(0.0f, agent->position_.x() - agentTree_[agentTree_[node].right].maxX)) + sqr(std::max(0.0f, agentTree_[agentTree_[node].right].minY - agent->position_.y())) + sqr(std::max(0.0f, agent->position_.y() - agentTree_[agentTree_[node].right].maxY)); + + if (distSqLeft < distSqRight) { + if (distSqLeft < rangeSq) { + queryAgentTreeRecursive(agent, rangeSq, agentTree_[node].left); + + if (distSqRight < rangeSq) { + queryAgentTreeRecursive(agent, rangeSq, agentTree_[node].right); + } + } + } + else { + if (distSqRight < rangeSq) { + queryAgentTreeRecursive(agent, rangeSq, agentTree_[node].right); + + if (distSqLeft < rangeSq) { + queryAgentTreeRecursive(agent, rangeSq, agentTree_[node].left); + } + } + } + + } + } + + void KdTree2D::queryObstacleTreeRecursive(Agent2D *agent, float rangeSq, const ObstacleTreeNode *node) const + { + if (node == NULL) { + return; + } + else { + const Obstacle2D *const obstacle1 = node->obstacle; + const Obstacle2D *const obstacle2 = obstacle1->nextObstacle_; + + const float agentLeftOfLine = leftOf(obstacle1->point_, obstacle2->point_, agent->position_); + + queryObstacleTreeRecursive(agent, rangeSq, (agentLeftOfLine >= 0.0f ? node->left : node->right)); + + const float distSqLine = sqr(agentLeftOfLine) / absSq(obstacle2->point_ - obstacle1->point_); + + if (distSqLine < rangeSq) { + if (agentLeftOfLine < 0.0f) { + /* + * Try obstacle at this node only if agent is on right side of + * obstacle (and can see obstacle). + */ + agent->insertObstacleNeighbor(node->obstacle, rangeSq); + } + + /* Try other side of line. */ + queryObstacleTreeRecursive(agent, rangeSq, (agentLeftOfLine >= 0.0f ? node->right : node->left)); + + } + } + } + + bool KdTree2D::queryVisibility(const Vector2 &q1, const Vector2 &q2, float radius) const + { + return queryVisibilityRecursive(q1, q2, radius, obstacleTree_); + } + + bool KdTree2D::queryVisibilityRecursive(const Vector2 &q1, const Vector2 &q2, float radius, const ObstacleTreeNode *node) const + { + if (node == NULL) { + return true; + } + else { + const Obstacle2D *const obstacle1 = node->obstacle; + const Obstacle2D *const obstacle2 = obstacle1->nextObstacle_; + + const float q1LeftOfI = leftOf(obstacle1->point_, obstacle2->point_, q1); + const float q2LeftOfI = leftOf(obstacle1->point_, obstacle2->point_, q2); + const float invLengthI = 1.0f / absSq(obstacle2->point_ - obstacle1->point_); + + if (q1LeftOfI >= 0.0f && q2LeftOfI >= 0.0f) { + return queryVisibilityRecursive(q1, q2, radius, node->left) && ((sqr(q1LeftOfI) * invLengthI >= sqr(radius) && sqr(q2LeftOfI) * invLengthI >= sqr(radius)) || queryVisibilityRecursive(q1, q2, radius, node->right)); + } + else if (q1LeftOfI <= 0.0f && q2LeftOfI <= 0.0f) { + return queryVisibilityRecursive(q1, q2, radius, node->right) && ((sqr(q1LeftOfI) * invLengthI >= sqr(radius) && sqr(q2LeftOfI) * invLengthI >= sqr(radius)) || queryVisibilityRecursive(q1, q2, radius, node->left)); + } + else if (q1LeftOfI >= 0.0f && q2LeftOfI <= 0.0f) { + /* One can see through obstacle from left to right. */ + return queryVisibilityRecursive(q1, q2, radius, node->left) && queryVisibilityRecursive(q1, q2, radius, node->right); + } + else { + const float point1LeftOfQ = leftOf(q1, q2, obstacle1->point_); + const float point2LeftOfQ = leftOf(q1, q2, obstacle2->point_); + const float invLengthQ = 1.0f / absSq(q2 - q1); + + return (point1LeftOfQ * point2LeftOfQ >= 0.0f && sqr(point1LeftOfQ) * invLengthQ > sqr(radius) && sqr(point2LeftOfQ) * invLengthQ > sqr(radius) && queryVisibilityRecursive(q1, q2, radius, node->left) && queryVisibilityRecursive(q1, q2, radius, node->right)); + } + } + } +} diff --git a/thirdparty/rvo2/rvo2_2d/KdTree2d.h b/thirdparty/rvo2/rvo2_2d/KdTree2d.h new file mode 100644 index 0000000000..c7159eab97 --- /dev/null +++ b/thirdparty/rvo2/rvo2_2d/KdTree2d.h @@ -0,0 +1,203 @@ +/* + * KdTree2d.h + * RVO2 Library + * + * Copyright 2008 University of North Carolina at Chapel Hill + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Please send all bug reports to <geom@cs.unc.edu>. + * + * The authors may be contacted via: + * + * Jur van den Berg, Stephen J. Guy, Jamie Snape, Ming C. Lin, Dinesh Manocha + * Dept. of Computer Science + * 201 S. Columbia St. + * Frederick P. Brooks, Jr. Computer Science Bldg. + * Chapel Hill, N.C. 27599-3175 + * United States of America + * + * <http://gamma.cs.unc.edu/RVO2/> + */ + +#ifndef RVO2D_KD_TREE_H_ +#define RVO2D_KD_TREE_H_ + +/** + * \file KdTree2d.h + * \brief Contains the KdTree class. + */ + +#include "Definitions.h" + +namespace RVO2D { + /** + * \brief Defines <i>k</i>d-trees for agents and static obstacles in the + * simulation. + */ + class KdTree2D { + public: + /** + * \brief Defines an agent <i>k</i>d-tree node. + */ + class AgentTreeNode { + public: + /** + * \brief The beginning node number. + */ + size_t begin; + + /** + * \brief The ending node number. + */ + size_t end; + + /** + * \brief The left node number. + */ + size_t left; + + /** + * \brief The maximum x-coordinate. + */ + float maxX; + + /** + * \brief The maximum y-coordinate. + */ + float maxY; + + /** + * \brief The minimum x-coordinate. + */ + float minX; + + /** + * \brief The minimum y-coordinate. + */ + float minY; + + /** + * \brief The right node number. + */ + size_t right; + }; + + /** + * \brief Defines an obstacle <i>k</i>d-tree node. + */ + class ObstacleTreeNode { + public: + /** + * \brief The left obstacle tree node. + */ + ObstacleTreeNode *left; + + /** + * \brief The obstacle number. + */ + const Obstacle2D *obstacle; + + /** + * \brief The right obstacle tree node. + */ + ObstacleTreeNode *right; + }; + + /** + * \brief Constructs a <i>k</i>d-tree instance. + * \param sim The simulator instance. + */ + explicit KdTree2D(RVOSimulator2D *sim); + + /** + * \brief Destroys this kd-tree instance. + */ + ~KdTree2D(); + + /** + * \brief Builds an agent <i>k</i>d-tree. + */ + void buildAgentTree(std::vector<Agent2D *> agents); + + void buildAgentTreeRecursive(size_t begin, size_t end, size_t node); + + /** + * \brief Builds an obstacle <i>k</i>d-tree. + */ + void buildObstacleTree(std::vector<Obstacle2D *> obstacles); + + ObstacleTreeNode *buildObstacleTreeRecursive(const std::vector<Obstacle2D *> & + obstacles); + + /** + * \brief Computes the agent neighbors of the specified agent. + * \param agent A pointer to the agent for which agent + * neighbors are to be computed. + * \param rangeSq The squared range around the agent. + */ + void computeAgentNeighbors(Agent2D *agent, float &rangeSq) const; + + /** + * \brief Computes the obstacle neighbors of the specified agent. + * \param agent A pointer to the agent for which obstacle + * neighbors are to be computed. + * \param rangeSq The squared range around the agent. + */ + void computeObstacleNeighbors(Agent2D *agent, float rangeSq) const; + + /** + * \brief Deletes the specified obstacle tree node. + * \param node A pointer to the obstacle tree node to be + * deleted. + */ + void deleteObstacleTree(ObstacleTreeNode *node); + + void queryAgentTreeRecursive(Agent2D *agent, float &rangeSq, + size_t node) const; + + void queryObstacleTreeRecursive(Agent2D *agent, float rangeSq, + const ObstacleTreeNode *node) const; + + /** + * \brief Queries the visibility between two points within a + * specified radius. + * \param q1 The first point between which visibility is + * to be tested. + * \param q2 The second point between which visibility is + * to be tested. + * \param radius The radius within which visibility is to be + * tested. + * \return True if q1 and q2 are mutually visible within the radius; + * false otherwise. + */ + bool queryVisibility(const Vector2 &q1, const Vector2 &q2, + float radius) const; + + bool queryVisibilityRecursive(const Vector2 &q1, const Vector2 &q2, + float radius, + const ObstacleTreeNode *node) const; + + std::vector<Agent2D *> agents_; + std::vector<AgentTreeNode> agentTree_; + ObstacleTreeNode *obstacleTree_; + RVOSimulator2D *sim_; + + static const size_t MAX_LEAF_SIZE = 10; + + friend class Agent2D; + friend class RVOSimulator2D; + }; +} + +#endif /* RVO2D_KD_TREE_H_ */ diff --git a/thirdparty/rvo2/rvo2_2d/Obstacle2d.cpp b/thirdparty/rvo2/rvo2_2d/Obstacle2d.cpp new file mode 100644 index 0000000000..a80c8af136 --- /dev/null +++ b/thirdparty/rvo2/rvo2_2d/Obstacle2d.cpp @@ -0,0 +1,38 @@ +/* + * Obstacle2d.cpp + * RVO2 Library + * + * Copyright 2008 University of North Carolina at Chapel Hill + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Please send all bug reports to <geom@cs.unc.edu>. + * + * The authors may be contacted via: + * + * Jur van den Berg, Stephen J. Guy, Jamie Snape, Ming C. Lin, Dinesh Manocha + * Dept. of Computer Science + * 201 S. Columbia St. + * Frederick P. Brooks, Jr. Computer Science Bldg. + * Chapel Hill, N.C. 27599-3175 + * United States of America + * + * <http://gamma.cs.unc.edu/RVO2/> + */ + +#include "Obstacle2d.h" +#include "RVOSimulator2d.h" + +namespace RVO2D { + Obstacle2D::Obstacle2D() : isConvex_(false), nextObstacle_(NULL), prevObstacle_(NULL), id_(0) { } +} diff --git a/thirdparty/rvo2/rvo2_2d/Obstacle2d.h b/thirdparty/rvo2/rvo2_2d/Obstacle2d.h new file mode 100644 index 0000000000..9ba5937053 --- /dev/null +++ b/thirdparty/rvo2/rvo2_2d/Obstacle2d.h @@ -0,0 +1,72 @@ +/* + * Obstacle2d.h + * RVO2 Library + * + * Copyright 2008 University of North Carolina at Chapel Hill + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Please send all bug reports to <geom@cs.unc.edu>. + * + * The authors may be contacted via: + * + * Jur van den Berg, Stephen J. Guy, Jamie Snape, Ming C. Lin, Dinesh Manocha + * Dept. of Computer Science + * 201 S. Columbia St. + * Frederick P. Brooks, Jr. Computer Science Bldg. + * Chapel Hill, N.C. 27599-3175 + * United States of America + * + * <http://gamma.cs.unc.edu/RVO2/> + */ + +#ifndef RVO2D_OBSTACLE_H_ +#define RVO2D_OBSTACLE_H_ + +/** + * \file Obstacle2d.h + * \brief Contains the Obstacle class. + */ + +#include "Definitions.h" + +namespace RVO2D { + /** + * \brief Defines static obstacles in the simulation. + */ + class Obstacle2D { + public: + /** + * \brief Constructs a static obstacle instance. + */ + Obstacle2D(); + + bool isConvex_; + Obstacle2D *nextObstacle_; + Vector2 point_; + Obstacle2D *prevObstacle_; + Vector2 unitDir_; + + float height_ = 1.0; + float elevation_ = 0.0; + uint32_t avoidance_layers_ = 1; + + size_t id_; + + friend class Agent2D; + friend class KdTree2D; + friend class RVOSimulator2D; + }; +} + +#endif /* RVO2D_OBSTACLE_H_ */ diff --git a/thirdparty/rvo2/rvo2_2d/RVOSimulator2d.cpp b/thirdparty/rvo2/rvo2_2d/RVOSimulator2d.cpp new file mode 100644 index 0000000000..9fb1555ebc --- /dev/null +++ b/thirdparty/rvo2/rvo2_2d/RVOSimulator2d.cpp @@ -0,0 +1,363 @@ +/* + * RVOSimulator2d.cpp + * RVO2 Library + * + * Copyright 2008 University of North Carolina at Chapel Hill + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Please send all bug reports to <geom@cs.unc.edu>. + * + * The authors may be contacted via: + * + * Jur van den Berg, Stephen J. Guy, Jamie Snape, Ming C. Lin, Dinesh Manocha + * Dept. of Computer Science + * 201 S. Columbia St. + * Frederick P. Brooks, Jr. Computer Science Bldg. + * Chapel Hill, N.C. 27599-3175 + * United States of America + * + * <http://gamma.cs.unc.edu/RVO2/> + */ + +#include "RVOSimulator2d.h" + +#include "Agent2d.h" +#include "KdTree2d.h" +#include "Obstacle2d.h" + +#ifdef _OPENMP +#include <omp.h> +#endif + +namespace RVO2D { + RVOSimulator2D::RVOSimulator2D() : defaultAgent_(NULL), globalTime_(0.0f), kdTree_(NULL), timeStep_(0.0f) + { + kdTree_ = new KdTree2D(this); + } + + RVOSimulator2D::RVOSimulator2D(float timeStep, float neighborDist, size_t maxNeighbors, float timeHorizon, float timeHorizonObst, float radius, float maxSpeed, const Vector2 &velocity) : defaultAgent_(NULL), globalTime_(0.0f), kdTree_(NULL), timeStep_(timeStep) + { + kdTree_ = new KdTree2D(this); + defaultAgent_ = new Agent2D(); + + defaultAgent_->maxNeighbors_ = maxNeighbors; + defaultAgent_->maxSpeed_ = maxSpeed; + defaultAgent_->neighborDist_ = neighborDist; + defaultAgent_->radius_ = radius; + defaultAgent_->timeHorizon_ = timeHorizon; + defaultAgent_->timeHorizonObst_ = timeHorizonObst; + defaultAgent_->velocity_ = velocity; + } + + RVOSimulator2D::~RVOSimulator2D() + { + if (defaultAgent_ != NULL) { + delete defaultAgent_; + } + + for (size_t i = 0; i < agents_.size(); ++i) { + delete agents_[i]; + } + + for (size_t i = 0; i < obstacles_.size(); ++i) { + delete obstacles_[i]; + } + + delete kdTree_; + } + + size_t RVOSimulator2D::addAgent(const Vector2 &position) + { + if (defaultAgent_ == NULL) { + return RVO2D_ERROR; + } + + Agent2D *agent = new Agent2D(); + + agent->position_ = position; + agent->maxNeighbors_ = defaultAgent_->maxNeighbors_; + agent->maxSpeed_ = defaultAgent_->maxSpeed_; + agent->neighborDist_ = defaultAgent_->neighborDist_; + agent->radius_ = defaultAgent_->radius_; + agent->timeHorizon_ = defaultAgent_->timeHorizon_; + agent->timeHorizonObst_ = defaultAgent_->timeHorizonObst_; + agent->velocity_ = defaultAgent_->velocity_; + + agent->id_ = agents_.size(); + + agents_.push_back(agent); + + return agents_.size() - 1; + } + + size_t RVOSimulator2D::addAgent(const Vector2 &position, float neighborDist, size_t maxNeighbors, float timeHorizon, float timeHorizonObst, float radius, float maxSpeed, const Vector2 &velocity) + { + Agent2D *agent = new Agent2D(); + + agent->position_ = position; + agent->maxNeighbors_ = maxNeighbors; + agent->maxSpeed_ = maxSpeed; + agent->neighborDist_ = neighborDist; + agent->radius_ = radius; + agent->timeHorizon_ = timeHorizon; + agent->timeHorizonObst_ = timeHorizonObst; + agent->velocity_ = velocity; + + agent->id_ = agents_.size(); + + agents_.push_back(agent); + + return agents_.size() - 1; + } + + size_t RVOSimulator2D::addObstacle(const std::vector<Vector2> &vertices) + { + if (vertices.size() < 2) { + return RVO2D_ERROR; + } + + const size_t obstacleNo = obstacles_.size(); + + for (size_t i = 0; i < vertices.size(); ++i) { + Obstacle2D *obstacle = new Obstacle2D(); + obstacle->point_ = vertices[i]; + + if (i != 0) { + obstacle->prevObstacle_ = obstacles_.back(); + obstacle->prevObstacle_->nextObstacle_ = obstacle; + } + + if (i == vertices.size() - 1) { + obstacle->nextObstacle_ = obstacles_[obstacleNo]; + obstacle->nextObstacle_->prevObstacle_ = obstacle; + } + + obstacle->unitDir_ = normalize(vertices[(i == vertices.size() - 1 ? 0 : i + 1)] - vertices[i]); + + if (vertices.size() == 2) { + obstacle->isConvex_ = true; + } + else { + obstacle->isConvex_ = (leftOf(vertices[(i == 0 ? vertices.size() - 1 : i - 1)], vertices[i], vertices[(i == vertices.size() - 1 ? 0 : i + 1)]) >= 0.0f); + } + + obstacle->id_ = obstacles_.size(); + + obstacles_.push_back(obstacle); + } + + return obstacleNo; + } + + void RVOSimulator2D::doStep() + { + kdTree_->buildAgentTree(agents_); + + for (int i = 0; i < static_cast<int>(agents_.size()); ++i) { + agents_[i]->computeNeighbors(this); + agents_[i]->computeNewVelocity(this); + } + + for (int i = 0; i < static_cast<int>(agents_.size()); ++i) { + agents_[i]->update(this); + } + + globalTime_ += timeStep_; + } + + size_t RVOSimulator2D::getAgentAgentNeighbor(size_t agentNo, size_t neighborNo) const + { + return agents_[agentNo]->agentNeighbors_[neighborNo].second->id_; + } + + size_t RVOSimulator2D::getAgentMaxNeighbors(size_t agentNo) const + { + return agents_[agentNo]->maxNeighbors_; + } + + float RVOSimulator2D::getAgentMaxSpeed(size_t agentNo) const + { + return agents_[agentNo]->maxSpeed_; + } + + float RVOSimulator2D::getAgentNeighborDist(size_t agentNo) const + { + return agents_[agentNo]->neighborDist_; + } + + size_t RVOSimulator2D::getAgentNumAgentNeighbors(size_t agentNo) const + { + return agents_[agentNo]->agentNeighbors_.size(); + } + + size_t RVOSimulator2D::getAgentNumObstacleNeighbors(size_t agentNo) const + { + return agents_[agentNo]->obstacleNeighbors_.size(); + } + + size_t RVOSimulator2D::getAgentNumORCALines(size_t agentNo) const + { + return agents_[agentNo]->orcaLines_.size(); + } + + size_t RVOSimulator2D::getAgentObstacleNeighbor(size_t agentNo, size_t neighborNo) const + { + return agents_[agentNo]->obstacleNeighbors_[neighborNo].second->id_; + } + + const Line &RVOSimulator2D::getAgentORCALine(size_t agentNo, size_t lineNo) const + { + return agents_[agentNo]->orcaLines_[lineNo]; + } + + const Vector2 &RVOSimulator2D::getAgentPosition(size_t agentNo) const + { + return agents_[agentNo]->position_; + } + + const Vector2 &RVOSimulator2D::getAgentPrefVelocity(size_t agentNo) const + { + return agents_[agentNo]->prefVelocity_; + } + + float RVOSimulator2D::getAgentRadius(size_t agentNo) const + { + return agents_[agentNo]->radius_; + } + + float RVOSimulator2D::getAgentTimeHorizon(size_t agentNo) const + { + return agents_[agentNo]->timeHorizon_; + } + + float RVOSimulator2D::getAgentTimeHorizonObst(size_t agentNo) const + { + return agents_[agentNo]->timeHorizonObst_; + } + + const Vector2 &RVOSimulator2D::getAgentVelocity(size_t agentNo) const + { + return agents_[agentNo]->velocity_; + } + + float RVOSimulator2D::getGlobalTime() const + { + return globalTime_; + } + + size_t RVOSimulator2D::getNumAgents() const + { + return agents_.size(); + } + + size_t RVOSimulator2D::getNumObstacleVertices() const + { + return obstacles_.size(); + } + + const Vector2 &RVOSimulator2D::getObstacleVertex(size_t vertexNo) const + { + return obstacles_[vertexNo]->point_; + } + + size_t RVOSimulator2D::getNextObstacleVertexNo(size_t vertexNo) const + { + return obstacles_[vertexNo]->nextObstacle_->id_; + } + + size_t RVOSimulator2D::getPrevObstacleVertexNo(size_t vertexNo) const + { + return obstacles_[vertexNo]->prevObstacle_->id_; + } + + float RVOSimulator2D::getTimeStep() const + { + return timeStep_; + } + + void RVOSimulator2D::processObstacles() + { + kdTree_->buildObstacleTree(obstacles_); + } + + bool RVOSimulator2D::queryVisibility(const Vector2 &point1, const Vector2 &point2, float radius) const + { + return kdTree_->queryVisibility(point1, point2, radius); + } + + void RVOSimulator2D::setAgentDefaults(float neighborDist, size_t maxNeighbors, float timeHorizon, float timeHorizonObst, float radius, float maxSpeed, const Vector2 &velocity) + { + if (defaultAgent_ == NULL) { + defaultAgent_ = new Agent2D(); + } + + defaultAgent_->maxNeighbors_ = maxNeighbors; + defaultAgent_->maxSpeed_ = maxSpeed; + defaultAgent_->neighborDist_ = neighborDist; + defaultAgent_->radius_ = radius; + defaultAgent_->timeHorizon_ = timeHorizon; + defaultAgent_->timeHorizonObst_ = timeHorizonObst; + defaultAgent_->velocity_ = velocity; + } + + void RVOSimulator2D::setAgentMaxNeighbors(size_t agentNo, size_t maxNeighbors) + { + agents_[agentNo]->maxNeighbors_ = maxNeighbors; + } + + void RVOSimulator2D::setAgentMaxSpeed(size_t agentNo, float maxSpeed) + { + agents_[agentNo]->maxSpeed_ = maxSpeed; + } + + void RVOSimulator2D::setAgentNeighborDist(size_t agentNo, float neighborDist) + { + agents_[agentNo]->neighborDist_ = neighborDist; + } + + void RVOSimulator2D::setAgentPosition(size_t agentNo, const Vector2 &position) + { + agents_[agentNo]->position_ = position; + } + + void RVOSimulator2D::setAgentPrefVelocity(size_t agentNo, const Vector2 &prefVelocity) + { + agents_[agentNo]->prefVelocity_ = prefVelocity; + } + + void RVOSimulator2D::setAgentRadius(size_t agentNo, float radius) + { + agents_[agentNo]->radius_ = radius; + } + + void RVOSimulator2D::setAgentTimeHorizon(size_t agentNo, float timeHorizon) + { + agents_[agentNo]->timeHorizon_ = timeHorizon; + } + + void RVOSimulator2D::setAgentTimeHorizonObst(size_t agentNo, float timeHorizonObst) + { + agents_[agentNo]->timeHorizonObst_ = timeHorizonObst; + } + + void RVOSimulator2D::setAgentVelocity(size_t agentNo, const Vector2 &velocity) + { + agents_[agentNo]->velocity_ = velocity; + } + + void RVOSimulator2D::setTimeStep(float timeStep) + { + timeStep_ = timeStep; + } +} diff --git a/thirdparty/rvo2/rvo2_2d/RVOSimulator2d.h b/thirdparty/rvo2/rvo2_2d/RVOSimulator2d.h new file mode 100644 index 0000000000..e074e0fe0e --- /dev/null +++ b/thirdparty/rvo2/rvo2_2d/RVOSimulator2d.h @@ -0,0 +1,592 @@ +/* + * RVOSimulator2d.h + * RVO2 Library + * + * Copyright 2008 University of North Carolina at Chapel Hill + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Please send all bug reports to <geom@cs.unc.edu>. + * + * The authors may be contacted via: + * + * Jur van den Berg, Stephen J. Guy, Jamie Snape, Ming C. Lin, Dinesh Manocha + * Dept. of Computer Science + * 201 S. Columbia St. + * Frederick P. Brooks, Jr. Computer Science Bldg. + * Chapel Hill, N.C. 27599-3175 + * United States of America + * + * <http://gamma.cs.unc.edu/RVO2/> + */ + +#ifndef RVO2D_RVO_SIMULATOR_H_ +#define RVO2D_RVO_SIMULATOR_H_ + +/** + * \file RVOSimulator2d.h + * \brief Contains the RVOSimulator2D class. + */ + +#include <cstddef> +#include <limits> +#include <vector> + +#include "Vector2.h" + +namespace RVO2D { + /** + * \brief Error value. + * + * A value equal to the largest unsigned integer that is returned in case + * of an error by functions in RVO2D::RVOSimulator2D. + */ + const size_t RVO2D_ERROR = std::numeric_limits<size_t>::max(); + + /** + * \brief Defines a directed line. + */ + class Line { + public: + /** + * \brief A point on the directed line. + */ + Vector2 point; + + /** + * \brief The direction of the directed line. + */ + Vector2 direction; + }; + + class Agent2D; + class KdTree2D; + class Obstacle2D; + + /** + * \brief Defines the simulation. + * + * The main class of the library that contains all simulation functionality. + */ + class RVOSimulator2D { + public: + /** + * \brief Constructs a simulator instance. + */ + RVOSimulator2D(); + + /** + * \brief Constructs a simulator instance and sets the default + * properties for any new agent that is added. + * \param timeStep The time step of the simulation. + * Must be positive. + * \param neighborDist The default maximum distance (center point + * to center point) to other agents a new agent + * takes into account in the navigation. The + * larger this number, the longer he running + * time of the simulation. If the number is too + * low, the simulation will not be safe. Must be + * non-negative. + * \param maxNeighbors The default maximum number of other agents a + * new agent takes into account in the + * navigation. The larger this number, the + * longer the running time of the simulation. + * If the number is too low, the simulation + * will not be safe. + * \param timeHorizon The default minimal amount of time for which + * a new agent's velocities that are computed + * by the simulation are safe with respect to + * other agents. The larger this number, the + * sooner an agent will respond to the presence + * of other agents, but the less freedom the + * agent has in choosing its velocities. + * Must be positive. + * \param timeHorizonObst The default minimal amount of time for which + * a new agent's velocities that are computed + * by the simulation are safe with respect to + * obstacles. The larger this number, the + * sooner an agent will respond to the presence + * of obstacles, but the less freedom the agent + * has in choosing its velocities. + * Must be positive. + * \param radius The default radius of a new agent. + * Must be non-negative. + * \param maxSpeed The default maximum speed of a new agent. + * Must be non-negative. + * \param velocity The default initial two-dimensional linear + * velocity of a new agent (optional). + */ + RVOSimulator2D(float timeStep, float neighborDist, size_t maxNeighbors, + float timeHorizon, float timeHorizonObst, float radius, + float maxSpeed, const Vector2 &velocity = Vector2()); + + /** + * \brief Destroys this simulator instance. + */ + ~RVOSimulator2D(); + + /** + * \brief Adds a new agent with default properties to the + * simulation. + * \param position The two-dimensional starting position of + * this agent. + * \return The number of the agent, or RVO2D::RVO2D_ERROR when the agent + * defaults have not been set. + */ + size_t addAgent(const Vector2 &position); + + /** + * \brief Adds a new agent to the simulation. + * \param position The two-dimensional starting position of + * this agent. + * \param neighborDist The maximum distance (center point to + * center point) to other agents this agent + * takes into account in the navigation. The + * larger this number, the longer the running + * time of the simulation. If the number is too + * low, the simulation will not be safe. + * Must be non-negative. + * \param maxNeighbors The maximum number of other agents this + * agent takes into account in the navigation. + * The larger this number, the longer the + * running time of the simulation. If the + * number is too low, the simulation will not + * be safe. + * \param timeHorizon The minimal amount of time for which this + * agent's velocities that are computed by the + * simulation are safe with respect to other + * agents. The larger this number, the sooner + * this agent will respond to the presence of + * other agents, but the less freedom this + * agent has in choosing its velocities. + * Must be positive. + * \param timeHorizonObst The minimal amount of time for which this + * agent's velocities that are computed by the + * simulation are safe with respect to + * obstacles. The larger this number, the + * sooner this agent will respond to the + * presence of obstacles, but the less freedom + * this agent has in choosing its velocities. + * Must be positive. + * \param radius The radius of this agent. + * Must be non-negative. + * \param maxSpeed The maximum speed of this agent. + * Must be non-negative. + * \param velocity The initial two-dimensional linear velocity + * of this agent (optional). + * \return The number of the agent. + */ + size_t addAgent(const Vector2 &position, float neighborDist, + size_t maxNeighbors, float timeHorizon, + float timeHorizonObst, float radius, float maxSpeed, + const Vector2 &velocity = Vector2()); + + /** + * \brief Adds a new obstacle to the simulation. + * \param vertices List of the vertices of the polygonal + * obstacle in counterclockwise order. + * \return The number of the first vertex of the obstacle, + * or RVO2D::RVO2D_ERROR when the number of vertices is less than two. + * \note To add a "negative" obstacle, e.g. a bounding polygon around + * the environment, the vertices should be listed in clockwise + * order. + */ + size_t addObstacle(const std::vector<Vector2> &vertices); + + /** + * \brief Lets the simulator perform a simulation step and updates the + * two-dimensional position and two-dimensional velocity of + * each agent. + */ + void doStep(); + + /** + * \brief Returns the specified agent neighbor of the specified + * agent. + * \param agentNo The number of the agent whose agent + * neighbor is to be retrieved. + * \param neighborNo The number of the agent neighbor to be + * retrieved. + * \return The number of the neighboring agent. + */ + size_t getAgentAgentNeighbor(size_t agentNo, size_t neighborNo) const; + + /** + * \brief Returns the maximum neighbor count of a specified agent. + * \param agentNo The number of the agent whose maximum + * neighbor count is to be retrieved. + * \return The present maximum neighbor count of the agent. + */ + size_t getAgentMaxNeighbors(size_t agentNo) const; + + /** + * \brief Returns the maximum speed of a specified agent. + * \param agentNo The number of the agent whose maximum speed + * is to be retrieved. + * \return The present maximum speed of the agent. + */ + float getAgentMaxSpeed(size_t agentNo) const; + + /** + * \brief Returns the maximum neighbor distance of a specified + * agent. + * \param agentNo The number of the agent whose maximum + * neighbor distance is to be retrieved. + * \return The present maximum neighbor distance of the agent. + */ + float getAgentNeighborDist(size_t agentNo) const; + + /** + * \brief Returns the count of agent neighbors taken into account to + * compute the current velocity for the specified agent. + * \param agentNo The number of the agent whose count of agent + * neighbors is to be retrieved. + * \return The count of agent neighbors taken into account to compute + * the current velocity for the specified agent. + */ + size_t getAgentNumAgentNeighbors(size_t agentNo) const; + + /** + * \brief Returns the count of obstacle neighbors taken into account + * to compute the current velocity for the specified agent. + * \param agentNo The number of the agent whose count of + * obstacle neighbors is to be retrieved. + * \return The count of obstacle neighbors taken into account to + * compute the current velocity for the specified agent. + */ + size_t getAgentNumObstacleNeighbors(size_t agentNo) const; + + + /** + * \brief Returns the count of ORCA constraints used to compute + * the current velocity for the specified agent. + * \param agentNo The number of the agent whose count of ORCA + * constraints is to be retrieved. + * \return The count of ORCA constraints used to compute the current + * velocity for the specified agent. + */ + size_t getAgentNumORCALines(size_t agentNo) const; + + /** + * \brief Returns the specified obstacle neighbor of the specified + * agent. + * \param agentNo The number of the agent whose obstacle + * neighbor is to be retrieved. + * \param neighborNo The number of the obstacle neighbor to be + * retrieved. + * \return The number of the first vertex of the neighboring obstacle + * edge. + */ + size_t getAgentObstacleNeighbor(size_t agentNo, size_t neighborNo) const; + + /** + * \brief Returns the specified ORCA constraint of the specified + * agent. + * \param agentNo The number of the agent whose ORCA + * constraint is to be retrieved. + * \param lineNo The number of the ORCA constraint to be + * retrieved. + * \return A line representing the specified ORCA constraint. + * \note The halfplane to the left of the line is the region of + * permissible velocities with respect to the specified + * ORCA constraint. + */ + const Line &getAgentORCALine(size_t agentNo, size_t lineNo) const; + + /** + * \brief Returns the two-dimensional position of a specified + * agent. + * \param agentNo The number of the agent whose + * two-dimensional position is to be retrieved. + * \return The present two-dimensional position of the (center of the) + * agent. + */ + const Vector2 &getAgentPosition(size_t agentNo) const; + + /** + * \brief Returns the two-dimensional preferred velocity of a + * specified agent. + * \param agentNo The number of the agent whose + * two-dimensional preferred velocity is to be + * retrieved. + * \return The present two-dimensional preferred velocity of the agent. + */ + const Vector2 &getAgentPrefVelocity(size_t agentNo) const; + + /** + * \brief Returns the radius of a specified agent. + * \param agentNo The number of the agent whose radius is to + * be retrieved. + * \return The present radius of the agent. + */ + float getAgentRadius(size_t agentNo) const; + + /** + * \brief Returns the time horizon of a specified agent. + * \param agentNo The number of the agent whose time horizon + * is to be retrieved. + * \return The present time horizon of the agent. + */ + float getAgentTimeHorizon(size_t agentNo) const; + + /** + * \brief Returns the time horizon with respect to obstacles of a + * specified agent. + * \param agentNo The number of the agent whose time horizon + * with respect to obstacles is to be + * retrieved. + * \return The present time horizon with respect to obstacles of the + * agent. + */ + float getAgentTimeHorizonObst(size_t agentNo) const; + + /** + * \brief Returns the two-dimensional linear velocity of a + * specified agent. + * \param agentNo The number of the agent whose + * two-dimensional linear velocity is to be + * retrieved. + * \return The present two-dimensional linear velocity of the agent. + */ + const Vector2 &getAgentVelocity(size_t agentNo) const; + + /** + * \brief Returns the global time of the simulation. + * \return The present global time of the simulation (zero initially). + */ + float getGlobalTime() const; + + /** + * \brief Returns the count of agents in the simulation. + * \return The count of agents in the simulation. + */ + size_t getNumAgents() const; + + /** + * \brief Returns the count of obstacle vertices in the simulation. + * \return The count of obstacle vertices in the simulation. + */ + size_t getNumObstacleVertices() const; + + /** + * \brief Returns the two-dimensional position of a specified obstacle + * vertex. + * \param vertexNo The number of the obstacle vertex to be + * retrieved. + * \return The two-dimensional position of the specified obstacle + * vertex. + */ + const Vector2 &getObstacleVertex(size_t vertexNo) const; + + /** + * \brief Returns the number of the obstacle vertex succeeding the + * specified obstacle vertex in its polygon. + * \param vertexNo The number of the obstacle vertex whose + * successor is to be retrieved. + * \return The number of the obstacle vertex succeeding the specified + * obstacle vertex in its polygon. + */ + size_t getNextObstacleVertexNo(size_t vertexNo) const; + + /** + * \brief Returns the number of the obstacle vertex preceding the + * specified obstacle vertex in its polygon. + * \param vertexNo The number of the obstacle vertex whose + * predecessor is to be retrieved. + * \return The number of the obstacle vertex preceding the specified + * obstacle vertex in its polygon. + */ + size_t getPrevObstacleVertexNo(size_t vertexNo) const; + + /** + * \brief Returns the time step of the simulation. + * \return The present time step of the simulation. + */ + float getTimeStep() const; + + /** + * \brief Processes the obstacles that have been added so that they + * are accounted for in the simulation. + * \note Obstacles added to the simulation after this function has + * been called are not accounted for in the simulation. + */ + void processObstacles(); + + /** + * \brief Performs a visibility query between the two specified + * points with respect to the obstacles + * \param point1 The first point of the query. + * \param point2 The second point of the query. + * \param radius The minimal distance between the line + * connecting the two points and the obstacles + * in order for the points to be mutually + * visible (optional). Must be non-negative. + * \return A boolean specifying whether the two points are mutually + * visible. Returns true when the obstacles have not been + * processed. + */ + bool queryVisibility(const Vector2 &point1, const Vector2 &point2, + float radius = 0.0f) const; + + /** + * \brief Sets the default properties for any new agent that is + * added. + * \param neighborDist The default maximum distance (center point + * to center point) to other agents a new agent + * takes into account in the navigation. The + * larger this number, the longer he running + * time of the simulation. If the number is too + * low, the simulation will not be safe. + * Must be non-negative. + * \param maxNeighbors The default maximum number of other agents a + * new agent takes into account in the + * navigation. The larger this number, the + * longer the running time of the simulation. + * If the number is too low, the simulation + * will not be safe. + * \param timeHorizon The default minimal amount of time for which + * a new agent's velocities that are computed + * by the simulation are safe with respect to + * other agents. The larger this number, the + * sooner an agent will respond to the presence + * of other agents, but the less freedom the + * agent has in choosing its velocities. + * Must be positive. + * \param timeHorizonObst The default minimal amount of time for which + * a new agent's velocities that are computed + * by the simulation are safe with respect to + * obstacles. The larger this number, the + * sooner an agent will respond to the presence + * of obstacles, but the less freedom the agent + * has in choosing its velocities. + * Must be positive. + * \param radius The default radius of a new agent. + * Must be non-negative. + * \param maxSpeed The default maximum speed of a new agent. + * Must be non-negative. + * \param velocity The default initial two-dimensional linear + * velocity of a new agent (optional). + */ + void setAgentDefaults(float neighborDist, size_t maxNeighbors, + float timeHorizon, float timeHorizonObst, + float radius, float maxSpeed, + const Vector2 &velocity = Vector2()); + + /** + * \brief Sets the maximum neighbor count of a specified agent. + * \param agentNo The number of the agent whose maximum + * neighbor count is to be modified. + * \param maxNeighbors The replacement maximum neighbor count. + */ + void setAgentMaxNeighbors(size_t agentNo, size_t maxNeighbors); + + /** + * \brief Sets the maximum speed of a specified agent. + * \param agentNo The number of the agent whose maximum speed + * is to be modified. + * \param maxSpeed The replacement maximum speed. Must be + * non-negative. + */ + void setAgentMaxSpeed(size_t agentNo, float maxSpeed); + + /** + * \brief Sets the maximum neighbor distance of a specified agent. + * \param agentNo The number of the agent whose maximum + * neighbor distance is to be modified. + * \param neighborDist The replacement maximum neighbor distance. + * Must be non-negative. + */ + void setAgentNeighborDist(size_t agentNo, float neighborDist); + + /** + * \brief Sets the two-dimensional position of a specified agent. + * \param agentNo The number of the agent whose + * two-dimensional position is to be modified. + * \param position The replacement of the two-dimensional + * position. + */ + void setAgentPosition(size_t agentNo, const Vector2 &position); + + /** + * \brief Sets the two-dimensional preferred velocity of a + * specified agent. + * \param agentNo The number of the agent whose + * two-dimensional preferred velocity is to be + * modified. + * \param prefVelocity The replacement of the two-dimensional + * preferred velocity. + */ + void setAgentPrefVelocity(size_t agentNo, const Vector2 &prefVelocity); + + /** + * \brief Sets the radius of a specified agent. + * \param agentNo The number of the agent whose radius is to + * be modified. + * \param radius The replacement radius. + * Must be non-negative. + */ + void setAgentRadius(size_t agentNo, float radius); + + /** + * \brief Sets the time horizon of a specified agent with respect + * to other agents. + * \param agentNo The number of the agent whose time horizon + * is to be modified. + * \param timeHorizon The replacement time horizon with respect + * to other agents. Must be positive. + */ + void setAgentTimeHorizon(size_t agentNo, float timeHorizon); + + /** + * \brief Sets the time horizon of a specified agent with respect + * to obstacles. + * \param agentNo The number of the agent whose time horizon + * with respect to obstacles is to be modified. + * \param timeHorizonObst The replacement time horizon with respect to + * obstacles. Must be positive. + */ + void setAgentTimeHorizonObst(size_t agentNo, float timeHorizonObst); + + /** + * \brief Sets the two-dimensional linear velocity of a specified + * agent. + * \param agentNo The number of the agent whose + * two-dimensional linear velocity is to be + * modified. + * \param velocity The replacement two-dimensional linear + * velocity. + */ + void setAgentVelocity(size_t agentNo, const Vector2 &velocity); + + /** + * \brief Sets the time step of the simulation. + * \param timeStep The time step of the simulation. + * Must be positive. + */ + void setTimeStep(float timeStep); + + public: + std::vector<Agent2D *> agents_; + Agent2D *defaultAgent_; + float globalTime_; + KdTree2D *kdTree_; + std::vector<Obstacle2D *> obstacles_; + float timeStep_; + + friend class Agent2D; + friend class KdTree2D; + friend class Obstacle2D; + }; +} + +#endif /* RVO2D_RVO_SIMULATOR_H_ */ diff --git a/thirdparty/rvo2/rvo2_2d/Vector2.h b/thirdparty/rvo2/rvo2_2d/Vector2.h new file mode 100644 index 0000000000..24353e09f3 --- /dev/null +++ b/thirdparty/rvo2/rvo2_2d/Vector2.h @@ -0,0 +1,346 @@ +/* + * Vector2.h + * RVO2 Library + * + * Copyright 2008 University of North Carolina at Chapel Hill + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Please send all bug reports to <geom@cs.unc.edu>. + * + * The authors may be contacted via: + * + * Jur van den Berg, Stephen J. Guy, Jamie Snape, Ming C. Lin, Dinesh Manocha + * Dept. of Computer Science + * 201 S. Columbia St. + * Frederick P. Brooks, Jr. Computer Science Bldg. + * Chapel Hill, N.C. 27599-3175 + * United States of America + * + * <http://gamma.cs.unc.edu/RVO2/> + */ + +#ifndef RVO_VECTOR2_H_ +#define RVO_VECTOR2_H_ + +/** + * \file Vector2.h + * \brief Contains the Vector2 class. + */ + +#include <cmath> +#include <ostream> + +namespace RVO2D { + /** + * \brief Defines a two-dimensional vector. + */ + class Vector2 { + public: + /** + * \brief Constructs and initializes a two-dimensional vector instance + * to (0.0, 0.0). + */ + inline Vector2() : x_(0.0f), y_(0.0f) { } + + /** + * \brief Constructs and initializes a two-dimensional vector from + * the specified xy-coordinates. + * \param x The x-coordinate of the two-dimensional + * vector. + * \param y The y-coordinate of the two-dimensional + * vector. + */ + inline Vector2(float x, float y) : x_(x), y_(y) { } + + inline Vector2(const Vector2 &vector) + { + x_ = vector.x(); + y_ = vector.y(); + } + + /** + * \brief Returns the x-coordinate of this two-dimensional vector. + * \return The x-coordinate of the two-dimensional vector. + */ + inline float x() const { return x_; } + + /** + * \brief Returns the y-coordinate of this two-dimensional vector. + * \return The y-coordinate of the two-dimensional vector. + */ + inline float y() const { return y_; } + + /** + * \brief Computes the negation of this two-dimensional vector. + * \return The negation of this two-dimensional vector. + */ + inline Vector2 operator-() const + { + return Vector2(-x_, -y_); + } + + /** + * \brief Computes the dot product of this two-dimensional vector with + * the specified two-dimensional vector. + * \param vector The two-dimensional vector with which the + * dot product should be computed. + * \return The dot product of this two-dimensional vector with a + * specified two-dimensional vector. + */ + inline float operator*(const Vector2 &vector) const + { + return x_ * vector.x() + y_ * vector.y(); + } + + /** + * \brief Computes the scalar multiplication of this + * two-dimensional vector with the specified scalar value. + * \param s The scalar value with which the scalar + * multiplication should be computed. + * \return The scalar multiplication of this two-dimensional vector + * with a specified scalar value. + */ + inline Vector2 operator*(float s) const + { + return Vector2(x_ * s, y_ * s); + } + + /** + * \brief Computes the scalar division of this two-dimensional vector + * with the specified scalar value. + * \param s The scalar value with which the scalar + * division should be computed. + * \return The scalar division of this two-dimensional vector with a + * specified scalar value. + */ + inline Vector2 operator/(float s) const + { + const float invS = 1.0f / s; + + return Vector2(x_ * invS, y_ * invS); + } + + /** + * \brief Computes the vector sum of this two-dimensional vector with + * the specified two-dimensional vector. + * \param vector The two-dimensional vector with which the + * vector sum should be computed. + * \return The vector sum of this two-dimensional vector with a + * specified two-dimensional vector. + */ + inline Vector2 operator+(const Vector2 &vector) const + { + return Vector2(x_ + vector.x(), y_ + vector.y()); + } + + /** + * \brief Computes the vector difference of this two-dimensional + * vector with the specified two-dimensional vector. + * \param vector The two-dimensional vector with which the + * vector difference should be computed. + * \return The vector difference of this two-dimensional vector with a + * specified two-dimensional vector. + */ + inline Vector2 operator-(const Vector2 &vector) const + { + return Vector2(x_ - vector.x(), y_ - vector.y()); + } + + /** + * \brief Tests this two-dimensional vector for equality with the + * specified two-dimensional vector. + * \param vector The two-dimensional vector with which to + * test for equality. + * \return True if the two-dimensional vectors are equal. + */ + inline bool operator==(const Vector2 &vector) const + { + return x_ == vector.x() && y_ == vector.y(); + } + + /** + * \brief Tests this two-dimensional vector for inequality with the + * specified two-dimensional vector. + * \param vector The two-dimensional vector with which to + * test for inequality. + * \return True if the two-dimensional vectors are not equal. + */ + inline bool operator!=(const Vector2 &vector) const + { + return x_ != vector.x() || y_ != vector.y(); + } + + /** + * \brief Sets the value of this two-dimensional vector to the scalar + * multiplication of itself with the specified scalar value. + * \param s The scalar value with which the scalar + * multiplication should be computed. + * \return A reference to this two-dimensional vector. + */ + inline Vector2 &operator*=(float s) + { + x_ *= s; + y_ *= s; + + return *this; + } + + /** + * \brief Sets the value of this two-dimensional vector to the scalar + * division of itself with the specified scalar value. + * \param s The scalar value with which the scalar + * division should be computed. + * \return A reference to this two-dimensional vector. + */ + inline Vector2 &operator/=(float s) + { + const float invS = 1.0f / s; + x_ *= invS; + y_ *= invS; + + return *this; + } + + /** + * \brief Sets the value of this two-dimensional vector to the vector + * sum of itself with the specified two-dimensional vector. + * \param vector The two-dimensional vector with which the + * vector sum should be computed. + * \return A reference to this two-dimensional vector. + */ + inline Vector2 &operator+=(const Vector2 &vector) + { + x_ += vector.x(); + y_ += vector.y(); + + return *this; + } + + /** + * \brief Sets the value of this two-dimensional vector to the vector + * difference of itself with the specified two-dimensional + * vector. + * \param vector The two-dimensional vector with which the + * vector difference should be computed. + * \return A reference to this two-dimensional vector. + */ + inline Vector2 &operator-=(const Vector2 &vector) + { + x_ -= vector.x(); + y_ -= vector.y(); + + return *this; + } + + inline Vector2 &operator=(const Vector2 &vector) + { + x_ = vector.x(); + y_ = vector.y(); + + return *this; + } + + private: + float x_; + float y_; + }; + + /** + * \relates Vector2 + * \brief Computes the scalar multiplication of the specified + * two-dimensional vector with the specified scalar value. + * \param s The scalar value with which the scalar + * multiplication should be computed. + * \param vector The two-dimensional vector with which the scalar + * multiplication should be computed. + * \return The scalar multiplication of the two-dimensional vector with the + * scalar value. + */ + inline Vector2 operator*(float s, const Vector2 &vector) + { + return Vector2(s * vector.x(), s * vector.y()); + } + + /** + * \relates Vector2 + * \brief Inserts the specified two-dimensional vector into the specified + * output stream. + * \param os The output stream into which the two-dimensional + * vector should be inserted. + * \param vector The two-dimensional vector which to insert into + * the output stream. + * \return A reference to the output stream. + */ + inline std::ostream &operator<<(std::ostream &os, const Vector2 &vector) + { + os << "(" << vector.x() << "," << vector.y() << ")"; + + return os; + } + + /** + * \relates Vector2 + * \brief Computes the length of a specified two-dimensional vector. + * \param vector The two-dimensional vector whose length is to be + * computed. + * \return The length of the two-dimensional vector. + */ + inline float abs(const Vector2 &vector) + { + return std::sqrt(vector * vector); + } + + /** + * \relates Vector2 + * \brief Computes the squared length of a specified two-dimensional + * vector. + * \param vector The two-dimensional vector whose squared length + * is to be computed. + * \return The squared length of the two-dimensional vector. + */ + inline float absSq(const Vector2 &vector) + { + return vector * vector; + } + + /** + * \relates Vector2 + * \brief Computes the determinant of a two-dimensional square matrix with + * rows consisting of the specified two-dimensional vectors. + * \param vector1 The top row of the two-dimensional square + * matrix. + * \param vector2 The bottom row of the two-dimensional square + * matrix. + * \return The determinant of the two-dimensional square matrix. + */ + inline float det(const Vector2 &vector1, const Vector2 &vector2) + { + return vector1.x() * vector2.y() - vector1.y() * vector2.x(); + } + + /** + * \relates Vector2 + * \brief Computes the normalization of the specified two-dimensional + * vector. + * \param vector The two-dimensional vector whose normalization + * is to be computed. + * \return The normalization of the two-dimensional vector. + */ + inline Vector2 normalize(const Vector2 &vector) + { + return vector / abs(vector); + } +} + +#endif /* RVO_VECTOR2_H_ */ diff --git a/thirdparty/rvo2/Agent.cpp b/thirdparty/rvo2/rvo2_3d/Agent3d.cpp index b35eee9c12..bddf226db1 100644 --- a/thirdparty/rvo2/Agent.cpp +++ b/thirdparty/rvo2/rvo2_3d/Agent3d.cpp @@ -30,15 +30,15 @@ * <https://gamma.cs.unc.edu/RVO2/> */ -#include "Agent.h" +#include "Agent3d.h" #include <cmath> #include <algorithm> #include "Definitions.h" -#include "KdTree.h" +#include "KdTree3d.h" -namespace RVO { +namespace RVO3D { /** * \brief A sufficiently small positive number. */ @@ -47,7 +47,7 @@ namespace RVO { /** * \brief Defines a directed line. */ - class Line { + class Line3D { public: /** * \brief The direction of the directed line. @@ -71,7 +71,7 @@ namespace RVO { * \param result A reference to the result of the linear program. * \return True if successful. */ - bool linearProgram1(const std::vector<Plane> &planes, size_t planeNo, const Line &line, float radius, const Vector3 &optVelocity, bool directionOpt, Vector3 &result); + bool linearProgram1(const std::vector<Plane> &planes, size_t planeNo, const Line3D &line, float radius, const Vector3 &optVelocity, bool directionOpt, Vector3 &result); /** * \brief Solves a two-dimensional linear program on a specified plane subject to linear constraints defined by planes and a spherical constraint. @@ -105,42 +105,34 @@ namespace RVO { */ void linearProgram4(const std::vector<Plane> &planes, size_t beginPlane, float radius, Vector3 &result); - Agent::Agent() : id_(0), maxNeighbors_(0), maxSpeed_(0.0f), neighborDist_(0.0f), radius_(0.0f), timeHorizon_(0.0f), ignore_y_(false) { } + Agent3D::Agent3D() : id_(0), maxNeighbors_(0), maxSpeed_(0.0f), neighborDist_(0.0f), radius_(0.0f), timeHorizon_(0.0f) { } - void Agent::computeNeighbors(KdTree *kdTree_) + void Agent3D::computeNeighbors(RVOSimulator3D *sim_) { agentNeighbors_.clear(); + if (maxNeighbors_ > 0) { - kdTree_->computeAgentNeighbors(this, neighborDist_ * neighborDist_); + sim_->kdTree_->computeAgentNeighbors(this, neighborDist_ * neighborDist_); } } - void Agent::computeNewVelocity(float timeStep) + void Agent3D::computeNewVelocity(RVOSimulator3D *sim_) { orcaPlanes_.clear(); + const float invTimeHorizon = 1.0f / timeHorizon_; /* Create agent ORCA planes. */ for (size_t i = 0; i < agentNeighbors_.size(); ++i) { - const Agent *const other = agentNeighbors_[i].second; - - Vector3 relativePosition = other->position_ - position_; - Vector3 relativeVelocity = velocity_ - other->velocity_; - const float combinedRadius = radius_ + other->radius_; + const Agent3D *const other = agentNeighbors_[i].second; - // This is a Godot feature that allow the agents to avoid the collision - // by moving only on the horizontal plane relative to the player velocity. - if (ignore_y_) { - // Skip if these are in two different heights -#define ABS(m_v) (((m_v) < 0) ? (-(m_v)) : (m_v)) - if (ABS(relativePosition[1]) > combinedRadius * 2) { - continue; - } - relativePosition[1] = 0; - relativeVelocity[1] = 0; - } + //const float timeHorizon_mod = (avoidance_priority_ - other->avoidance_priority_ + 1.0f) * 0.5f; + //const float invTimeHorizon = (1.0f / timeHorizon_) * timeHorizon_mod; + const Vector3 relativePosition = other->position_ - position_; + const Vector3 relativeVelocity = velocity_ - other->velocity_; const float distSq = absSq(relativePosition); + const float combinedRadius = radius_ + other->radius_; const float combinedRadiusSq = sqr(combinedRadius); Plane plane; @@ -168,17 +160,17 @@ namespace RVO { const float b = relativePosition * relativeVelocity; const float c = absSq(relativeVelocity) - absSq(cross(relativePosition, relativeVelocity)) / (distSq - combinedRadiusSq); const float t = (b + std::sqrt(sqr(b) - a * c)) / a; - const Vector3 ww = relativeVelocity - t * relativePosition; - const float wwLength = abs(ww); - const Vector3 unitWW = ww / wwLength; + const Vector3 w = relativeVelocity - t * relativePosition; + const float wLength = abs(w); + const Vector3 unitW = w / wLength; - plane.normal = unitWW; - u = (combinedRadius * t - wwLength) * unitWW; + plane.normal = unitW; + u = (combinedRadius * t - wLength) * unitW; } } else { /* Collision. */ - const float invTimeStep = 1.0f / timeStep; + const float invTimeStep = 1.0f / sim_->timeStep_; const Vector3 w = relativeVelocity - invTimeStep * relativePosition; const float wLength = abs(w); const Vector3 unitW = w / wLength; @@ -196,40 +188,52 @@ namespace RVO { if (planeFail < orcaPlanes_.size()) { linearProgram4(orcaPlanes_, planeFail, maxSpeed_, newVelocity_); } - - if (ignore_y_) { - // Not 100% necessary, but better to have. - newVelocity_[1] = prefVelocity_[1]; - } } - void Agent::insertAgentNeighbor(const Agent *agent, float &rangeSq) + void Agent3D::insertAgentNeighbor(const Agent3D *agent, float &rangeSq) { - if (this != agent) { - const float distSq = absSq(position_ - agent->position_); + // no point processing same agent + if (this == agent) { + return; + } + // ignore other agent if layers/mask bitmasks have no matching bit + if ((avoidance_mask_ & agent->avoidance_layers_) == 0) { + return; + } - if (distSq < rangeSq) { - if (agentNeighbors_.size() < maxNeighbors_) { - agentNeighbors_.push_back(std::make_pair(distSq, agent)); - } + if (avoidance_priority_ > agent->avoidance_priority_) { + return; + } - size_t i = agentNeighbors_.size() - 1; + const float distSq = absSq(position_ - agent->position_); - while (i != 0 && distSq < agentNeighbors_[i - 1].first) { - agentNeighbors_[i] = agentNeighbors_[i - 1]; - --i; - } + if (distSq < rangeSq) { + if (agentNeighbors_.size() < maxNeighbors_) { + agentNeighbors_.push_back(std::make_pair(distSq, agent)); + } - agentNeighbors_[i] = std::make_pair(distSq, agent); + size_t i = agentNeighbors_.size() - 1; - if (agentNeighbors_.size() == maxNeighbors_) { - rangeSq = agentNeighbors_.back().first; - } + while (i != 0 && distSq < agentNeighbors_[i - 1].first) { + agentNeighbors_[i] = agentNeighbors_[i - 1]; + --i; + } + + agentNeighbors_[i] = std::make_pair(distSq, agent); + + if (agentNeighbors_.size() == maxNeighbors_) { + rangeSq = agentNeighbors_.back().first; } } } - bool linearProgram1(const std::vector<Plane> &planes, size_t planeNo, const Line &line, float radius, const Vector3 &optVelocity, bool directionOpt, Vector3 &result) + void Agent3D::update(RVOSimulator3D *sim_) + { + velocity_ = newVelocity_; + position_ += velocity_ * sim_->timeStep_; + } + + bool linearProgram1(const std::vector<Plane> &planes, size_t planeNo, const Line3D &line, float radius, const Vector3 &optVelocity, bool directionOpt, Vector3 &result) { const float dotProduct = line.point * line.direction; const float discriminant = sqr(dotProduct) + sqr(radius) - absSq(line.point); @@ -248,7 +252,7 @@ namespace RVO { const float denominator = line.direction * planes[i].normal; if (sqr(denominator) <= RVO3D_EPSILON) { - /* Lines line is (almost) parallel to plane i. */ + /* Lines3D line is (almost) parallel to plane i. */ if (numerator > 0.0f) { return false; } @@ -352,7 +356,7 @@ namespace RVO { return false; } - Line line; + Line3D line; line.direction = normalize(crossProduct); const Vector3 lineNormal = cross(line.direction, planes[planeNo].normal); line.point = planes[planeNo].point + (((planes[i].point - planes[planeNo].point) * planes[i].normal) / (lineNormal * planes[i].normal)) * lineNormal; diff --git a/thirdparty/rvo2/Agent.h b/thirdparty/rvo2/rvo2_3d/Agent3d.h index 45fbead2f5..d99e3cac66 100644 --- a/thirdparty/rvo2/Agent.h +++ b/thirdparty/rvo2/rvo2_3d/Agent3d.h @@ -8,7 +8,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * https://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -27,7 +27,7 @@ * Chapel Hill, N.C. 27599-3175 * United States of America * - * <https://gamma.cs.unc.edu/RVO2/> + * <http://gamma.cs.unc.edu/RVO2/> */ /** @@ -38,80 +38,68 @@ #define RVO3D_AGENT_H_ #include <cstddef> +#include <cstdint> #include <utility> #include <vector> +#include "RVOSimulator3d.h" #include "Vector3.h" -// Note: Slightly modified to work better in Godot. -// - The agent can be created by anyone. -// - The simulator pointer is removed. -// - The update function is removed. -// - The compute velocity function now need the timeStep. -// - Moved the `Plane` class here. -// - Added a new parameter `ignore_y_` in the `Agent`. This parameter is used to control a godot feature that allows to avoid collisions by moving on the horizontal plane. -namespace RVO { - /** - * \brief Defines a plane. - */ - class Plane { - public: - /** - * \brief A point on the plane. - */ - Vector3 point; - - /** - * \brief The normal to the plane. - */ - Vector3 normal; - }; - +namespace RVO3D { /** * \brief Defines an agent in the simulation. */ - class Agent { + class Agent3D { public: /** * \brief Constructs an agent instance. * \param sim The simulator instance. */ - explicit Agent(); + explicit Agent3D(); /** * \brief Computes the neighbors of this agent. */ - void computeNeighbors(class KdTree *kdTree_); + void computeNeighbors(RVOSimulator3D *sim_); /** * \brief Computes the new velocity of this agent. */ - void computeNewVelocity(float timeStep); + void computeNewVelocity(RVOSimulator3D *sim_); /** * \brief Inserts an agent neighbor into the set of neighbors of this agent. * \param agent A pointer to the agent to be inserted. * \param rangeSq The squared range around this agent. */ - void insertAgentNeighbor(const Agent *agent, float &rangeSq); + void insertAgentNeighbor(const Agent3D *agent, float &rangeSq); + + /** + * \brief Updates the three-dimensional position and three-dimensional velocity of this agent. + */ + void update(RVOSimulator3D *sim_); Vector3 newVelocity_; Vector3 position_; Vector3 prefVelocity_; Vector3 velocity_; + RVOSimulator3D *sim_; size_t id_; size_t maxNeighbors_; float maxSpeed_; float neighborDist_; float radius_; float timeHorizon_; - std::vector<std::pair<float, const Agent *> > agentNeighbors_; + float timeHorizonObst_; + std::vector<std::pair<float, const Agent3D *> > agentNeighbors_; std::vector<Plane> orcaPlanes_; - /// This is a godot feature that allows the Agent to avoid collision by mooving - /// on the horizontal plane. - bool ignore_y_; + float height_ = 1.0; + uint32_t avoidance_layers_ = 1; + uint32_t avoidance_mask_ = 1; + float avoidance_priority_ = 1.0; - friend class KdTree; + friend class KdTree3D; + friend class RVOSimulator3D; }; } diff --git a/thirdparty/rvo2/Definitions.h b/thirdparty/rvo2/rvo2_3d/Definitions.h index 707d3c897f..34d1d06e0a 100644 --- a/thirdparty/rvo2/Definitions.h +++ b/thirdparty/rvo2/rvo2_3d/Definitions.h @@ -38,7 +38,7 @@ #ifndef RVO3D_DEFINITIONS_H_ #define RVO3D_DEFINITIONS_H_ -namespace RVO { +namespace RVO3D { /** * \brief Computes the square of a float. * \param scalar The float to be squared. diff --git a/thirdparty/rvo2/KdTree.cpp b/thirdparty/rvo2/rvo2_3d/KdTree3d.cpp index c857f299df..2534871db1 100644 --- a/thirdparty/rvo2/KdTree.cpp +++ b/thirdparty/rvo2/rvo2_3d/KdTree3d.cpp @@ -30,19 +30,20 @@ * <https://gamma.cs.unc.edu/RVO2/> */ -#include "KdTree.h" +#include "KdTree3d.h" #include <algorithm> -#include "Agent.h" +#include "Agent3d.h" #include "Definitions.h" +#include "RVOSimulator3d.h" -namespace RVO { +namespace RVO3D { const size_t RVO3D_MAX_LEAF_SIZE = 10; - KdTree::KdTree() { } + KdTree3D::KdTree3D(RVOSimulator3D *sim) : sim_(sim) { } - void KdTree::buildAgentTree(std::vector<Agent *> agents) + void KdTree3D::buildAgentTree(std::vector<Agent3D *> agents) { agents_.swap(agents); @@ -52,7 +53,7 @@ namespace RVO { } } - void KdTree::buildAgentTreeRecursive(size_t begin, size_t end, size_t node) + void KdTree3D::buildAgentTreeRecursive(size_t begin, size_t end, size_t node) { agentTree_[node].begin = begin; agentTree_[node].end = end; @@ -120,12 +121,12 @@ namespace RVO { } } - void KdTree::computeAgentNeighbors(Agent *agent, float rangeSq) const + void KdTree3D::computeAgentNeighbors(Agent3D *agent, float rangeSq) const { queryAgentTreeRecursive(agent, rangeSq, 0); } - void KdTree::queryAgentTreeRecursive(Agent *agent, float &rangeSq, size_t node) const + void KdTree3D::queryAgentTreeRecursive(Agent3D *agent, float &rangeSq, size_t node) const { if (agentTree_[node].end - agentTree_[node].begin <= RVO3D_MAX_LEAF_SIZE) { for (size_t i = agentTree_[node].begin; i < agentTree_[node].end; ++i) { diff --git a/thirdparty/rvo2/KdTree.h b/thirdparty/rvo2/rvo2_3d/KdTree3d.h index 69d8920ce0..c018f98b23 100644 --- a/thirdparty/rvo2/KdTree.h +++ b/thirdparty/rvo2/rvo2_3d/KdTree3d.h @@ -41,22 +41,19 @@ #include "Vector3.h" -// Note: Slightly modified to work better with Godot. -// - Removed `sim_`. -// - KdTree things are public -namespace RVO { - class Agent; - class RVOSimulator; +namespace RVO3D { + class Agent3D; + class RVOSimulator3D; /** * \brief Defines <i>k</i>d-trees for agents in the simulation. */ - class KdTree { + class KdTree3D { public: /** * \brief Defines an agent <i>k</i>d-tree node. */ - class AgentTreeNode { + class AgentTreeNode3D { public: /** * \brief The beginning node number. @@ -93,12 +90,12 @@ namespace RVO { * \brief Constructs a <i>k</i>d-tree instance. * \param sim The simulator instance. */ - explicit KdTree(); + explicit KdTree3D(RVOSimulator3D *sim); /** * \brief Builds an agent <i>k</i>d-tree. */ - void buildAgentTree(std::vector<Agent *> agents); + void buildAgentTree(std::vector<Agent3D *> agents); void buildAgentTreeRecursive(size_t begin, size_t end, size_t node); @@ -107,15 +104,16 @@ namespace RVO { * \param agent A pointer to the agent for which agent neighbors are to be computed. * \param rangeSq The squared range around the agent. */ - void computeAgentNeighbors(Agent *agent, float rangeSq) const; + void computeAgentNeighbors(Agent3D *agent, float rangeSq) const; - void queryAgentTreeRecursive(Agent *agent, float &rangeSq, size_t node) const; + void queryAgentTreeRecursive(Agent3D *agent, float &rangeSq, size_t node) const; - std::vector<Agent *> agents_; - std::vector<AgentTreeNode> agentTree_; + std::vector<Agent3D *> agents_; + std::vector<AgentTreeNode3D> agentTree_; + RVOSimulator3D *sim_; - friend class Agent; - friend class RVOSimulator; + friend class Agent3D; + friend class RVOSimulator3D; }; } diff --git a/thirdparty/rvo2/rvo2_3d/RVOSimulator3d.cpp b/thirdparty/rvo2/rvo2_3d/RVOSimulator3d.cpp new file mode 100644 index 0000000000..71e5aea9e2 --- /dev/null +++ b/thirdparty/rvo2/rvo2_3d/RVOSimulator3d.cpp @@ -0,0 +1,274 @@ +/* + * RVOSimulator.cpp + * RVO2-3D Library + * + * Copyright 2008 University of North Carolina at Chapel Hill + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Please send all bug reports to <geom@cs.unc.edu>. + * + * The authors may be contacted via: + * + * Jur van den Berg, Stephen J. Guy, Jamie Snape, Ming C. Lin, Dinesh Manocha + * Dept. of Computer Science + * 201 S. Columbia St. + * Frederick P. Brooks, Jr. Computer Science Bldg. + * Chapel Hill, N.C. 27599-3175 + * United States of America + * + * <http://gamma.cs.unc.edu/RVO2/> + */ + +#include "RVOSimulator3d.h" + +#ifdef _OPENMP +#include <omp.h> +#endif + +#include "Agent3d.h" +#include "KdTree3d.h" + +namespace RVO3D { + RVOSimulator3D::RVOSimulator3D() : defaultAgent_(NULL), kdTree_(NULL), globalTime_(0.0f), timeStep_(0.0f) + { + kdTree_ = new KdTree3D(this); + } + + RVOSimulator3D::RVOSimulator3D(float timeStep, float neighborDist, size_t maxNeighbors, float timeHorizon, float radius, float maxSpeed, const Vector3 &velocity) : defaultAgent_(NULL), kdTree_(NULL), globalTime_(0.0f), timeStep_(timeStep) + { + kdTree_ = new KdTree3D(this); + defaultAgent_ = new Agent3D(); + + defaultAgent_->maxNeighbors_ = maxNeighbors; + defaultAgent_->maxSpeed_ = maxSpeed; + defaultAgent_->neighborDist_ = neighborDist; + defaultAgent_->radius_ = radius; + defaultAgent_->timeHorizon_ = timeHorizon; + defaultAgent_->velocity_ = velocity; + } + + RVOSimulator3D::~RVOSimulator3D() + { + if (defaultAgent_ != NULL) { + delete defaultAgent_; + } + + for (size_t i = 0; i < agents_.size(); ++i) { + delete agents_[i]; + } + + if (kdTree_ != NULL) { + delete kdTree_; + } + } + + size_t RVOSimulator3D::getAgentNumAgentNeighbors(size_t agentNo) const + { + return agents_[agentNo]->agentNeighbors_.size(); + } + + size_t RVOSimulator3D::getAgentAgentNeighbor(size_t agentNo, size_t neighborNo) const + { + return agents_[agentNo]->agentNeighbors_[neighborNo].second->id_; + } + + size_t RVOSimulator3D::getAgentNumORCAPlanes(size_t agentNo) const + { + return agents_[agentNo]->orcaPlanes_.size(); + } + + const Plane &RVOSimulator3D::getAgentORCAPlane(size_t agentNo, size_t planeNo) const + { + return agents_[agentNo]->orcaPlanes_[planeNo]; + } + + void RVOSimulator3D::removeAgent(size_t agentNo) + { + delete agents_[agentNo]; + agents_[agentNo] = agents_.back(); + agents_.pop_back(); + } + + size_t RVOSimulator3D::addAgent(const Vector3 &position) + { + if (defaultAgent_ == NULL) { + return RVO3D_ERROR; + } + + Agent3D *agent = new Agent3D(); + + agent->position_ = position; + agent->maxNeighbors_ = defaultAgent_->maxNeighbors_; + agent->maxSpeed_ = defaultAgent_->maxSpeed_; + agent->neighborDist_ = defaultAgent_->neighborDist_; + agent->radius_ = defaultAgent_->radius_; + agent->timeHorizon_ = defaultAgent_->timeHorizon_; + agent->velocity_ = defaultAgent_->velocity_; + + agent->id_ = agents_.size(); + + agents_.push_back(agent); + + return agents_.size() - 1; + } + + size_t RVOSimulator3D::addAgent(const Vector3 &position, float neighborDist, size_t maxNeighbors, float timeHorizon, float radius, float maxSpeed, const Vector3 &velocity) + { + Agent3D *agent = new Agent3D(); + + agent->position_ = position; + agent->maxNeighbors_ = maxNeighbors; + agent->maxSpeed_ = maxSpeed; + agent->neighborDist_ = neighborDist; + agent->radius_ = radius; + agent->timeHorizon_ = timeHorizon; + agent->velocity_ = velocity; + + agent->id_ = agents_.size(); + + agents_.push_back(agent); + + return agents_.size() - 1; + } + + void RVOSimulator3D::doStep() + { + kdTree_->buildAgentTree(agents_); + + for (int i = 0; i < static_cast<int>(agents_.size()); ++i) { + agents_[i]->computeNeighbors(this); + agents_[i]->computeNewVelocity(this); + } + + for (int i = 0; i < static_cast<int>(agents_.size()); ++i) { + agents_[i]->update(this); + } + + globalTime_ += timeStep_; + } + + size_t RVOSimulator3D::getAgentMaxNeighbors(size_t agentNo) const + { + return agents_[agentNo]->maxNeighbors_; + } + + float RVOSimulator3D::getAgentMaxSpeed(size_t agentNo) const + { + return agents_[agentNo]->maxSpeed_; + } + + float RVOSimulator3D::getAgentNeighborDist(size_t agentNo) const + { + return agents_[agentNo]->neighborDist_; + } + + const Vector3 &RVOSimulator3D::getAgentPosition(size_t agentNo) const + { + return agents_[agentNo]->position_; + } + + const Vector3 &RVOSimulator3D::getAgentPrefVelocity(size_t agentNo) const + { + return agents_[agentNo]->prefVelocity_; + } + + float RVOSimulator3D::getAgentRadius(size_t agentNo) const + { + return agents_[agentNo]->radius_; + } + + float RVOSimulator3D::getAgentTimeHorizon(size_t agentNo) const + { + return agents_[agentNo]->timeHorizon_; + } + + const Vector3 &RVOSimulator3D::getAgentVelocity(size_t agentNo) const + { + return agents_[agentNo]->velocity_; + } + + float RVOSimulator3D::getGlobalTime() const + { + return globalTime_; + } + + size_t RVOSimulator3D::getNumAgents() const + { + return agents_.size(); + } + + float RVOSimulator3D::getTimeStep() const + { + return timeStep_; + } + + void RVOSimulator3D::setAgentDefaults(float neighborDist, size_t maxNeighbors, float timeHorizon, float radius, float maxSpeed, const Vector3 &velocity) + { + if (defaultAgent_ == NULL) { + defaultAgent_ = new Agent3D(); + } + + defaultAgent_->maxNeighbors_ = maxNeighbors; + defaultAgent_->maxSpeed_ = maxSpeed; + defaultAgent_->neighborDist_ = neighborDist; + defaultAgent_->radius_ = radius; + defaultAgent_->timeHorizon_ = timeHorizon; + defaultAgent_->velocity_ = velocity; + } + + void RVOSimulator3D::setAgentMaxNeighbors(size_t agentNo, size_t maxNeighbors) + { + agents_[agentNo]->maxNeighbors_ = maxNeighbors; + } + + void RVOSimulator3D::setAgentMaxSpeed(size_t agentNo, float maxSpeed) + { + agents_[agentNo]->maxSpeed_ = maxSpeed; + } + + void RVOSimulator3D::setAgentNeighborDist(size_t agentNo, float neighborDist) + { + agents_[agentNo]->neighborDist_ = neighborDist; + } + + void RVOSimulator3D::setAgentPosition(size_t agentNo, const Vector3 &position) + { + agents_[agentNo]->position_ = position; + } + + void RVOSimulator3D::setAgentPrefVelocity(size_t agentNo, const Vector3 &prefVelocity) + { + agents_[agentNo]->prefVelocity_ = prefVelocity; + } + + void RVOSimulator3D::setAgentRadius(size_t agentNo, float radius) + { + agents_[agentNo]->radius_ = radius; + } + + void RVOSimulator3D::setAgentTimeHorizon(size_t agentNo, float timeHorizon) + { + agents_[agentNo]->timeHorizon_ = timeHorizon; + } + + void RVOSimulator3D::setAgentVelocity(size_t agentNo, const Vector3 &velocity) + { + agents_[agentNo]->velocity_ = velocity; + } + + void RVOSimulator3D::setTimeStep(float timeStep) + { + timeStep_ = timeStep; + } +} diff --git a/thirdparty/rvo2/rvo2_3d/RVOSimulator3d.h b/thirdparty/rvo2/rvo2_3d/RVOSimulator3d.h new file mode 100644 index 0000000000..4ea093d74c --- /dev/null +++ b/thirdparty/rvo2/rvo2_3d/RVOSimulator3d.h @@ -0,0 +1,324 @@ +/* + * RVOSimulator.h + * RVO2-3D Library + * + * Copyright 2008 University of North Carolina at Chapel Hill + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Please send all bug reports to <geom@cs.unc.edu>. + * + * The authors may be contacted via: + * + * Jur van den Berg, Stephen J. Guy, Jamie Snape, Ming C. Lin, Dinesh Manocha + * Dept. of Computer Science + * 201 S. Columbia St. + * Frederick P. Brooks, Jr. Computer Science Bldg. + * Chapel Hill, N.C. 27599-3175 + * United States of America + * + * <http://gamma.cs.unc.edu/RVO2/> + */ + +/** + * \file RVOSimulator.h + * \brief Contains the RVOSimulator class. + */ +#ifndef RVO3D_RVO_SIMULATOR_H_ +#define RVO3D_RVO_SIMULATOR_H_ + +#include <cstddef> +#include <limits> +#include <vector> + +#include "Vector3.h" + +namespace RVO3D { + class Agent3D; + class KdTree3D; + + /** + * \brief Error value. + * + * A value equal to the largest unsigned integer, which is returned in case of an error by functions in RVO3D::RVOSimulator. + */ + const size_t RVO3D_ERROR = std::numeric_limits<size_t>::max(); + + /** + * \brief Defines a plane. + */ + class Plane { + public: + /** + * \brief A point on the plane. + */ + Vector3 point; + + /** + * \brief The normal to the plane. + */ + Vector3 normal; + }; + + /** + * \brief Defines the simulation. + * + * The main class of the library that contains all simulation functionality. + */ + class RVOSimulator3D { + public: + /** + * \brief Constructs a simulator instance. + */ + RVOSimulator3D(); + + /** + * \brief Constructs a simulator instance and sets the default properties for any new agent that is added. + * \param timeStep The time step of the simulation. Must be positive. + * \param neighborDist The default maximum distance (center point to center point) to other agents a new agent takes into account in the navigation. The larger this number, the longer he running time of the simulation. If the number is too low, the simulation will not be safe. Must be non-negative. + * \param maxNeighbors The default maximum number of other agents a new agent takes into account in the navigation. The larger this number, the longer the running time of the simulation. If the number is too low, the simulation will not be safe. + * \param timeHorizon The default minimum amount of time for which a new agent's velocities that are computed by the simulation are safe with respect to other agents. The larger this number, the sooner an agent will respond to the presence of other agents, but the less freedom the agent has in choosing its velocities. Must be positive. + * \param radius The default radius of a new agent. Must be non-negative. + * \param maxSpeed The default maximum speed of a new agent. Must be non-negative. + * \param velocity The default initial three-dimensional linear velocity of a new agent (optional). + */ + RVOSimulator3D(float timeStep, float neighborDist, size_t maxNeighbors, float timeHorizon, float radius, float maxSpeed, const Vector3 &velocity = Vector3()); + + /** + * \brief Destroys this simulator instance. + */ + ~RVOSimulator3D(); + + /** + * \brief Adds a new agent with default properties to the simulation. + * \param position The three-dimensional starting position of this agent. + * \return The number of the agent, or RVO3D::RVO3D_ERROR when the agent defaults have not been set. + */ + size_t addAgent(const Vector3 &position); + + /** + * \brief Adds a new agent to the simulation. + * \param position The three-dimensional starting position of this agent. + * \param neighborDist The maximum distance (center point to center point) to other agents this agent takes into account in the navigation. The larger this number, the longer the running time of the simulation. If the number is too low, the simulation will not be safe. Must be non-negative. + * \param maxNeighbors The maximum number of other agents this agent takes into account in the navigation. The larger this number, the longer the running time of the simulation. If the number is too low, the simulation will not be safe. + * \param timeHorizon The minimum amount of time for which this agent's velocities that are computed by the simulation are safe with respect to other agents. The larger this number, the sooner this agent will respond to the presence of other agents, but the less freedom this agent has in choosing its velocities. Must be positive. + * \param radius The radius of this agent. Must be non-negative. + * \param maxSpeed The maximum speed of this agent. Must be non-negative. + * \param velocity The initial three-dimensional linear velocity of this agent (optional). + * \return The number of the agent. + */ + size_t addAgent(const Vector3 &position, float neighborDist, size_t maxNeighbors, float timeHorizon, float radius, float maxSpeed, const Vector3 &velocity = Vector3()); + + /** + * \brief Lets the simulator perform a simulation step and updates the three-dimensional position and three-dimensional velocity of each agent. + */ + void doStep(); + + /** + * \brief Returns the specified agent neighbor of the specified agent. + * \param agentNo The number of the agent whose agent neighbor is to be retrieved. + * \param neighborNo The number of the agent neighbor to be retrieved. + * \return The number of the neighboring agent. + */ + size_t getAgentAgentNeighbor(size_t agentNo, size_t neighborNo) const; + + /** + * \brief Returns the maximum neighbor count of a specified agent. + * \param agentNo The number of the agent whose maximum neighbor count is to be retrieved. + * \return The present maximum neighbor count of the agent. + */ + size_t getAgentMaxNeighbors(size_t agentNo) const; + + /** + * \brief Returns the maximum speed of a specified agent. + * \param agentNo The number of the agent whose maximum speed is to be retrieved. + * \return The present maximum speed of the agent. + */ + float getAgentMaxSpeed(size_t agentNo) const; + + /** + * \brief Returns the maximum neighbor distance of a specified agent. + * \param agentNo The number of the agent whose maximum neighbor distance is to be retrieved. + * \return The present maximum neighbor distance of the agent. + */ + float getAgentNeighborDist(size_t agentNo) const; + + /** + * \brief Returns the count of agent neighbors taken into account to compute the current velocity for the specified agent. + * \param agentNo The number of the agent whose count of agent neighbors is to be retrieved. + * \return The count of agent neighbors taken into account to compute the current velocity for the specified agent. + */ + size_t getAgentNumAgentNeighbors(size_t agentNo) const; + + /** + * \brief Returns the count of ORCA constraints used to compute the current velocity for the specified agent. + * \param agentNo The number of the agent whose count of ORCA constraints is to be retrieved. + * \return The count of ORCA constraints used to compute the current velocity for the specified agent. + */ + size_t getAgentNumORCAPlanes(size_t agentNo) const; + + /** + * \brief Returns the specified ORCA constraint of the specified agent. + * \param agentNo The number of the agent whose ORCA constraint is to be retrieved. + * \param planeNo The number of the ORCA constraint to be retrieved. + * \return A plane representing the specified ORCA constraint. + * \note The halfspace to which the normal of the plane points is the region of permissible velocities with respect to the specified ORCA constraint. + */ + const Plane &getAgentORCAPlane(size_t agentNo, size_t planeNo) const; + + /** + * \brief Returns the three-dimensional position of a specified agent. + * \param agentNo The number of the agent whose three-dimensional position is to be retrieved. + * \return The present three-dimensional position of the (center of the) agent. + */ + const Vector3 &getAgentPosition(size_t agentNo) const; + + /** + * \brief Returns the three-dimensional preferred velocity of a specified agent. + * \param agentNo The number of the agent whose three-dimensional preferred velocity is to be retrieved. + * \return The present three-dimensional preferred velocity of the agent. + */ + const Vector3 &getAgentPrefVelocity(size_t agentNo) const; + + /** + * \brief Returns the radius of a specified agent. + * \param agentNo The number of the agent whose radius is to be retrieved. + * \return The present radius of the agent. + */ + float getAgentRadius(size_t agentNo) const; + + /** + * \brief Returns the time horizon of a specified agent. + * \param agentNo The number of the agent whose time horizon is to be retrieved. + * \return The present time horizon of the agent. + */ + float getAgentTimeHorizon(size_t agentNo) const; + + /** + * \brief Returns the three-dimensional linear velocity of a specified agent. + * \param agentNo The number of the agent whose three-dimensional linear velocity is to be retrieved. + * \return The present three-dimensional linear velocity of the agent. + */ + const Vector3 &getAgentVelocity(size_t agentNo) const; + + /** + * \brief Returns the global time of the simulation. + * \return The present global time of the simulation (zero initially). + */ + float getGlobalTime() const; + + /** + * \brief Returns the count of agents in the simulation. + * \return The count of agents in the simulation. + */ + size_t getNumAgents() const; + + /** + * \brief Returns the time step of the simulation. + * \return The present time step of the simulation. + */ + float getTimeStep() const; + + /** + * \brief Removes an agent from the simulation. + * \param agentNo The number of the agent that is to be removed. + * \note After the removal of the agent, the agent that previously had number getNumAgents() - 1 will now have number agentNo. + */ + void removeAgent(size_t agentNo); + + /** + * \brief Sets the default properties for any new agent that is added. + * \param neighborDist The default maximum distance (center point to center point) to other agents a new agent takes into account in the navigation. The larger this number, the longer he running time of the simulation. If the number is too low, the simulation will not be safe. Must be non-negative. + * \param maxNeighbors The default maximum number of other agents a new agent takes into account in the navigation. The larger this number, the longer the running time of the simulation. If the number is too low, the simulation will not be safe. + * \param timeHorizon The default minimum amount of time for which a new agent's velocities that are computed by the simulation are safe with respect to other agents. The larger this number, the sooner an agent will respond to the presence of other agents, but the less freedom the agent has in choosing its velocities. Must be positive. + * \param radius The default radius of a new agent. Must be non-negative. + * \param maxSpeed The default maximum speed of a new agent. Must be non-negative. + * \param velocity The default initial three-dimensional linear velocity of a new agent (optional). + */ + void setAgentDefaults(float neighborDist, size_t maxNeighbors, float timeHorizon, float radius, float maxSpeed, const Vector3 &velocity = Vector3()); + + /** + * \brief Sets the maximum neighbor count of a specified agent. + * \param agentNo The number of the agent whose maximum neighbor count is to be modified. + * \param maxNeighbors The replacement maximum neighbor count. + */ + void setAgentMaxNeighbors(size_t agentNo, size_t maxNeighbors); + + /** + * \brief Sets the maximum speed of a specified agent. + * \param agentNo The number of the agent whose maximum speed is to be modified. + * \param maxSpeed The replacement maximum speed. Must be non-negative. + */ + void setAgentMaxSpeed(size_t agentNo, float maxSpeed); + + /** + * \brief Sets the maximum neighbor distance of a specified agent. + * \param agentNo The number of the agent whose maximum neighbor distance is to be modified. + * \param neighborDist The replacement maximum neighbor distance. Must be non-negative. + */ + void setAgentNeighborDist(size_t agentNo, float neighborDist); + + /** + * \brief Sets the three-dimensional position of a specified agent. + * \param agentNo The number of the agent whose three-dimensional position is to be modified. + * \param position The replacement of the three-dimensional position. + */ + void setAgentPosition(size_t agentNo, const Vector3 &position); + + /** + * \brief Sets the three-dimensional preferred velocity of a specified agent. + * \param agentNo The number of the agent whose three-dimensional preferred velocity is to be modified. + * \param prefVelocity The replacement of the three-dimensional preferred velocity. + */ + void setAgentPrefVelocity(size_t agentNo, const Vector3 &prefVelocity); + + /** + * \brief Sets the radius of a specified agent. + * \param agentNo The number of the agent whose radius is to be modified. + * \param radius The replacement radius. Must be non-negative. + */ + void setAgentRadius(size_t agentNo, float radius); + + /** + * \brief Sets the time horizon of a specified agent with respect to other agents. + * \param agentNo The number of the agent whose time horizon is to be modified. + * \param timeHorizon The replacement time horizon with respect to other agents. Must be positive. + */ + void setAgentTimeHorizon(size_t agentNo, float timeHorizon); + + /** + * \brief Sets the three-dimensional linear velocity of a specified agent. + * \param agentNo The number of the agent whose three-dimensional linear velocity is to be modified. + * \param velocity The replacement three-dimensional linear velocity. + */ + void setAgentVelocity(size_t agentNo, const Vector3 &velocity); + + /** + * \brief Sets the time step of the simulation. + * \param timeStep The time step of the simulation. Must be positive. + */ + void setTimeStep(float timeStep); + + public: + Agent3D *defaultAgent_; + KdTree3D *kdTree_; + float globalTime_; + float timeStep_; + std::vector<Agent3D *> agents_; + + friend class Agent3D; + friend class KdTree3D; + }; +} + +#endif diff --git a/thirdparty/rvo2/Vector3.h b/thirdparty/rvo2/rvo2_3d/Vector3.h index f44e311f29..6fa4bb074c 100644 --- a/thirdparty/rvo2/Vector3.h +++ b/thirdparty/rvo2/rvo2_3d/Vector3.h @@ -41,13 +41,11 @@ #include <cstddef> #include <ostream> -#define RVO3D_EXPORT - -namespace RVO { +namespace RVO3D { /** * \brief Defines a three-dimensional vector. */ - class RVO3D_EXPORT Vector3 { + class Vector3 { public: /** * \brief Constructs and initializes a three-dimensional vector instance to zero. @@ -60,6 +58,17 @@ namespace RVO { } /** + * \brief Constructs and initializes a three-dimensional vector from the specified three-dimensional vector. + * \param vector The three-dimensional vector containing the xyz-coordinates. + */ + inline Vector3(const Vector3 &vector) + { + val_[0] = vector[0]; + val_[1] = vector[1]; + val_[2] = vector[2]; + } + + /** * \brief Constructs and initializes a three-dimensional vector from the specified three-element array. * \param val The three-element array containing the xyz-coordinates. */ @@ -255,6 +264,15 @@ namespace RVO { return *this; } + inline Vector3 &operator=(const Vector3 &vector) + { + val_[0] = vector[0]; + val_[1] = vector[1]; + val_[2] = vector[2]; + + return *this; + } + private: float val_[3]; }; @@ -267,7 +285,7 @@ namespace RVO { * \param vector The three-dimensional vector with which the scalar multiplication should be computed. * \return The scalar multiplication of the three-dimensional vector with the scalar value. */ - RVO3D_EXPORT inline Vector3 operator*(float scalar, const Vector3 &vector) + inline Vector3 operator*(float scalar, const Vector3 &vector) { return Vector3(scalar * vector[0], scalar * vector[1], scalar * vector[2]); } @@ -279,7 +297,7 @@ namespace RVO { * \param vector2 The second vector with which the cross product should be computed. * \return The cross product of the two specified vectors. */ - RVO3D_EXPORT inline Vector3 cross(const Vector3 &vector1, const Vector3 &vector2) + inline Vector3 cross(const Vector3 &vector1, const Vector3 &vector2) { return Vector3(vector1[1] * vector2[2] - vector1[2] * vector2[1], vector1[2] * vector2[0] - vector1[0] * vector2[2], vector1[0] * vector2[1] - vector1[1] * vector2[0]); } @@ -291,7 +309,7 @@ namespace RVO { * \param vector The three-dimensional vector which to insert into the output stream. * \return A reference to the output stream. */ - RVO3D_EXPORT inline std::ostream &operator<<(std::ostream &os, const Vector3 &vector) + inline std::ostream &operator<<(std::ostream &os, const Vector3 &vector) { os << "(" << vector[0] << "," << vector[1] << "," << vector[2] << ")"; @@ -304,7 +322,7 @@ namespace RVO { * \param vector The three-dimensional vector whose length is to be computed. * \return The length of the three-dimensional vector. */ - RVO3D_EXPORT inline float abs(const Vector3 &vector) + inline float abs(const Vector3 &vector) { return std::sqrt(vector * vector); } @@ -315,7 +333,7 @@ namespace RVO { * \param vector The three-dimensional vector whose squared length is to be computed. * \return The squared length of the three-dimensional vector. */ - RVO3D_EXPORT inline float absSq(const Vector3 &vector) + inline float absSq(const Vector3 &vector) { return vector * vector; } @@ -326,7 +344,7 @@ namespace RVO { * \param vector The three-dimensional vector whose normalization is to be computed. * \return The normalization of the three-dimensional vector. */ - RVO3D_EXPORT inline Vector3 normalize(const Vector3 &vector) + inline Vector3 normalize(const Vector3 &vector) { return vector / abs(vector); } |